原文: https://devblogs.microsoft.com/dotnet/configureawait-faq/
.NET 在七多年前在語言和類別庫添加了 async/await ,在那個時候,它像野火一樣流行,不僅遍及.NET生態系統,而且還可以以多種其他語言和框架進行復制,在利用異步的其他語言構造,提供異步支持的API以及進行async/ await相關的基礎架構方面的基本改進方面,.NET也實作了很多改進(特別是.NET Core的性能和支持診斷的改進) ,
但是,async/ await依舊引起疑問的一個方面是ConfigureAwait在這篇文章中,我希望回答其中的許多問題,我希望這篇文章從頭到尾都是可讀的,并且是可以用作將來參考的常見問題解答(FAQ)串列,
要真正理解ConfigureAwait,我們需要提前一點開始…
什么是SynchronizationContext?
System.Threading.SynchronizationContext 檔案這樣描述SynchronizationContext:它在各種同步模型中提供傳輸同步背景關系的基本功能,這并不是一個顯而易懂的描述,
對于99.9%的情況,SynchronizationContext僅是一種提供虛擬Post方法的型別,該方法需要委托以異步方式執行(還有各在SynchronizationContext上的各種其他虛擬成員,但它們的使用量少得多,因此與本討論無關) ,基本型別的Post字面意義只是異步呼叫 ThreadPool.QueueUserWorkItem以提供的委托,但是,派生型別將覆寫Post以使該委托能夠在最合適的位置和最合適的時間執行,
例如,Windows Forms 具有SynchronizationContext派生的型別,該型別將重寫Post以等同于Control.BeginInvoke;這意味著對它的Post方法的任何呼叫都將導致稍后在與該相關控制元件關聯的執行緒(也稱為“ UI執行緒”)上呼叫委托,Windows Forms依賴Win32訊息處理,并且在UI執行緒上運行“訊息回圈”,該執行緒只是等待新訊息到達以進行處理,這些訊息可能用于滑鼠移動和單擊,用于鍵盤鍵入,用于系統事件,可供可呼叫的委托等,因此,給定SynchronizationContextWindows Forms應用程式的UI執行緒的實體,以使委托在其上執行UI執行緒,只需要將其傳遞給Post,
Windows Presentation Foundation(WPF)也是如此,它具有自己的SynchronizationContext派生型別,并帶有Post覆寫,該覆寫類似地(通過Dispatcher.BeginInvoke)“封送” UI執行緒的委托,在這種情況下,由WPF Dispatcher而不是Windows Forms Control管理,
對于Windows運行時(WinRT),它具有自己的SynchronizationContext派生型別并帶有Post重寫,該重寫也通過將該佇列排隊到UI執行緒CoreDispatcher,
這超出了“在UI執行緒上運行此委托”的范圍,任何人都可以SynchronizationContext使用Post做任何事情的實作 ,例如,我可能不在乎委托在哪個執行緒上運行,但是我想確保Post對我的所有委托SynchronizationContext都以一定程度的并發度執行,我可以通過這樣的自定義來實作SynchronizationContext:
internal sealed class MaxConcurrencySynchronizationContext : SynchronizationContext
{
private readonly SemaphoreSlim _semaphore;
public MaxConcurrencySynchronizationContext(int maxConcurrencyLevel) =>
_semaphore = new SemaphoreSlim(maxConcurrencyLevel);
public override void Post(SendOrPostCallback d, object state) =>
_semaphore.WaitAsync().ContinueWith(delegate
{
try { d(state); } finally { _semaphore.Release(); }
}, default, TaskContinuationOptions.None, TaskScheduler.Default);
public override void Send(SendOrPostCallback d, object state)
{
_semaphore.Wait();
try { d(state); } finally { _semaphore.Release(); }
}
}
實際上,單元測驗框架xunit 提供了SynchronizationContext與之非常相似的功能,它用于限制與可以并行運行的測驗相關的代碼量,
所有這些的好處與任何抽象都是一樣的:它提供了一個API,可用于將委托排隊,以處理實作的創建者所希望的,而無需了解該實作的細節,因此,如果我正在撰寫一個庫,并且想開始做一些作業,然后將一個代表排隊回到原始位置的“背景關系”,則只需要抓住它們SynchronizationContext,然后堅持下去,然后我的作業已經完成,請Post在該背景關系上呼叫以移交我要呼叫的委托,我不需要知道對于Windows Forms我應該抓住Control并使用它BeginInvoke,或者對于WPF我應該抓住Dispatcher并使用它BeginInvoke,或者對于xunit我應該以某種方式獲取其背景關系并排隊,我只需要抓住當前SynchronizationContext并在以后使用,為此,SynchronizationContext提供一個Current屬性,以便實作上述目標,我可以撰寫如下代碼:
public void DoWork(Action worker, Action completion)
{
SynchronizationContext sc = SynchronizationContext.Current;
ThreadPool.QueueUserWorkItem(_ =>
{
try { worker(); }
finally { sc.Post(_ => completion(), null); }
});
}
想要從中公開自定義背景關系的框架Current使用此SynchronizationContext.SetSynchronizationContext方法,
什么是TaskScheduler?
SynchronizationContext是“調度程式”的一般抽象,各個框架有時會對調度程式有自己的抽象,System.Threading.Tasks也不例外,當Task由委托支持時,可以將它們排隊并執行,它們與關聯System.Threading.Tasks.TaskScheduler,就像SynchronizationContext提供了一種虛擬Post方法來排隊委托的呼叫(通過稍后的實作通過典型的委托呼叫機制呼叫委托)一樣,TaskScheduler提供了抽象的QueueTask方法(通過實作的稍后Task通過ExecuteTask方法呼叫委托),
回傳的默認調度程式TaskScheduler.Default是執行緒池,但是可以派生TaskScheduler并覆寫相關方法,以實作在何時何地呼叫的Task行為,例如,核心庫包括System.Threading.Tasks.ConcurrentExclusiveSchedulerPair型別,此類的實體公開兩個TaskScheduler屬性,一個稱為ExclusiveScheduler,一個稱為ConcurrentScheduler,安排到的任務ConcurrentScheduler可以同時運行,但是要受其ConcurrentExclusiveSchedulerPair構建時的限制(類似于前面顯示的MaxConcurrencySynchronizationContext),并且ConcurrentScheduler Task在Task計劃到運行時將不運行ExclusiveScheduler,一次只能Task運行一個互斥物件…這樣,它的行為非常類似于讀/寫鎖,
類似SynchronizationContext,TaskScheduler還具有一個Current屬性,該屬性回傳“當前的TaskScheduler,不類似于SynchronizationContext,然而,這沒有設定當前調度程式的方法,相反,當前調度程式是與當前正在運行Task的調度程式相關聯的調度程式,并且調度程式作為啟動的一部分提供給系統Task,因此,舉例來說,使用這個程式將輸出“True”,使用lambda在StartNew上執行ConcurrentExclusiveSchedulerPair的ExclusiveScheduler,將看到TaskScheduler.Current設定為調度程式:
using System;
using System.Threading.Tasks;
class Program
{
static void Main()
{
var cesp = new ConcurrentExclusiveSchedulerPair();
Task.Factory.StartNew(() =>
{
Console.WriteLine(TaskScheduler.Current == cesp.ExclusiveScheduler);
}, default, TaskCreationOptions.None, cesp.ExclusiveScheduler).Wait();
}
}
有趣的是,TaskScheduler提供了一個靜態FromCurrentSynchronizationContext方法,它創建了一個新的TaskScheduler在SynchronizationContext.Current回傳的內容上排隊運行Post,
SynchronizationContext和TaskScheduler與等待如何關聯?
考慮在UI應用使用編Button,在點擊Button,我們要下載從網站一些文字,并將其設定為Button的Content,該Button只應該從擁有它的UI執行緒訪問,所以當我們已經成功地下載了新的日期和時間文本和想把它存回Button的Content,我們需要從擁有控制執行緒這樣做,如果不這樣做,則會出現類似以下的例外:
System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.'
如果我們手動將其寫出,則可以使用SynchronizationContext如前所示的將Content的設定為原始背景關系,例如通過TaskScheduler:
private static readonly HttpClient s_httpClient = new HttpClient();
private void downloadBtn_Click(object sender, RoutedEventArgs e)
{
s_httpClient.GetStringAsync("http://example.com/currenttime").ContinueWith(downloadTask =>
{
downloadBtn.Content = downloadTask.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
或者直接使用SynchronizationContext:
private static readonly HttpClient s_httpClient = new HttpClient();
private void downloadBtn_Click(object sender, RoutedEventArgs e)
{
SynchronizationContext sc = SynchronizationContext.Current;
s_httpClient.GetStringAsync("http://example.com/currenttime").ContinueWith(downloadTask =>
{
sc.Post(delegate
{
downloadBtn.Content = downloadTask.Result;
}, null);
});
}
不過,這兩種方法都明確使用回呼,相反,我們想自然地用 async/await:
private static readonly HttpClient s_httpClient = new HttpClient();
private async void downloadBtn_Click(object sender, RoutedEventArgs e)
{
string text = await s_httpClient.GetStringAsync("http://example.com/currenttime");
downloadBtn.Content = text;
}
這個“正確操作”,成功地設定Content在UI執行緒上,因為就像上面手動實作的版本一樣,await回默認使用SynchronizationContext.Current以及TaskScheduler.Current,當你在C#中await任何東西,編譯器轉換代碼會詢問(通過呼叫GetAwaiter)的“awaitable”(在這種情況下,Task)一個“awaiter”(在這種情況下,TaskAwaiter<string>),該awaiter負責掛接回呼(通常稱為“繼續”),該回呼將在等待的物件完成時回呼到狀態機中,并使用在回呼時捕獲的任何背景關系/調度程式來完成此操作,注冊,雖然不完全是所使用的代碼(使用了其他優化和調整),但實際上是這樣的:
object scheduler = SynchronizationContext.Current;
if (scheduler is null && TaskScheduler.Current != TaskScheduler.Default)
{
scheduler = TaskScheduler.Current;
}
換句話說,它首先檢查是否存在SynchronizationContext集合,如果沒有,則在運行中是否存在非默認值TaskScheduler,如果找到一個,則在準備好呼叫回呼時,它將使用捕獲的調度程式;否則,將使用捕獲的調度程式,否則,它通常只會在完成等待任務的操作中執行回呼,
ConfigureAwait(false)有什么作用?
該ConfigureAwait方法并不特殊:編譯器或運行時不會以任何特殊方式對其進行識別,它只是一個回傳結構(a ConfiguredTaskAwaitable)的方法,該結構包裝了呼叫它的原始任務以及指定的布林值,請記住,它await可以與任何公開正確模式的型別一起使用,通過回傳不同的型別,這意味著當編譯器訪問GetAwaiter方法(模式的一部分)時,它是根據從回傳的型別ConfigureAwait而不是直接從任務回傳的型別來執行此操作的,并且提供了一個掛鉤來更改行為await通過此自定義等候者的行為方式,
具體來說,等待ConfigureAwait(continueOnCapturedContext: false)而不是Task直接回傳回傳的型別最侄訓影響前面顯示的邏輯,以捕獲目標背景關系/計劃程式,它有效地使前面顯示的邏輯更像這樣:
object scheduler = null;
if (continueOnCapturedContext)
{
scheduler = SynchronizationContext.Current;
if (scheduler is null && TaskScheduler.Current != TaskScheduler.Default)
{
scheduler = TaskScheduler.Current;
}
}
換句話說,通過指定false,即使有當前背景關系或調度程式要回呼,它也會假裝沒有,
我為什么要使用ConfigureAwait(false)?
ConfigureAwait(continueOnCapturedContext: false)用于避免強制在原始背景關系或調度程式上呼叫回呼,這有一些好處:
提高性能,對回呼進行排隊而不是僅僅呼叫它是有代價的,這不僅是因為涉及額外的作業(通常是額外的分配),而且還因為它意味著我們無法在運行時使用某些優化方法(當我們確切知道回呼將如何呼叫時,我們可以進行更多優化,但是如果將其移交給抽象的任意實作,則有時會受到限制,對于非常熱的路徑,即使檢查當前SynchronizationContext和當前TaskScheduler(這兩者都涉及訪問執行緒靜態資料)的額外成本也可能增加可衡量的開銷,如果后面的代碼await實際上并不需要在原始背景關系中運行,請使用ConfigureAwait(false)可以避免所有這些開銷:它不需要不必要的排隊,它可以利用它可以召集的所有優化方法,并且可以避免不必要的執行緒靜態訪問,
避免死鎖,考慮一種用于await某些網路下載結果的庫方法,呼叫此方法,并同步地阻塞等待它完成,例如通過使用.Wait()或.Result或.GetAwaiter().GetResult()關倍訓傳的Task物件,現在考慮會發生什么,如果你對它的呼叫發生在當前SynchronizationContext是一個限制,可以在其上運行1操作的次數,無論是通過什么樣的明確MaxConcurrencySynchronizationContext這個是一個背景下,只有一個提到的方式,或含蓄可以使用的執行緒,例如UI執行緒,因此,您可以在那個執行緒上呼叫該方法,然后將其阻塞,以等待操作完成,該操作將啟動網路下載并等待它,由于默認情況下等待Task它將捕獲當前SynchronizationContext,當網路下載完成時,它將排隊回傳SynchronizationContext將呼叫該操作其余部分的回呼,但是,當前唯一可以處理排隊回呼的執行緒已被您的代碼阻塞所阻塞,等待操作完成,并且該操作要等到回呼被處理后才能完成,僵局!即使背景關系不將并發限制為1,而是以任何方式限制資源,這也可以適用,想象一下相同的情況,除了使用MaxConcurrencySynchronizationContext限制為4,我們不僅對該操作進行一次呼叫,還對背景關系進行了4次呼叫排隊,每個呼叫都進行了呼叫并阻塞了等待它完成的呼叫,現在,我們在等待異步方法完成時仍然阻塞了所有資源,唯一允許這些異步方法完成的事情是,是否可以通過已經被完全消耗掉的背景關系處理它們的回呼,再次,僵局!如果庫方法已使用ConfigureAwait(false),則它不會將回呼排隊回到原始背景關系,避免出現死鎖情況,
我為什么要使用ConfigureAwait(true)?
不會的,除非您純粹將其用作表明您有意未使用的指示ConfigureAwait(false)(例如,使靜態分析警告等保持沉默),ConfigureAwait(true)沒有任何意義,await task與進行比較時await task.ConfigureAwait(true),它們在功能上是相同的,如果您ConfigureAwait(true)在生產代碼中看到,則可以洗掉它而不會產生不良影響,
該ConfigureAwait方法接受布林值,因為在某些特殊情況下,您需要傳遞變數來控制配置,但是99%的用例具有硬編碼的錯誤引數值ConfigureAwait(false),
什么時候應該使用ConfigureAwait(false)?
這取決于:您是在實作應用程式級代碼還是通用庫代碼?
撰寫應用程式時,通常需要默認行為(這就是為什么它是默認行為),如果應用程式模型/環境(例如Windows表單,WPF,ASP.NET Core等)發布了自定義SynchronizationContext,則幾乎可以肯定有一個很好的理由:它為關心同步背景關系與代碼互動的代碼提供了一種方法,應用模型/環境,所以,如果你在Windows撰寫的事件處理程式表單應用程式,書寫xUnit的單元測驗,在ASP.NET MVC控制器撰寫代碼時,應用模式是否也其實發布SynchronizationContext,您希望使用SynchronizationContext,如果它存在,這意味著默認值/ ConfigureAwait(true),您簡單地使用await,并且正確的事情發生在將回呼/繼續發布回原始背景關系(如果存在)的方面,這導致了以下一般指導:如果您正在撰寫應用程式級代碼,請不要使用ConfigureAwait(false),如果您回想一下本文前面的Click事件處理程式代碼示例:
private static readonly HttpClient s_httpClient = new HttpClient();
private async void downloadBtn_Click(object sender, RoutedEventArgs e)
{
string text = await s_httpClient.GetStringAsync("http://example.com/currenttime");
downloadBtn.Content = text;
}
downloadBtn.Content = text需要在原始背景關系中完成設定,如果代碼違反了該準則,而是ConfigureAwait(false)在不應當遵循的準則下使用:
private static readonly HttpClient s_httpClient = new HttpClient();
private async void downloadBtn_Click(object sender, RoutedEventArgs e)
{
string text = await s_httpClient.GetStringAsync("http://example.com/currenttime").ConfigureAwait(false); // bug
downloadBtn.Content = text;
}
會導致不良行為,依賴于經典ASP.NET應用程式中的代碼也是如此HttpContext.Current;使用ConfigureAwait(false)然后嘗試使用HttpContext.Current可能會導致問題,
相反,通用庫是“通用的”,部分原因是它們不關心使用它們的環境,您可以從Web應用程式,客戶端應用程式或測驗中使用它們,這無關緊要,因為庫代碼對于可能使用的應用程式模型是不可知的,不可知論則也意味著它不會做某種需要以特定方式與應用程式模型互動的事情,例如,它將不會訪問UI控制元件,因為通用庫對UI控制元件一無所知,由于我們不需要在任何特定環境中運行代碼,因此可以避免將繼續/回呼強制回到原始背景關系,而我們可以通過使用ConfigureAwait(false)并獲得其帶來的性能和可靠性優勢來做到這一點,這導致以下方面的一般指導:如果您要撰寫通用庫代碼,請使用ConfigureAwait(false),例如,這就是為什么您會看到await在.NET Core運行時庫中的每個(或幾乎每個)都在ConfigureAwait(false)every上使用的原因await,除少數例外,如果不是這樣,很可能會修復一個錯誤,例如,此PR修復了中的丟失ConfigureAwait(false)呼叫HttpClient,
當然,與所有指南一樣,在沒有意義的地方也可能會有例外,例如,通用庫中較大的豁免項(或至少需要考慮的類別)之一是當這些庫具有可呼叫委托的API時,在這種情況下,庫的呼叫者正在傳遞可能由庫呼叫的應用程式級代碼,然后有效地呈現了庫模擬的那些“通用”假設,例如,考慮LINQ的Where方法的異步版本,例如public static async IAsyncEnumerable<T> WhereAsync(this IAsyncEnumerable<T> source, Func<T, bool> predicate),是否predicate需要在呼叫SynchronizationContext方的原始位置上重新呼叫?這取決于WhereAsync決定的實作,這是它可能選擇不使用的原因ConfigureAwait(false),
即使有這些特殊情況,通用指南仍然是一個很好的起點:ConfigureAwait(false)如果要撰寫通用庫/與應用程式模型無關的代碼,請使用此指南,否則請不要使用,
ConfigureAwait(false)是否保證回呼不會在原始背景關系中運行?
不,它保證它不會被排隊回到原始背景關系中……但這并不意味著await task.ConfigureAwait(false)之后的代碼仍無法在原始背景關系中運行,那是因為等待已經完成的等待物件只是保持await同步運行,而不是強迫任何東西排隊,因此,如果您await的任務在等待時已經完成,無論您是否使用過ConfigureAwait(false),緊隨其后的代碼將在當前背景關系中繼續在當前執行緒上執行,
在我的方法中僅在第一次等待時使用ConfigureAwait(false)可以嗎?
一般來說,沒有,請參閱前面的常見問題解答,如果await task.ConfigureAwait(false)涉及到的任務在等待時已經完成(這實際上是很常見的),則這ConfigureAwait(false)將毫無意義,因為執行緒在此之后繼續在該方法中執行代碼,并且仍在與之前相同的背景關系中執行,
一個值得注意的例外是,如果您知道第一個await總是將異步完成,并且正在等待的事物將在沒有自定義SynchronizationContext或TaskScheduler的環境中呼叫其回呼,例如,CryptoStream在.NET運行時庫中,要確保其潛在的計算密集型代碼不會作為呼叫方的同步呼叫的一部分運行,因此它使用自定義的等待程式來確保第await一個之后的所有內容都在執行緒池執行緒上運行,但是,即使在那種情況下,您也會注意到next await仍然使用ConfigureAwait(false); 從技術上講這不是必需的,但是它使代碼檢查變得容易得多,因為否則每次查看此代碼時都不需要進行分析以了解原因ConfigureAwait(false) 被遺棄了,
我可以使用Task.Run來避免使用ConfigureAwait(false)嗎?
是,如果您寫:
Task.Run(async delegate
{
await SomethingAsync(); // won't see the original context
});
則ConfigureAwait(false)該SomethingAsync()呼叫將是nop,因為傳遞給的委托Task.Run將在執行緒池執行緒上執行,而堆疊上沒有更高的用戶代碼,因此SynchronizationContext.Current將回傳null,此外,Task.Run隱式使用TaskScheduler.Default,這意味著TaskScheduler.Current在委托內部進行查詢也將回傳Default,這意味著await無論是否ConfigureAwait(false)使用,都表現出相同的行為,它還不能保證此lambda內的代碼可以做什么,如果您有代碼:
Task.Run(async delegate
{
SynchronizationContext.SetSynchronizationContext(new SomeCoolSyncCtx());
await SomethingAsync(); // will target SomeCoolSyncCtx
});
那么里面的代碼SomethingAsync實際上將SynchronizationContext.Current視為該SomeCoolSyncCtx實體,并且此內部await以及所有未配置的waits SomethingAsync都將發回到該實體,因此,使用這種方法時,您需要了解排隊的所有代碼可能做什么或可能不做什么,以及它的行為是否會阻礙您的行為,
這種方法還以需要創建/排隊其他任務物件為代價,這取決于您的性能敏感性,對您的應用程式或庫而言可能無關緊要,
還請記住,這些技巧可能會導致更多問題,超出其應有的價值,并帶來其他意想不到的后果,例如,已經撰寫了靜態分析工具(例如Roslyn分析儀)來標記未使用的標志ConfigureAwait(false),例如CA2007,如果啟用了這樣的分析器,但是為了避免使用ConfigureAwait,使用了這樣的技巧,則分析器很有可能會對其進行標記,這實際上會為您帶來更多的作業,因此,也許您可??能由于其噪音而禁用了分析器,現在您最終錯過了代碼庫中本應使用的其他位置ConfigureAwait(false),
我可以使用SynchronizationContext.SetSynchronizationContext來避免使用ConfigureAwait(false)嗎?
不,也許,這取決于所涉及的代碼,
一些開發人員撰寫如下代碼:
Task t;
SynchronizationContext old = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(null);
try
{
t = CallCodeThatUsesAwaitAsync(); // awaits in here won't see the original context
}
finally { SynchronizationContext.SetSynchronizationContext(old); }
await t; // will still target the original context
希望它可以使內部代碼CallCodeThatUsesAwaitAsync將當前背景關系視為null,而且會的,但是,以上內容不會影響await所見內容TaskScheduler.Current,因此,如果此代碼在某個自定義項上運行TaskScheduler,則await內部CallCodeThatUsesAwaitAsync(且未使用ConfigureAwait(false))的仍將看到并排隊回傳該自定義項TaskScheduler,
所有相同的警告也適用于與上一個Task.Run相關的FAQ中的問題:這種解決方法存在一些性能方面的問題,嘗試中的代碼也可以通過設定不同的背景關系(或使用非默認值呼叫代碼TaskScheduler)來阻止這些嘗試,
使用這種模式,您還需要注意一些細微的變化:
SynchronizationContext old = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(null);
try
{
await t;
}
finally { SynchronizationContext.SetSynchronizationContext(old); }
看到問題了嗎?這很難看,但也很有影響力,無法保證await將最終在原始執行緒上呼叫回呼/繼續,這意味著SynchronizationContext在原始執行緒上可能實際上沒有發生將重置回原始執行緒的事情,這可能導致該執行緒上的后續作業項看到錯誤背景關系(為解決此問題,撰寫自定義背景關系的撰寫良好的應用程式模型通常會添加代碼以在呼叫任何其他用戶代碼之前手動將其重置),而且即使它確實在同一執行緒上運行,也可能要等一會兒才能使背景關系暫時恢復,而且,如果它在其他執行緒上運行,可能最侄訓在該執行緒上設定錯誤的背景關系,等等,非常不理想,
我正在使用GetAwaiter(),GetResult(),我需要使用ConfigureAwait(false)嗎?
不需要,ConfigureAwait僅會影響回呼,具體來說,等待者模式要求等待者公開IsCompleted屬性,GetResult方法和OnCompleted方法(可選地帶有UnsafeOnCompleted方法),ConfigureAwait只會影響的行為{Unsafe}OnCompleted,因此,如果您只是直接呼叫等待者的GetResult()方法,則無論您是在上TaskAwaiter還是在ConfiguredTaskAwaitable.ConfiguredTaskAwaiter行為上使行為差為零,因此,如果您task.ConfigureAwait(false).GetAwaiter().GetResult()在代碼中看到,則可以將其替換為task.GetAwaiter().GetResult()(并且還要考慮是否真的要像這樣進行阻塞),
我知道我在一個永遠不會具有自定義SynchronizationContext或自定義TaskScheduler的環境中運行,我可以跳過使用ConfigureAwait(false)嗎?
也許,這取決于您對“從不”這一部分的信心,正如前面的常見問題解答中提到的那樣,僅因為您正在使用的應用程式模型未設定自定義且未在自定義SynchronizationContext上呼叫代碼TaskScheduler并不意味著其他用戶或庫代碼未設定自定義,因此,您需要確保不是這種情況,或者至少要確定是否存在這種風險,
我聽說.NET Core中不再需要ConfigureAwait(false),真偽?
假,在.NET Core上運行時需要它,其原因與在.NET Framework上運行時完全相同,在這方面沒有任何改變,
但是,改變的是某些環境是否發布自己的環境SynchronizationContext,特別是,雖然.NET Framework上的經典ASP.NET具有自己SynchronizationContext的元素,但ASP.NET Core卻沒有,這意味著默認情況下,在ASP.NET Core應用程式中運行的代碼將看不到 customSynchronizationContext,從而減少了ConfigureAwait(false)在這種環境中運行的需要,
但是,這并不意味著永遠不會有習俗SynchronizationContext或TaskScheduler禮物,如果某些用戶代碼(或您的應用程式正在使用的其他庫代碼)設定了自定義背景關系并呼叫了您的代碼,或者按Task預定的習慣呼叫了您的代碼TaskScheduler,那么即使在ASP.NET Core中,您等待的物件也可能會看到非默認背景關系或會導致您要使用的調度程式ConfigureAwait(false),當然,在這種情況下,如果您避免同步阻塞(無論如何都應避免在Web應用程式中進行阻塞),并且如果您不介意在這種情況下出現小的性能開銷,則可能無需使用即可擺脫困境ConfigureAwait(false),
可以在等待IAsyncEnumerable時使用ConfigureAwait?
是,有關示例,請參見此《 MSDN雜志》文章,
await foreach系結到一個模式,因此盡管它可以用于列舉IAsyncEnumerable<T>,但它也可以用于列舉暴露正確的API表面積的東西,.NET運行時庫上包括一個ConfigureAwait 擴展方法,IAsyncEnumerable<T>該方法回傳一個自定義型別,該自定義型別包裝IAsyncEnumerable<T>和Boolean并公開正確的模式,當編譯器生成對列舉數MoveNextAsync和DisposeAsync方法的呼叫時,這些呼叫是對回傳的已配置列舉數結構型別的呼叫,然后依次以所需的配置方式執行等待,
當“等待使用” IAsyncDisposable時可以使用ConfigureAwait嗎?
是的,盡管有輕微的并發癥,
就像IAsyncEnumerable<T>前面的常見問題解答中所述,.NET運行時庫在上公開了ConfigureAwait擴展方法IAsyncDisposable,并且await using在實作適當的模式(即公開適當的DisposeAsync方法)時將很高興地使用此擴展方法:
await using (var c = new MyAsyncDisposableClass().ConfigureAwait(false))
{
...
}
這里的問題在于,c現在的型別不是MyAsyncDisposableClass,但是相當于System.Runtime.CompilerServices.ConfiguredAsyncDisposable,這是從ConfigureAwait擴展方法on 回傳的型別IAsyncDisposable,
為了解決這個問題,您需要多寫一行:
var c = new MyAsyncDisposableClass();
await using (c.ConfigureAwait(false))
{
...
}
現在,c再次需要型別MyAsyncDisposableClass,這也有增加范圍的作用c; 如果有影響,則可以將整個內容括在大括號中,
我使用了ConfigureAwait(false),但是我的AsyncLocal等待之后仍然流向代碼,那是個錯誤嗎?
不,這是預期的,AsyncLocal<T>資料流作為的一部分ExecutionContext,與分開SynchronizationContext,除非您顯式禁用ExecutionContext了ExecutionContext.SuppressFlow(),否則ExecutionContext(,因此AsyncLocal<T>資料)始終將流經awaits,無論是否ConfigureAwait用于避免捕獲原始SynchronizationContext,有關更多資訊,請參閱此博客文章,
該語言可以幫助我避免在我的庫中顯式使用ConfigureAwait(false)嗎?
類別庫開發人員有時會對需要使用ConfigureAwait(false)而感到沮喪,并要求侵入性較小的替代方案,
當前沒有任何語言,至少沒有內置在語言/編譯器/運行時中,但是,對于這樣的解決方案可能有很多建議,例如https://github.com/dotnet/csharplang/issues/645、https://github.com/dotnet/csharplang/issues/2542、https:/ /github.com/dotnet/csharplang/issues/2649和https://github.com/dotnet/csharplang/issues/2746,
如果這對您很重要,或者您覺得這里有新的有趣的想法,我鼓勵您為這些或新的討論貢獻自己的想法,
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/80533.html
標籤:C#
上一篇:關鍵字Lock的簡單小例子
