【C# TAP 異步編程】三、async\await的運作機理詳解


【原創】 本文只是個人筆記,很多錯誤,歡迎指出。

環境:vs2022  .net6.0 C#10

參考:https://blog.csdn.net/brook_shi/article/details/50803957

Await 就像一個一元運算符:它接受一個參數,一個可等待的("awaitable"是一個異步操作)

使用場景:1、首次顯示頁面/表單時,需要將其同步初始化為一種"正在加載"狀態,然后啟動異步操作以檢索所需的數據。稍后,當數據到達時,更新現有頁面/表單以顯示處於"已加載"狀態的數據。同樣適用與wpf開發。

 異步編程

異步編程就是使用future模式(又稱promise)或者回調機制來實現(Non-blocking on waiting)。

如果使用回調或事件來實現(容易callback hell),不僅編寫這樣的代碼不直觀,很快就容易把代碼搞得一團糟。不過在.NET 4.5(C# 5)中引入的async/await關鍵字(在.NET 4.0中通過添加Microsoft.Bcl.Async包也可以使用),讓編寫異步代碼變得容易和優雅。通過使用async/await關鍵字,可以像寫同步代碼那樣編寫異步代碼,所有的回調和事件處理都交給編譯器和運行時幫你處理了。

使用異步編程有兩個好處:不阻塞主線程(比如UI線程),提高服務端應用的吞吐量。所以微軟推薦ASP.NET中默認使用異步來處理請求。

介紹關鍵Async 和 Await


異步方法看起來像這樣:

public async Task DoSomethingAsync()
{
  // In the Real World, we would actually do something...
  // For this example, we're just going to (asynchronously) wait 100ms.
  await Task.Delay(100);
}

“async”關鍵字啟用該方法中的“await”關鍵字,並讓編譯器改變該方法的處理方式,生成一個異步狀態機類,將方法內部代碼剪切到狀態機類的內部,這就是async關鍵字所做的一切!它不會在線程池線程上運行此方法。async關鍵字只啟用await關鍵字(並管理方法結果)。

異步方法的開始就像其他方法一樣執行。也就是說,它同步運行,直到遇到“await”(或拋出異常)。
await關鍵字是可以實現異步的地方。Await就像一個一元操作符:它只接受一個參數,一個awaitable(“awaitable”是一個異步操作)。Await檢查Await,看看它是否已經完成;如果awaitable已經完成,那么該方法將繼續運行(同步運行,就像常規方法一樣)。
如果“await”看到awaitable還沒有完成,那么它將采取異步操作。它告訴awaitable在完成時運行該方法的剩余部分,然后從async方法返回。
稍后,當awaitable完成時,它將執行async方法的其余部分。如果你正在等待一個內置的awaitable(比如一個任務),那么async方法的其余部分將在“await”返回之前捕獲的“上下文”上執行。
我喜歡將“等待”視為“異步等待”。也就是說,async方法會暫停直到awaitable完成(所以它會等待),但實際的線程沒有被阻塞(所以它是異步的)。

等待者模式

Await/Async 異步使模式使用的是等待者模式。等待者模式要求等待者公開IsCompleted屬性,GetResult方法和OnCompleted方法(可選地帶有UnsafeOnCompleted方法,unsafe表示不使用可執行上下 回導致漏洞)。

Awaitable-可等待類型

正如我所提到的,"await"采用單個參數 - 一個"awaitable" - 這是一個異步操作。.NET 框架中已經有兩種常見的可等待類型:Task<T> 和 Task。
特殊方法(如"Task.Yield")返回YieldAwaitable可等待對象,並且 WinRT 運行時(在 Windows 8 中提供)具有非托管的可等待類型。
您還可以創建自己的可等待類型(通常出於性能原因),或使用擴展方法使不可等待的類型可等待。
關於 awaitables 的一個要點是:它是可等待的類型,而不是返回該類型的方法。換句話說,您可以等待返回 Task ...因為該方法返回 Task,而不是因為它是異步的。因此,您也可以等待返回 Task 的非異步方法的結果:

public async Task NewStuffAsync()
{
  // 異步方法
  await ...
}

public Task MyOldTaskParallelLibraryCode()
{
  // 不是異步方法  
  ...
}

public async Task ComposeAsync()
{
  // We can await Tasks, regardless of where they come from.
  await NewStuffAsync();
  await MyOldTaskParallelLibraryCode();
}

 

返回類型

異步方法可以返回 Task<T>、Task 或 void。在幾乎所有情況下,您都希望返回 Task<T> 或 Task,並且僅在必要時返回 void。

為什么要返回 Task<T> 或 Task?因為它們是可以等待的,而虛空不是。因此,如果您有一個異步方法返回 Task<T> 或 Task,則可以將結果傳遞給等待。使用void方法,您無需等待任何東西。

當您具有異步事件處理程序時,必須返回 void。

返回值

返回 Task 或 void 的異步方法沒有返回值。返回 Task<T> 的異步方法必須返回 T 類型的值:

public async Task<int> CalculateAnswer()
{
  await Task.Delay(100); // (Probably should be longer...)

  // Return a type of "int", not "Task<int>"
  return 42;
}

這有點奇怪,但這種設計背后有很好的理由

問:為什么我不能寫這個:

// Hypothetical syntax
public async int GetValue()
{
  await TaskEx.Delay(100);
  return 13; // Return type is "int"
}

考慮一下:方法簽名對調用方的外觀如何?返回值的類型為是可等待的對象,那么我們對異步方法調用時候可以給該方法或對象標注成await。如果其他類型我們將很難判斷該方法是否可以標注成await。

上下文

在概述中,我提到,當您等待內置的可等待對象時,等待對象將捕獲當前的"上下文",然后將其應用於異步方法的其余部分。這個"背景"到底是什么?

簡單的答案:

  1. 如果你位於 UI 線程上,則它是 UI 上下文。
  2. 如果您正在響應 ASP.NET 請求,則這是一個 ASP.NET 請求上下文。
  3. 否則,它通常是線程池上下文。

復雜的答案:

  1. 如果 SynchronizationContext.Current 不為 null,則它是當前的 SyncContext。(UI 和 ASP.NET 請求上下文是同步上下文上下文)。
  2. 否則,它是當前的 TaskScheduler(TaskScheduler.Default 是線程池上下文)。

這在現實世界中意味着什么?首先,捕獲(和還原)UI/ASP.NET 上下文是透明完成的:

// WinForms example (it works exactly the same for WPF).
private async void DownloadFileButton_Click(object sender, EventArgs e)
{
  // Since we asynchronously wait, the UI thread is not blocked by the file download.
  await DownloadFileAsync(fileNameTextBox.Text);

  // Since we resume on the UI context, we can directly access UI elements.
  resultTextBox.Text = "File downloaded!";
}

// ASP.NET example
protected async void MyButton_Click(object sender, EventArgs e)
{
  // Since we asynchronously wait, the ASP.NET thread is not blocked by the file download.
  // This allows the thread to handle other requests while we're waiting.
  await DownloadFileAsync(...);

  // Since we resume on the ASP.NET context, we can access the current request.
  // We may actually be on another *thread*, but we have the same ASP.NET request context.
  Response.Write("File downloaded!");
}

這對於事件處理程序非常有用,但事實證明,它不是您希望大多數其他代碼(實際上,您將要編寫的大多數異步代碼)所需要的。

避免上下文

大多數情況下,您不需要同步回"主要"上下文。大多數異步方法在設計時都會考慮組合:它們等待其他操作,每個操作本身都表示一個異步操作(可以由其他操作組成)。在這種情況下,您希望通過調用 ConfigureAwait 並傳遞 false 來告訴等待者不要捕獲當前上下文,例如:

private async Task DownloadFileAsync(string fileName)
{
  // Use HttpClient or whatever to download the file contents.
  var fileContents = await DownloadFileContentsAsync(fileName).ConfigureAwait(false);

  // Note that because of the ConfigureAwait(false), we are not on the original context here.
  // Instead, we're running on the thread pool.

  // Write the file contents out to a disk file.
  await WriteToDiskAsync(fileName, fileContents).ConfigureAwait(false);

  // The second call to ConfigureAwait(false) is not *required*, but it is Good Practice.
}

// WinForms example (it works exactly the same for WPF).
private async void DownloadFileButton_Click(object sender, EventArgs e)
{
  // Since we asynchronously wait, the UI thread is not blocked by the file download.
  await DownloadFileAsync(fileNameTextBox.Text);

  // Since we resume on the UI context, we can directly access UI elements.
  resultTextBox.Text = "File downloaded!";
}

此示例需要注意的重要一點是,異步方法調用的每個"級別"都有自己的上下文。DownloadFileButton_Click在 UI 上下文中啟動,名為 DownloadFileAsync。DownloadFileAsync 也在 UI 上下文中啟動,但隨后通過調用 ConfigureAwait(false) 退出了其上下文。下載文件異步的其余部分在線程池上下文中運行。但是,當 DownloadFileAsync 完成並DownloadFileButton_Click恢復時,它會在 UI 上下文中恢復。

一個好的經驗法則是使用 ConfigureAwait(false),除非您知道確實需要上下文。

異步組合

到目前為止,我們只考慮了串行組合:異步方法一次等待一個操作。也可以啟動多個操作並等待其中一個(或所有)完成。您可以通過啟動操作來執行此操作,但直到稍后才等待它們:

public async Task DoOperationsConcurrentlyAsync()
{
  Task[] tasks = new Task[3];
  tasks[0] = DoOperation0Async();
  tasks[1] = DoOperation1Async();
  tasks[2] = DoOperation2Async();

  // At this point, all three tasks are running at the same time.

  // Now, we await them all.
  await Task.WhenAll(tasks);
}

public async Task<int> GetFirstToRespondAsync()
{
  // Call two web services; take the first response.
  Task<int>[] tasks = new[] { WebService1Async(), WebService2Async() };

  // Await for the first one to respond.
  Task<int> firstTask = await Task.WhenAny(tasks);

  // Return the result.
  return await firstTask;
}

通過使用並發組合(Task.WhenAll 或 Task.WhenAny),可以執行簡單的並發操作。您還可以將這些方法與 Task.Run 一起使用,以進行簡單的並行計算。但是,這不能替代任務並行庫 - 任何高級 CPU 密集型並行操作都應使用 TPL 完成。

原文:異步和等待 (stephencleary.com)

 

異步的使用心得

當你省略await/async時候異常的報錯方式也會不一樣。
所以你當你理解異步原理后,還是建議不要省略await/async。

Task 無疑是一個現代 OVERLAPPED 抽象。它表示一些工作,通常是 I/O 密集型的,可以由 CPU 之外的某些工作完成,並且當該工作完成時,任務將收到通知。
事件處理程序是從同步角度設計的,這就是為什么將它們與異步一起使用是很尷尬的原因。

async\await的代碼運行順序

通過分析一段代碼運行來了解async\await的運作機理(本案例是控制台應用程序)

namespace MyTask;
class Program
{
    public static void Main(string[] args)
    {
        Task<int> baconTask = GetUrlContentLengthAsync(3);
        baconTask.ContinueWith(t => Console.WriteLine(t.Result));
        Console.Read();
    }
    static async Task<int> GetUrlContentLengthAsync(int slices)
    {
        HttpClient httpClient = new();

        //********* 也可以合並起來寫*********//
       // string content = await httpClient.GetStringAsync("https://www.cnblogs.com/majiang/p/7899202.html");
//將生成TaskAwaiter的等待器,該等待器繼承 ITaskAwaiter Task<string> getContent= httpClient.GetStringAsync("https://www.cnblogs.com/majiang/p/7899202.html"); string content = await getContent; //await getContent反編譯后是awaiter = <getContent>5__2.GetAwaiter(); DoInDependentWork(); return content.Length; } static void DoInDependentWork() { Console.WriteLine($"content.Length:Working"); } }

 

備用 改進后的代碼,輸出線程的變化

 

using System.Runtime.CompilerServices;

namespace MyTask;
class Program
{
    public static void Main(string[] args)
    {
        Console.WriteLine("mian方法在異步方法 標志之前的線程:{0}", Environment.CurrentManagedThreadId);
        Task<int> baconTask = GetUrlContentLengthAsync(3);
        //   baconTask.ContinueWith(t => Console.WriteLine(t.Result));
 
        baconTask.Wait();
        Console.WriteLine("mian方法在異步方法 標志之后的線程:{0}", Environment.CurrentManagedThreadId);

        Console.Read();
    }
    static async Task<int> GetUrlContentLengthAsync(int slices)
    {
        Console.WriteLine("異步方法在await 標志之前的線程:{0}", Environment.CurrentManagedThreadId);

        HttpClient httpClient = new();
 
        // string content = await httpClient.GetStringAsync("https://www.cnblogs.com/majiang/p/7899202.html");

        Task<string> getContent = httpClient.GetStringAsync("https://www.cnblogs.com/majiang/p/7899202.html");
        string content = await getContent;

        //await getContent反編譯后是awaiter = <getContent>5__2.GetAwaiter();
        Console.WriteLine("異步方法在await 標志之后的線程:{0}",Environment.CurrentManagedThreadId);
      
        return content.Length;
    }
    static void DoInDependentWork()
    {

        Console.WriteLine($"content.Length:Working");

    }
}
/*
輸出
mian方法在異步方法 標志之前的線程:1
異步方法在await 標志之前的線程:1
異步方法在await 標志之后的線程:10
mian方法在異步方法 標志之后的線程:1
*/
View Code

 

IL反編譯后的代碼

// MyTask.Program
using System;
using System.Diagnostics;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using MyTask;

[System.Runtime.CompilerServices.NullableContext(1)]
[System.Runtime.CompilerServices.Nullable(0)]
internal class Program
{
    [Serializable]
    [CompilerGenerated]
    private sealed class <>c
    {
        [System.Runtime.CompilerServices.Nullable(0)]
        public static readonly <>c <>9 = new <>c();

        [System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })]
        public static Action<Task<int>> <>9__0_0;

        internal void <Main>b__0_0(Task<int> t)
        {
            Console.WriteLine(t.Result);
        }
    }

    [CompilerGenerated]
    private sealed class <GetUrlContentLengthAsync>d__1 : IAsyncStateMachine
    {
        public int <>1__state;

        [System.Runtime.CompilerServices.Nullable(0)]
        public AsyncTaskMethodBuilder<int> <>t__builder;

        public int slices;

        [System.Runtime.CompilerServices.Nullable(0)]
        private HttpClient <httpClient>5__1;

        [System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })]
        private Task<string> <getContent>5__2;

        [System.Runtime.CompilerServices.Nullable(0)]
        private string <content>5__3;

        [System.Runtime.CompilerServices.Nullable(0)]
        private string <>s__4;

        [System.Runtime.CompilerServices.Nullable(new byte[] { 0, 1 })]
        private TaskAwaiter<string> <>u__1;

        private void MoveNext()
        {
            int num = <>1__state;
            int length;
            try
            {
                TaskAwaiter<string> awaiter;
                if (num != 0)
                {
                    <httpClient>5__1 = new HttpClient();
                    <getContent>5__2 = <httpClient>5__1.GetStringAsync("https://www.cnblogs.com/majiang/p/7899202.html");
                    awaiter = <getContent>5__2.GetAwaiter();
                    if (!awaiter.IsCompleted)
                    {
                        num = (<>1__state = 0);
                        <>u__1 = awaiter;
                        <GetUrlContentLengthAsync>d__1 stateMachine = this;
                        <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
                        return;
                    }
                }
                else
                {
                    awaiter = <>u__1;
                    <>u__1 = default(TaskAwaiter<string>);
                    num = (<>1__state = -1);
                }
                <>s__4 = awaiter.GetResult();
                <content>5__3 = <>s__4;
                <>s__4 = null;
                DoInDependentWork();
                length = <content>5__3.Length;
            }
            catch (Exception exception)
            {
                <>1__state = -2;
                <httpClient>5__1 = null;
                <getContent>5__2 = null;
                <content>5__3 = null;
                <>t__builder.SetException(exception);
                return;
            }
            <>1__state = -2;
            <httpClient>5__1 = null;
            <getContent>5__2 = null;
            <content>5__3 = null;
            <>t__builder.SetResult(length);
        }

        void IAsyncStateMachine.MoveNext()
        {
            //ILSpy generated this explicit interface implementation from .override directive in MoveNext
            this.MoveNext();
        }

        [DebuggerHidden]
        private void SetStateMachine(IAsyncStateMachine stateMachine)
        {
        }

        void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
        {
            //ILSpy generated this explicit interface implementation from .override directive in SetStateMachine
            this.SetStateMachine(stateMachine);
        }
    }

    public static void Main(string[] args)
    {
        Task<int> baconTask = GetUrlContentLengthAsync(3);
        baconTask.ContinueWith(<>c.<>9__0_0 ?? (<>c.<>9__0_0 = new Action<Task<int>>(<>c.<>9.<Main>b__0_0)));
        Console.Read();
    }

    [AsyncStateMachine(typeof(<GetUrlContentLengthAsync>d__1))]
    [DebuggerStepThrough]
    private static Task<int> GetUrlContentLengthAsync(int slices)
    {
        <GetUrlContentLengthAsync>d__1 stateMachine = new <GetUrlContentLengthAsync>d__1();
        stateMachine.<>t__builder = AsyncTaskMethodBuilder<int>.Create();
        stateMachine.slices = slices;
        stateMachine.<>1__state = -1;
        stateMachine.<>t__builder.Start(ref stateMachine);
        return stateMachine.<>t__builder.Task;
    }

    private static void DoInDependentWork()
    {
        Console.WriteLine("content.Length:Working");
    }
}
View Code

 通過IL代碼我們可以分析出整個代碼運行過程 如下:

 

關系圖中的數字對應於以下步驟,在調用方法調用異步方法時啟動。

  1. Main調用方法調用並等待 GetUrlContentLengthAsync 異步方法。

  2. GetUrlContentLengthAsync 可創建 HttpClient 實例並調用 GetStringAsync 異步方法以下載網站內容作為字符串。

  3. GetStringAsync 中發生了某種情況,該情況掛起了它的進程。 可能必須等待網站下載或一些其他阻止活動。 為避免阻止資源,GetStringAsync 會將控制權出讓給其調用方 GetUrlContentLengthAsync

    GetStringAsync 返回 Task<TResult>,其中 TResult 為字符串,並且 GetUrlContentLengthAsync 將任務分配給 getStringTask 變量。 該任務表示調用 GetStringAsync 的正在進行的進程,其中承諾當工作完成時產生實際字符串值。

  4. 由於尚未等待 getStringTask,因此,GetUrlContentLengthAsync 可以繼續執行不依賴於 GetStringAsync 得出的最終結果的其他工作。 該任務由對同步方法 DoIndependentWork 的調用表示。

  5. DoIndependentWork 是完成其工作並返回其調用方的同步方法。

  6. GetUrlContentLengthAsync 已運行完畢,可以不受 getStringTask 的結果影響。 接下來,GetUrlContentLengthAsync 需要計算並返回已下載的字符串的長度,但該方法只有在獲得字符串的情況下才能計算該值。

    因此,GetUrlContentLengthAsync 使用一個 await 運算符來掛起其進度,並把控制權交給調用 GetUrlContentLengthAsync 的方法。 GetUrlContentLengthAsyncTask<int> 返回給調用方。 該任務表示對產生下載字符串長度的整數結果的一個承諾。

    備注

    如果 GetStringAsync(因此 getStringTask)在 GetUrlContentLengthAsync 等待前完成,則控制會保留在 GetUrlContentLengthAsync 中。 如果異步調用過程 getStringTask 已完成,並且 GetUrlContentLengthAsync 不必等待最終結果,則掛起然后返回到 GetUrlContentLengthAsync 將造成成本浪費。

    在調用方法中,處理模式會繼續。 在等待結果前,調用方可以開展不依賴於 GetUrlContentLengthAsync 結果的其他工作,否則就需等待片刻。 調用方法等待 GetUrlContentLengthAsync,而 GetUrlContentLengthAsync 等待 GetStringAsync

  7. GetStringAsync 完成並生成一個字符串結果。 字符串結果不是通過按你預期的方式調用 GetStringAsync 所返回的。 (記住,該方法已返回步驟 3 中的一個任務)。相反,字符串結果存儲在表示 getStringTask 方法完成的任務中。 await 運算符從 getStringTask 中檢索結果。   賦值語句將檢索到的結果賦給 contents

  8. GetUrlContentLengthAsync 具有字符串結果時,該方法可以計算字符串長度。 然后,GetUrlContentLengthAsync 工作也將完成,並且等待事件處理程序可繼續使用。 在此主題結尾處的完整示例中,可確認事件處理程序檢索並打印長度結果的值。 如果你不熟悉異步編程,請花 1 分鍾時間考慮同步行為和異步行為之間的差異。 當其工作完成時(第 5 步)會返回一個同步方法,但當其工作掛起時(第 3 步和第 6 步),異步方法會返回一個任務值。 在異步方法最終完成其工作時,任務會標記為已完成,而結果(如果有)將存儲在任務中。

 

以上內容來自:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/async/task-asynchronous-programming-model

 

 async\await的運行機理

反編譯后我們可以看到async\await的運作機理主要分為分異步狀態機和等待器,現在我主要來講解着兩部分的運行機制。

 

1、異步狀態機

(1)異步狀態機 始狀態是-1;

(2)運行第一個【等待器】 期間異步狀態機的狀態是0。

(3)第一個【等待器】完成后異步狀態機狀態恢復-1。

(4)運行等二個【等待器】期間 異步狀態機的狀態是1,后面【等待器】以此類推。

(5)當所有的等待器都完成后,異步狀態機的狀態為-2。

異步狀態機的組成

 

 

 

2、等待器

 TaskAwaiter等待器

第一個案例中代碼await httpClient.GetStringAsync("https://www.cnblogs.com/majiang/p/7899202.html");生成TaskAwaiter類型等待器。該類型等待器繼承ITaskAwaiter接口

(1)有返回值的等待器,它返回值就是異步方法public Task<TResult> GetStringAsync(string? requestUri)返回值Task<TResult> 中的TResult。

此例子 中httpClient.GetStringAsync() 返回值是Task<string>。

所以等待器 TaskAwaiter的返回值是 string(Task<string>中的string)。

 

ConfiguredTaskAwaiter等待器

 通過第 以下代碼了解 等二種等待器類型

await httpClient.GetStringAsync("https://www.cnblogs.com/majiang/p/7899202.html").ConfigureAwait(false);生成ConfiguredTaskAwaitable<string>.ConfiguredTaskAwaiter類型等待器.
該等待器將被設置成bool m_continueOnCapturedContext屬性被設置成fasle,  該等待器繼承IConfiguredTaskAwaiter接口。  

 StateMachineBoxAwareAwaiter等待器

 await Task.Yield();生成IStateMachineBoxAwareAwaiter類型等待器.,該類型的等待器繼承自IStateMachineBoxAwareAwaiter接口

 Task.Yield 簡單來說就是創建時就已經完成的 Task ,或者說執行時間為0的 Task ,或者說是空任務。
我們知道在 await 時會釋放當前線程,等所 await 的 Task 完成時會從線程池中申請新的線程繼續執行 await 之后的代碼,這本來是為了解決異步操作(比如IO操作)霸占線程實際卻用不到線程的問題,而 Task.Yield 卻產生了一個不僅沒有異步操作而且什么也不干的 Task ,不是吃飽了撐着嗎?
真正的圖謀是借助 await 實現線程的切換,讓 await 之后的操作重新排隊從線程池中申請線程繼續執行。
將不太重要的比較耗時的操作放在新的線程(重新排隊從線程池中申請到的線程)中執行。

 

3、通過IL代碼,查看異步的執行過程,以及最后是如何把任務交給線程池運行

 異步代碼大概運行順序:

步驟1、主程序main調用異步f方法GetUrlContentLengthAsync()

步驟2、GetUrlContentLengthAsync();完成狀態機初始化,然后調用狀態機中異步任務方法構建器 中的開始函數:stateMachine.<>t__builder.Start(ref stateMachine);

步驟3、運行Start()函數中的AsyncMethodBuilderCore.Start(ref stateMachine);語句,保存當前線程的ExecutionContext(執行狀態機)和SynchronizationContext(同步狀態機),然后執行stateMachine.MoveNext();

該語句調用狀態機的 MoveNext()方法;步驟4 開始進入狀態機的MoveNext()方法;

 

步驟4、生成等待器 awaiter = <getContent>5__2.GetAwaiter(); 將狀態機更改成0;然后運行<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);

 步驟5開始, 就是任務是一步一步被送入線程池運行,然后程序把控制權還給調用程序main。

步驟5、運行AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine, ref m_task);

步驟6、生成狀態機盒子,將狀態機和當前線程執行上下文 封裝入狀態機 代碼如下:

IAsyncStateMachineBox stateMachineBox = GetStateMachineBox(ref stateMachine, ref taskField);

然后運行AwaitUnsafeOnCompleted(ref awaiter, stateMachineBox);

步驟7、執行以下代碼。根據不同的等待器類型,選擇進入askAwaiter.UnsafeOnCompletedInternal()方法中。

 

 //第一種等待器 獲取同步上下文
     if (default(TAwaiter) != null && awaiter is ITaskAwaiter)
    {
        TaskAwaiter.UnsafeOnCompletedInternal(Unsafe.As<TAwaiter, TaskAwaiter>(ref awaiter).m_task, box, true);
        return;
    }
    //第二種等待器   根據初始化設置決定要不要獲取同步上下文
    if (default(TAwaiter) != null && awaiter is IConfiguredTaskAwaiter)
    {
        ref ConfiguredTaskAwaitable.ConfiguredTaskAwaiter reference = ref Unsafe.As<TAwaiter, ConfiguredTaskAwaitable.ConfiguredTaskAwaiter>(ref awaiter);
        TaskAwaiter.UnsafeOnCompletedInternal(reference.m_task, box, reference.m_continueOnCapturedContext);
        return;
    }
//第三種等待器
    if (default(TAwaiter) != null && awaiter is IStateMachineBoxAwareAwaiter)
    {
        try
        {
            ((IStateMachineBoxAwareAwaiter)(object)awaiter).AwaitUnsafeOnCompleted(box);
            return;
        }
        catch (Exception exception)
        {
            System.Threading.Tasks.Task.ThrowAsync(exception, null);
            return;
        }
    }

 

 

 

