# ConfigureAwait常見問題解答

来源:https://www.cnblogs.com/chasingdreams2017/archive/2020/01/13/12189830.html
-Advertisement-
Play Games

原文: "https://devblogs.microsoft.com/dotnet/configureawait faq/" .NET 在七多年前在語言和類庫添加了 。在那個時候,它像野火一樣流行,不僅遍及.NET生態系統,而且還可以以多種其他語言和框架進行複製。在利用非同步的其他語言構造,提供非同步 ...


原文: 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 TaskTask計划到運行時將不運行ExclusiveScheduler,一次只能Task運行一個互斥對象…這樣,它的行為非常類似於讀/寫鎖。

類似SynchronizationContextTaskScheduler還具有一個Current屬性,該屬性返回“當前的TaskScheduler。不類似於SynchronizationContext,然而,這沒有設置當前調度程式的方法。相反,當前調度程式是與當前正在運行Task的調度程式相關聯的調度程式,並且調度程式作為啟動的一部分提供給系統Task。因此,舉例來說,使用這個程式將輸出“True”,使用lambda在StartNew上執行ConcurrentExclusiveSchedulerPairExclusiveScheduler,將看到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方法,它創建了一個新的TaskSchedulerSynchronizationContext.Current返回的內容上排隊運行Post

SynchronizationContext和TaskScheduler與等待如何關聯?

考慮在UI應用使用編Button。在點擊Button,我們要下載從網站一些文字,並將其設置為ButtonContent。該Button只應該從擁有它的UI線程訪問,所以當我們已經成功地下載了新的日期和時間文本和想把它存回ButtonContent,我們需要從擁有控制線程這樣做。如果不這樣做,則會出現類似以下的異常:

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)在這種環境中運行的需要。

但是,這並不意味著永遠不會有習俗SynchronizationContextTaskScheduler禮物。如果某些用戶代碼(或您的應用程式正在使用的其他庫代碼)設置了自定義上下文並調用了您的代碼,或者按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。除非您顯式禁用ExecutionContextExecutionContext.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。

如果這對您很重要,或者您覺得這裡有新的有趣的想法,我鼓勵您為這些或新的討論貢獻自己的想法。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • #定義嵌套函數 def func1(): print('this is func1') def func2(): print('this is func2')#調用1func1()輸出:this is func1上面沒有輸出this is func2,說明嵌套子函數func2()沒有被調用原因:一個 ...
  • 定時任務我想大家都不預設,現在流行的框架spring就帶了定時任何 我的個人網站(http://www.yzcopen.com)上用戶上傳的文件都是用這套定時任務執行定時清除 第一步:在applicationContext.xml中加增加以下配置代碼 <!--要調度的對象--> <bean id=" ...
  • 單文件上傳前端頁面 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Upload A File</title> </head> <body> <form action="upload.php" method= ...
  • go微服務框架kratos學習筆記六(kratos 服務發現 discovery) [toc] 除了上次的warden直連方式外,kratos有另一個服務發現sdk : "discovery" discovery 可以先簡單理解為一個http服務、 它最簡單的發現過程可能是這樣的: 1、servic ...
  • 處理靜態文件 對於 HTML 頁面中的 css 以及 js 等靜態文件,需要使用使用 net/http 包下的以下 方法來處理 1) StripPrefix 函數 2) FileServer 函數 3) 例如: 會匹配 以 開發的路徑,當瀏覽器請求 頁面中的 s文件時, 首碼會被替換為 ,然後去 目 ...
  • 問題: 在一些特定的情況下,程式中需要用到進位之間的轉化,現在來說說十進位和十六進位的轉化。 其實java進位轉換非常的簡單。 那問什麼還要說這個問題呢? 因為在轉化的時候遇到一個問題。。。 記錄一下,防止再次發生。 一、十六進位轉十進位 1.錯誤的做法 其實對於這種寫法,部分是可以使用的(十六進位 ...
  • 今天在寫gin介面的時候,每次添加或修改個介面都需要重啟項目才能測試,感覺很麻煩。 因為beego有bee工具,bee run啟動項目fsnotify會監控文件變動,經查發現在iris項目中有個插件支持熱重啟。 使用方法如下: # 安裝rizla包 $ go get -u github.com/ka ...
  • 1. 安裝Sentry 2.在settings.py中配置 3.dsn的獲取 4.註意事項 最後,如有問題,請留言。 ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...