>>返回《C# 併發編程》html
注意: 本篇文章講述的是在 .Net Framework 環境下的分析, 可是我相信這與 .Net Core 設計思想是一致,但在實現上必定優化了不少。編程
下面開始本次講述:安全
ExecutionContext 實際上只是線程相關其餘上下文的容器。架構
ExecutionContext 與周圍環境的信息有關,這意味着,代碼正在運行時,它存儲了與 當前環境 或 「context」 有關的數據。併發
周圍環境: 代碼執行處,能夠訪問到的變量、方法、屬性等等。框架
在同步世界:異步
[ThreadStatic]
字段或 ThreadLocal<T>
中。
在異步世界,TLS變得可有可無,同步異步對比:async
ExecutionContext 其實是一個 state 包post
ExecutionContext 是使用靜態方法 Capture 捕獲的:優化
// 周圍環境的 state 捕獲到 ec 中 ExecutionContext ec = ExecutionContext.Capture();
經過靜態方法 Run ,在委託(Run方法的參數)調用時恢復 ExecutionContext
ExecutionContext.Run(ec, delegate { … // 這裏的代碼將上述 ec 的狀態視爲周圍環境 }, null);
全部派生異步工做的方法都以這種方式捕獲和還原 ExecutionContext 的。
例如:
Task.Run
時,對 Run
的調用將從調用線程中捕獲 ExecutionContext ,並將該 ExecutionContext 實例存儲到 Task
對象中Task.Run
的委託做爲該 Task
執行的一部分被調用時,它是使用存儲的 ExecutionContext 經過 ExecutionContext.Run
來完成的如下全部異步API的執行都是捕獲 ExecutionContext 並將其存儲,而後在調用某些代碼時再使用存儲的 ExecutionContext。
Task.Run
ThreadPool.QueueUserWorkItem
Delegate.BeginInvoke
Stream.BeginRead
DispatcherSynchronizationContext.Post
當咱們談論「flowing ExecutionContext」時,咱們其實是在討論:
前面咱們介紹了 SynchronizationContext
是如何調度線程的,如今,咱們要進行進行一次對比:
SynchronizationContext.Post
只是使用捕獲的狀態來調用委託,而不是在調用委託時設置該狀態爲當前狀態
async
和 await
關鍵字背後的框架支持會自動與 ExecutionContext 和 SynchronizationContext 交互。
每當代碼等待一個可等待項(awaitable),該可等待項(awaitable) 的 等待者(awaiter) 說還沒有完成時
IsCompleted
返回 false
則該方法須要暫停,並經過等待者(awaiter) 的 continuation
來恢復。
等待者(awaiter) : 能夠理解爲
await
產生的 Task對象。
await
的代碼一直流到 continuation
委託的執行。
async
方法即將掛起時,基礎設施將捕獲 ExecutionContextExecutionContext
帶領,啓用重要的周圍環境信息,去流過 awaits 。該框架還支持 SynchronizationContext 。前述對 ExecutionContext 的支持內置於表示 async
方法的「構建器」中
System.Runtime.CompilerServices.AsyncTaskMethodBuilder
await
/ async
會被編譯成執行碼而且這些構建器可確保 ExecutionContext 跨 await
點流動,不管使用哪一種可等待項(awaitable)。
相反,對 SynchronizationContext 的支持內置在 awaiting
的且已經構建好的Task
和 Task<TResult>
中
自定義的等待者(awaiter) (好比 new Task(...)
)能夠本身添加相似的邏輯,可是不會自動得到實例化時的SynchronizationContext
await
一個 task 時,默認狀況下,等待者(awaiter) 將捕獲當前的 SynchronizationContext(若是有的話)Post
這個前面提供的 continuation 委託並回到該 context 進行執行
ThreadPool
的線程上若是開發人員不但願這種封送處理行爲,則能夠經過更改在那裏使用的 可等待項(awaitable) / 等待者(awaiter) 來控制它。
Task
或 Task<TResult>
就時採用上述方式await
方法 task.ConfigureAwait(…)
的返回值來修改這種封送處理行爲
ConfigureAwait()
返回一個 可等待項(awaitable),它能夠抑制此默認的封送處理行爲。ConfigureAwait()
的惟一 bool
類型參數 continueOnCapturedContext
ConfigureAwait
如何,在恢復執行的線程上,運行時都會檢查當前的 context ,以肯定:
儘管 ConfigureAwait
提供了,用於改變 SynchronizationContext 行爲的、顯示的、與 await
相關的編程模型,可是沒有用於抑制 ExecutionContext
流動的、與 await
相關的編程模型支持。
SynchronizationContext 不是 ExecutionContext 的一部分嗎?
當您調用公共 ExecutionContext.Capture()
方法時,它將檢查當前的 SynchronizationContext ,若是有,則將其存儲到返回的 ExecutionContext 實例中。而後,當使用公共 ExecutionContext.Run(...)
方法時,在提供的委託執行期間,該捕獲的 SynchronizationContext 被恢復爲 Current 。
爲何這有問題?做爲 ExecutionContext 的一部分而流動的 SynchronizationContext 更改了 SynchronizationContext.Current
的含義。
應該能夠經過 SynchronizationContext.Current
返回到你最近調用 Current
時的環境
SynchronizationContext
流出,成爲另外一個線程的當前 SynchronizationContext
,則 SynchronizationContext.Current
就沒有意義了,因此不是這樣設計的。解釋此問題的一個示例,代碼以下:
private async void button1_Click(object sender, EventArgs e) { button1.Text = await Task.Run(async delegate { string data = await DownloadAsync(); return Compute(data); }); }
Task.Run
);
Compute(data)
);Task
完成button1.Text
屬性。若是 SynchronizationContext 不做爲 ExecutionContext 的一部分流動,個人預期就是有根據的。
若是 SynchronizationContext 流動了,不管如何,我將感到很是失望。
假設:SynchronizationContext 做爲 ExecutionContext 的一部分流動:
Task.Run
在調用時捕獲 ExecutionContext ,並使用它運行傳遞給它委託。Task.Run
調用時的當前 SynchronizationContext 將流動到 Task
中,並且將在 DownloadAsync
執行和等待結果期間成爲當前 SynchronizationContext ,
await
將看到當前 SynchronizationContext
,並 Post
異步方法的其他部分做爲一個 continuation 返回到 UI線程 上運行。Compute
方法將在 UI線程 上運行,而不是在 ThreadPool 上運行,從而致使個人應用程序出現響應性問題。從實際結果來看這是不對的,假設執行的代碼更像下面的
private async void button1_Click(object sender, EventArgs e) { string data = await DownloadAsync(); button1.Text = Compute(data); }
實際: 如今,咱們看看實際是如何處理的:
Task.Run(...)
這種異步Api的實現:
Capture
方法:
public
,供外部使用internal
的方法,是 mscorlib 大多數公開的異步功能(如:Task.Run(...)
)所使用的一個
Run
方法的 internal
重載也支持忽略存儲在 ExecutionContext 中的 SynchronizationContext
這意味着:
標識 async
關鍵字方法的實現:
async
方法中流動 ExecutionContext 所使用的方式
internal
的重載作一些事情。Post
回來是分開的async
方法的基礎設施嘗試忽略因爲流動而將 SynchronizationContexts 設置爲 Current 。SynchronizationContext.Current
不會「流動」穿過 await
點。參考資料:
《ExecutionContext vs SynchronizationContext》 --- Stephen Toub