TaskAwaiter.UnsafeOnCompletedInternal方法聲明如下:
internal static void UnsafeOnCompletedInternal(Task task, IAsyncStateMachineBox stateMachineBox, bool continueOnCapturedContext)

 

 

步驟8、然后運行以下代碼,進入 task.SetContinuationForAwait()方法中

internal static void UnsafeOnCompletedInternal(Task task, IAsyncStateMachineBox stateMachineBox, bool continueOnCapturedContext)
{
    if (TplEventSource.Log.IsEnabled() || Task.s_asyncDebuggingEnabled)
    {
        task.SetContinuationForAwait(OutputWaitEtwEvents(task, stateMachineBox.MoveNextAction), continueOnCapturedContext, false);
    }
    else
    {
        task.UnsafeSetContinuationForAwait(stateMachineBox, continueOnCapturedContext);
    }
}

 

運行OutputWaitEtwEvents(task, stateMachineBox.MoveNextAction,將任務和狀態機的moveNext()封裝成新的 委托。然后task.SetContinuationForAwait()運行該委托。

捕獲當前同步上下文,

步驟9、運行以下代碼, 這時候就用到了步驟7 提到的參數continueOnCapturedContext。由於步驟7傳入是true 所以該方法就獲取了SynchronizationContext同步上下文。

 

internal void SetContinuationForAwait(Action continuationAction, bool continueOnCapturedContext, bool flowExecutionContext)
{
    TaskContinuation taskContinuation = null;
    if (continueOnCapturedContext)
    {
        SynchronizationContext current = SynchronizationContext.Current;//獲取SynchronizationContext同步上下文,控制台程序為null,ui程序該項不為null
        if (current != null && current.GetType() != typeof(SynchronizationContext))
        {
           //SynchronizationContext不等於null線程執行這一步
            taskContinuation = new SynchronizationContextAwaitTaskContinuation(current, continuationAction, flowExecutionContext);//將同步上希望一起封裝入了實例TaskContinuation中
        }
        else
        {
           //SynchronizationContext等於null線程執行這一步
            TaskScheduler internalCurrent = TaskScheduler.InternalCurrent;
            if (internalCurrent != null && internalCurrent != TaskScheduler.Default)
            {
                taskContinuation = new TaskSchedulerAwaitTaskContinuation(internalCurrent, continuationAction, flowExecutionContext);
            }
        }
    }
    if (taskContinuation == null && flowExecutionContext)
    {
        taskContinuation = new AwaitTaskContinuation(continuationAction, true);
    }
    if (taskContinuation != null)
    {
        if (!AddTaskContinuation(taskContinuation, false))
        {
            
           //當線程池中的線程完成任務后該線程 會用taskContinuation.Run()中的SynchronizationContext同步上下文運行異步方法中剩下代碼。//由於控制台沒有顯示的SynchronizationContext(控制台有默認的SynchronizationContext),
// 默認的SynchronizationContext沒有轉移控執行權的功能,所以控制台會繼續使用多線程執行剩下異步方法中的代碼。默認的SynchronizationContext內部有計數器來保證剩余的異步方法只執行一次。
// 但是ui線程有SynchronizationContext同步上下文DispachuerSynchronizationContext,該同步上下文會讓會執行權轉移到回到主線程,讓主線程執行剩下的異步方法中的代碼,如果主線程中調用方法中使用task.wait()或者task<Tresult>.result ,
//那么就會形成死鎖。
taskContinuation.Run(this, false); } } else if (!AddTaskContinuation(continuationAction, false)) { //continueOnCapturedContext=false 執行這一步 AwaitTaskContinuation.UnsafeScheduleAction(continuationAction, this); } }

 

 

步驟10、跟蹤到這里,終於看到真面目了,UnsafeQueueUserWorkItemInternal()方法向線程池的操作請求隊列添加一個工作項以及其他數據項目,然后方法就會立即返回( 回到最開始的調用位置)。我知道還可以繼續跟着,但我覺得跟蹤到這邊已經夠了。

internal static void UnsafeScheduleAction(Action action, Task task)
{
    AwaitTaskContinuation awaitTaskContinuation = new AwaitTaskContinuation(action, false);
    TplEventSource log = TplEventSource.Log;
    if (log.IsEnabled() && task != null)
    {
        awaitTaskContinuation.m_continuationId = Task.NewId();
        log.AwaitTaskContinuationScheduled((task.ExecutingTaskScheduler ?? TaskScheduler.Default).Id, task.Id, awaitTaskContinuation.m_continuationId);
    }
    ThreadPool.UnsafeQueueUserWorkItemInternal(awaitTaskContinuation, true);
}

 回到最開始的調用位置<>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);

然后執行return;將控制權還給主程序。

private void MoveNext()
    {
        int num = <>1__state;
        int length;
        try
        {
            TaskAwaiter<string> awaiter;
            if (num != 0)
            {
                <httpClient>5__1 = new HttpClient();
                <getContent>5__2 = <httpClient>5__1.GetStringAsync("https://www.cnblogs.com/majiang/p/7899202.html");
                awaiter = <getContent>5__2.GetAwaiter();
                if (!awaiter.IsCompleted)
                {
                    num = (<>1__state = 0);
                    <>u__1 = awaiter;
                    <GetUrlContentLengthAsync>d__1 stateMachine = this;
                    <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
                    return;
                }
            }

 

最終線程池中的某個線程會處理該工作項。

 

 

參考: https://www.cnblogs.com/lsxqw2004/p/4922374.html


免責聲明!

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



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