async/Await使用和原理


await/async是.NetFramework4.5出現的,是語法糖,由編譯器提供的功能!

await/async 是C#保留關鍵字,通常是成對出現,一般的建議是:要么不用,要么用到底

  1. async修飾方法,可以單獨出現,但是沒有任何意義,而且有警告
  2. await在方法體,只能出現在task/async方法前面,只有await會報錯

下面來使用代碼來剖析async和await的用法。

一:只有一個async沒有await

/// <summary>
 /// 只有async沒有await,會有個warn
 /// 跟普通方法沒有區別
 /// </summary>
 private static async void NoReturnNoAwait()
 {
     //主線程執行
     Console.WriteLine($"NoReturnNoAwait Sleep before Task,ThreadId={Thread.CurrentThread.ManagedThreadId}");
     Task task = Task.Run(() =>//啟動新線程完成任務
     {
         Console.WriteLine($"NoReturnNoAwait Sleep before,ThreadId={Thread.CurrentThread.ManagedThreadId}");
         Thread.Sleep(3000);
         Console.WriteLine($"NoReturnNoAwait Sleep after,ThreadId={Thread.CurrentThread.ManagedThreadId}");
     });

     //主線程執行
     Console.WriteLine($"NoReturnNoAwait Sleep after Task,ThreadId={Thread.CurrentThread.ManagedThreadId}");
 }

調用如下:

private async static Task Test()
{
    Console.WriteLine($"start 當前主線程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
    {
        NoReturnNoAwait();
    }            
    Console.WriteLine($"end Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
    Console.Read();
}

運行結果如下:

如果沒有使用async,這就相當於一個普通的方法,另外注意的是方法的返回值使用Task接收,可以不用直接return

二:方法有async也有await

/// <summary>
 /// async/await 
 /// 不能單獨await
 /// await 只能放在task前面
 /// 不推薦void返回值,使用Task來代替
 /// Task和Task<T>能夠使用await, Task.WhenAny, Task.WhenAll等方式組合使用。Async Void 不行
 /// </summary>
 private static async Task NoReturn()
 {
     //主線程執行
     Console.WriteLine($"NoReturn Sleep before await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
     TaskFactory taskFactory = new TaskFactory();
     Task task = taskFactory.StartNew(() =>
     {
         Console.WriteLine($"NoReturn Sleep before,ThreadId={Thread.CurrentThread.ManagedThreadId}");
         Thread.Sleep(6000);
         Console.WriteLine($"NoReturn Sleep after,ThreadId={Thread.CurrentThread.ManagedThreadId}");
     });  
             
     await task;//主線程到這里就返回了,執行主線程任務
     Console.WriteLine($"NoReturn Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}");

     //注意上面
     // await task;
     // Console.WriteLine($"NoReturn Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
     //等同於下面的代碼
     //task.ContinueWith(t =>
     //{
     //    Console.WriteLine($"NoReturn Sleep after await,ThreadId={Thread.CurrentThread.ManagedThreadId}");
     //});
 }

調用如下:

private async static Task Test()
{
    Console.WriteLine($"start 當前主線程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
    {
        NoReturn();
        for (int i = 0; i < 5; i++)
        {
            Thread.Sleep(3000);
            Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId.ToString("00")} i={i}");
        }
    }
    Console.WriteLine($"end Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
    Console.Read();
}

運行結果如下:

從上面結果可以看出:

1:主線程調用async/await方法,主線程遇到await返回執行后續動作,
2:await后面的代碼會等着task任務的完成后再繼續執行,其實就像把await后面的代碼包裝成一個continue的回調動作
3: 然后這個回調動作可能是Task線程,也可能是新的子線程,也可能是主線程

三:有返回值的task,如果要得到返回值,必須要t.Result

/// <summary>
/// 帶返回值的Task  
/// 要使用返回值就一定要等子線程計算完畢
/// </summary>
/// <returns>async 就只返回long</returns>
private static async Task<long> SumAsync()
{
    Console.WriteLine($"SumAsync 111 start ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
    long result = 0;

    await Task.Run(() =>
    {
        for (int k = 0; k <3; k++)
        {
            Console.WriteLine($"SumAsync {k} await Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
            Thread.Sleep(1000);
        }
        for (long i = 0; i < 999999999; i++)
        {
            result += i;
        }
    });

    Console.WriteLine($"SumFactory 111   end ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
    await Task.Run(() =>
    {
        for (int k = 0; k <3; k++)
        {
            Console.WriteLine($"SumAsync11111 {k} await Task.Run ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
            Thread.Sleep(1000);
        }
        for (long i = 0; i < 999999999; i++)
        {
            result += i;
        }
    });          
    Console.WriteLine($"SumFactory 111   end ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");

    return result;
}

調用如下

private async static Task Test()
{
    Console.WriteLine($"start 當前主線程id={Thread.CurrentThread.ManagedThreadId.ToString("00")}");
    {
        Task<long> t = SumAsync();
        Console.WriteLine($"Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
        long result = t.Result;//訪問result   主線程等待Task的完成 等價於t.Wait();
      

        Console.WriteLine($"result=={result}");
    }
    Console.WriteLine($"end Main Thread Task ManagedThreadId={Thread.CurrentThread.ManagedThreadId}");
    Console.Read();
}

得到的結果如下:

通過上面可以得到:如果使用t.Result 或者t.Wait()則相當於堵塞主線程,也就是等待子線程完成之后,主線程才能進行下面的操作,而await 則是不堵塞主線程

四:下面通過代碼和反編譯看一下async的底層是怎么實現的

public class AwaitAsyncILSpy
    {
        public static void Show()
        {
            Console.WriteLine($"start1 {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            Async();
            Console.WriteLine($"aaa2 {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        }
        private static async void Async()
        {
            Console.WriteLine($"ddd5 {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            await Task.Run(() =>
            {
                Thread.Sleep(500);
                Console.WriteLine($"bbb3 {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
            });
            Console.WriteLine($"ccc4 {Thread.CurrentThread.ManagedThreadId.ToString("00")}");
        }
    }

運行得到的結果根據上面的分析肯定是:15234

我們使用ILSPy來反編譯dll,得到下圖:

我們通過上圖發現方法中加了async則會在反編譯的時候增加的AsyncStateMachine狀態(實現了IAsyncStateMachine接口),它實現的原理是:

初始化狀態-1--執行就修改狀態0--再執行就修改狀態-1---執行就修改狀態0---如果出現其他狀態(-2)就結束了,就比如紅綠燈一樣,一樣無限制的循環紅燈-綠燈-紅燈-綠燈

底層實現如下圖:

內容出處:https://www.cnblogs.com/loverwangshan/p/10524464.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM