原文: https://devblogs.microsoft.com/dotnet/configureawait-faq/git
.NET 在七多年前在语言和类库添加了 async/await
。在那个时候,它像野火同样流行,不只遍布.NET生态系统,并且还能够以多种其余语言和框架进行复制。在利用异步的其余语言构造,提供异步支持的API以及进行async/ await相关的基础架构方面的基本改进方面,.NET也实现了不少改进(特别是.NET Core的性能和支持诊断的改进) 。github
可是,async/ await
依旧引发疑问的一个方面是ConfigureAwait
在这篇文章中,我但愿回答其中的许多问题。我但愿这篇文章从头至尾都是可读的,而且是能够用做未来参考的常见问题解答(FAQ)列表。api
要真正理解ConfigureAwait
,咱们须要提早一点开始…网络
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线程上运行“消息循环”,该线程只是等待新消息到达以进行处理。这些消息可能用于鼠标移动和单击,用于键盘键入,用于系统事件,可供可调用的委托等。所以,给定SynchronizationContext
Windows Forms应用程序的UI线程的实例,以使委托在其上执行UI线程,只须要将其传递给Post
。框架
Windows Presentation Foundation(WPF)也是如此。它具备本身的SynchronizationContext派生类型,并带有Post
覆盖,该覆盖相似地(经过Dispatcher.BeginInvoke
)“封送” UI线程的委托,在这种状况下,由WPF Dispatcher而不是Windows Forms Control管理。异步
对于Windows运行时(WinRT)。它具备本身的SynchronizationContext派生类型并带有Post
重写,该重写也经过将该队列排队到UI线程CoreDispatcher
。async
这超出了“在UI线程上运行此委托”的范围。任何人均可以SynchronizationContext
使用Post
作任何事情的实现 。例如,我可能不在意委托在哪一个线程上运行,可是我想确保Post
对个人全部委托SynchronizationContext
都以必定程度的并发度执行。我能够经过这样的自定义来实现SynchronizationContext
:ide
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
方法。
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
。
考虑在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
方法并不特殊:编译器或运行时不会以任何特殊方式对其进行识别。它只是一个返回结构(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(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(false)
(例如,使静态分析警告等保持沉默)。ConfigureAwait(true)
没有任何意义。await task
与进行比较时await task.ConfigureAwait(true)
,它们在功能上是相同的。若是您ConfigureAwait(true)
在生产代码中看到,则能够删除它而不会产生不良影响。
该ConfigureAwait
方法接受布尔值,由于在某些特殊状况下,您须要传递变量来控制配置。可是99%的用例具备硬编码的错误参数值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)
若是要编写通用库/与应用程序模型无关的代码,请使用此指南,不然请不要使用。
不。它保证它不会被排队回到原始上下文中……但这并不意味着await task.ConfigureAwait(false)
以后的代码仍没法在原始上下文中运行。那是由于等待已经完成的等待对象只是保持await
同步运行,而不是强迫任何东西排队。所以,若是您await
的任务在等待时已经完成,不管您是否使用过ConfigureAwait(false)
,紧随其后的代码将在当前上下文中继续在当前线程上执行。
通常来讲,没有。请参阅前面的常见问题解答。若是await task.ConfigureAwait(false)
涉及到的任务在等待时已经完成(这其实是很常见的),则这ConfigureAwait(false)
将毫无心义,由于线程在此以后继续在该方法中执行代码,而且仍在与以前相同的上下文中执行。
一个值得注意的例外是,若是您知道第一个await
老是将异步完成,而且正在等待的事物将在没有自定义SynchronizationContext或TaskScheduler的环境中调用其回调。例如,CryptoStream
在.NET运行时库中,要确保其潜在的计算密集型代码不会做为调用方的同步调用的一部分运行,所以它使用自定义的等待程序来确保第await
一个以后的全部内容都在线程池线程上运行。可是,即便在那种状况下,您也会注意到next await仍然使用ConfigureAwait(false)
; 从技术上讲这不是必需的,可是它使代码检查变得容易得多,由于不然每次查看此代码时都不须要进行分析以了解缘由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)
。
不,也许。这取决于所涉及的代码。
一些开发人员编写以下代码:
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
在原始线程上可能实际上没有发生将重置回原始线程的事情,这可能致使该线程上的后续工做项看到错误上下文(为解决此问题,编写自定义上下文的编写良好的应用程序模型一般会添加代码以在调用任何其余用户代码以前手动将其重置)。并且即便它确实在同一线程上运行,也可能要等一下子才能使上下文暂时恢复。并且,若是它在其余线程上运行,可能最终会在该线程上设置错误的上下文。等等。很是不理想。
不须要,ConfigureAwait
仅会影响回调。具体来讲,等待者模式要求等待者公开IsCompleted属性,GetResult
方法和OnCompleted
方法(可选地带有UnsafeOnCompleted
方法)。ConfigureAwait
只会影响的行为{Unsafe}OnCompleted
,所以,若是您只是直接调用等待者的GetResult()
方法,则不管您是在上TaskAwaiter仍是在ConfiguredTaskAwaitable.ConfiguredTaskAwaiter
行为上使行为差为零。所以,若是您task.ConfigureAwait(false).GetAwaiter().GetResult()
在代码中看到,则能够将其替换为task.GetAwaiter().GetResult()
(而且还要考虑是否真的要像这样进行阻塞)。
也许。这取决于您对“从不”这一部分的信心。正如前面的常见问题解答中提到的那样,仅由于您正在使用的应用程序模型未设置自定义且未在自定义SynchronizationContext
上调用代码TaskScheduler
并不意味着其余用户或库代码未设置自定义。所以,您须要确保不是这种状况,或者至少要肯定是否存在这种风险。
假。在.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)
。
是。有关示例,请参见此《 MSDN杂志》文章。
await foreach
绑定到一个模式,所以尽管它能够用于枚举IAsyncEnumerable<T>
,但它也能够用于枚举暴露正确的API表面积的东西。.NET运行时库上包括一个ConfigureAwait
扩展方法,IAsyncEnumerable<T>
该方法返回一个自定义类型,该自定义类型包装IAsyncEnumerable<T>
和Boolean并公开正确的模式。当编译器生成对枚举数MoveNextAsync和DisposeAsync方法的调用时,这些调用是对返回的已配置枚举数结构类型的调用,而后依次以所需的配置方式执行等待。
是的,尽管有轻微的并发症。
就像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; 若是有影响,则能够将整个内容括在大括号中。
不,这是预期的。AsyncLocal<T>
数据流做为的一部分ExecutionContext
,与分开SynchronizationContext。除非您显式禁用ExecutionContext
了ExecutionContext.SuppressFlow()
,不然ExecutionContext
(,所以AsyncLocal<T>
数据)始终将流经awaits
,不管是否ConfigureAwait
用于避免捕获原始SynchronizationContext。有关更多信息,请参阅此博客文章。
类库开发人员有时会对须要使用ConfigureAwait(false)
而感到沮丧,并要求侵入性较小的替代方案。
当前没有任何语言,至少没有内置在语言/编译器/运行时中。可是,对于这样的解决方案可能有不少建议,例如https://github.com/dotnet/csharplang/issues/64五、https://github.com/dotnet/csharplang/issues/254二、https:/ /github.com/dotnet/csharplang/issues/2649和https://github.com/dotnet/csharplang/issues/2746。
若是这对您很重要,或者您以为这里有新的有趣的想法,我鼓励您为这些或新的讨论贡献本身的想法。