本篇文章给大家谈谈c#–即使使用ConfigureAwait,也会在WebAPI死锁中同步调用异步方法(false),以及c#invoke同步的知识点,同时本文还将给你拓展#ConfigureAwai
本篇文章给大家谈谈c# – 即使使用ConfigureAwait,也会在WebAPI死锁中同步调用异步方法(false),以及c# invoke 同步的知识点,同时本文还将给你拓展# ConfigureAwait常见问题解答、.NET/C# 使用async和await关键字调用异步方法、asp.net-mvc-4 – 从ASP.NET MVC Action调用ConfigureAwait、Async,Await和ConfigureAwait的关系等相关知识,希望对各位有所帮助,不要忘了收藏本站喔。
本文目录一览:- c# – 即使使用ConfigureAwait,也会在WebAPI死锁中同步调用异步方法(false)(c# invoke 同步)
- # ConfigureAwait常见问题解答
- .NET/C# 使用async和await关键字调用异步方法
- asp.net-mvc-4 – 从ASP.NET MVC Action调用ConfigureAwait
- Async,Await和ConfigureAwait的关系
c# – 即使使用ConfigureAwait,也会在WebAPI死锁中同步调用异步方法(false)(c# invoke 同步)
private QueryResult ExecuteSync() { var queryResults = ExecuteAsync(); queryResults.Wait(); return queryResults.Result; } private async Task<QueryResult> ExecuteQueryTaskAsync() { var queryTask = new QueryTask(_uri); return await queryTask.ExecuteAsync(_query).ConfigureAwait(false); }
这在我的程序/服务中完美运行.但是在Web API 2控制器中以这种方式使用ExecuteSync会导致它完全冻结并且永远不会返回响应.
我做了一些研究,并相信罪魁祸首在这里提到:
http://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
我绝对不想异步使用该函数.上面的函数是如此核心并且隐藏在4个包装器的深处,这将是我的库类的一个重大改进,冒泡异步方法只是为了支持这个web api调用.
我正在寻找围绕这个奇怪的Web API行为的解决方案/黑客/建议,以允许我同步运行这个异步方法,而不是让它死锁
解决方法
I absolutely do not want to use the function asynchronously.
不过我不得不说.异步代码是最好的解决方案.您正在执行的操作是异步的,并且为其公开同步API最多也是有问题的.
需要时间吗?当然.但是你的代码会更好.
I am looking for work-arounds/hacks/suggestions
我有an entire article on the subject of brownfield async development,在那里我涵盖了所有已知的黑客以及它们的缺点.
在您的特定情况下(从非核心ASP.NET上的WebApi调用,并考虑它可以从Console / Win32Service样式的应用程序工作),我会说Thread Pool Hack应该适合您.它看起来像这样:
private QueryResult ExecuteSync() { return Task.Run(() => ExecuteAsync()).GetAwaiter().GetResult(); }
想法是ExecuteAsync在请求上下文之外的线程池线程上运行.然后阻止请求线程,直到异步工作完成.
# ConfigureAwait常见问题解答
原文: 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线程上运行“消息循环”,该线程只是等待新消息到达以进行处理。这些消息可能用于鼠标移动和单击,用于键盘键入,用于系统事件,可供可调用的委托等。因此,给定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
。
这超出了“在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。
如果这对您很重要,或者您觉得这里有新的有趣的想法,我鼓励您为这些或新的讨论贡献自己的想法。
.NET/C# 使用async和await关键字调用异步方法
出处:http://shiyousan.com/article/2014/06/635389701226393373.html
下面的是使用控制台编写的简单测试代码:
class Program
{ static void Main(string[] args)
{
Console.WriteLine("--------------------------------------------------------------------");
Console.WriteLine("主线程开始执行,时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine("--------------------------------------------------------------------"); /*
* 主线程调用ShowInfoAsync异步方法
* 使用async和await关键字
*/
ShowInfoAsync();
Console.WriteLine(string.Empty); /*同步操作*/
for (int i = 0; i < 3; i++)
{
Console.WriteLine(string.Format("主线程同步循环显示序号:{0},时间:{1}", i.ToString(), DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")));
}
Console.WriteLine("--------------------------------------------------------------------");
Console.WriteLine("主线程执行结束,时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
Console.WriteLine("--------------------------------------------------------------------");
Console.ReadKey();
} /// <summary>
/// 异步显示输出信息
/// </summary>
static async void ShowInfoAsync()
{
//声明要调用的异步方法,获取异步任务操作类 Task
Task<string> getTask = GetDataAsync();
Console.WriteLine(string.Empty);
Console.WriteLine(string.Format("开始执行异步获取数据方法,时间:{0}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"))); /*
* 异步获取数据并显示
* 这里使用 await 关键词启用并等待异步返回结果
* 在声明await关键词的地方线程就会在此等待返回结果
* 只有返回结果才会继续执行下面步骤
*/
string strShowString = await getTask;
Console.WriteLine("***************************************************************");
Console.WriteLine(string.Format("异步获取数据执行结束,获取数据值:{0},时间:{1}", strShowString, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")));
Console.WriteLine("***************************************************************");
Console.WriteLine(string.Empty);
} /// <summary>
/// 异步获取返回的数据
/// </summary>
/// <returns></returns>
static async Task<string> GetDataAsync()
{ //这里也可以使用Task.Run
return await Task.Factory.StartNew(() =>
{
Thread.Sleep(500); string strReturn = string.Empty; for (int i = 0; i < 5; i++)
{
Thread.Sleep(500);
Console.WriteLine(string.Format("异步获取数据,第{0}次循环,时间:{1}", i + 1, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")));
strReturn += i.ToString();
} return strReturn;
});
}
}
最终返回结果如下:
代码中由于使用async定义了ShowInfoAsync方法,所以ShowInfoAsync就变成一个异步调用的方法,需要注意的是声明了async关键字的方法中一定要使用await关键字声明异步操作的返回结果,否则方法将作为同步方法执行,就不是异步执行了。可以看到代码中await声明的对象是Task<string>这个类,Task<TResult> 类表示一个可以返回值的异步操作,所以代码中GetDataAsync也必须是一个异步方法,而且返回值必须是Task类型或者Task<TResult>。
关于使用这两个关键字官方说明如下:
方法签名包含一个 Async 或 async 修饰符
按照约定,异步方法的名称以“Async”后缀结尾。
返回类型为下列类型之一:
如果你的方法有操作数为 TResult 类型的返回语句,则为 Task<TResult>。
如果你的方法没有返回语句或具有没有操作数的返回语句,则为 Task。
如果你编写的是异步事件处理程序,则为 Void(Visual Basic 中为 Sub)。
有关详细信息,请参见本主题后面的“返回类型和参数”(点击此处看官网完整说明)。
方法通常包含至少一个 await 表达式,该表达式标记一个点,在该点上,直到等待的异步操作完成方法才能继续。 同时,将方法挂起,并且控件返回到方法的调用方。 本主题的下一节将解释悬挂点发生的情况。
这里附上官方MSDN的链接:
使用 Async 和 Await 的异步编程(C# 和 Visual Basic)
async(C# 参考)
await(C# 参考)
asp.net-mvc-4 – 从ASP.NET MVC Action调用ConfigureAwait
ASP.NET不关心客户端应用程序的上下文或线程吗?如果是这样,AspNetSynchronizationContext的目的是什么?我不觉得在操作本身放置一个ConfigureAwait.有些东西似乎错了.有人可以解释吗
public async Task<ActionResult> AsyncWithBackendtest() { var result = await BackendCall().ConfigureAwait(false); var server = HttpContext.Server; HttpContext.Cache["hello"] = "world"; return Content(result); }
解决方法
Stephen Toub mentions this in an MSDN article:
Windows Forms isn’t the only environment that provides a
SynchronizationContext-derived class. ASP.NET also provides one,
AspNetSynchronizationContext,though it’s not public and is not meant
for external consumption. Rather,it is used under the covers by
ASP.NET to facilitate the asynchronous pages functionality in ASP.NET
2.0 (for more information,see msdn.microsoft.com/msdnmag/issues/05/10/WickedCode). This
implementation allows ASP.NET to prevent page processing completion
until all outstanding asynchronous invocations have been completed.
Stephen Cleary’s article from last year提供了有关同步上下文的更多细节.
图4特别表明它没有WinForms / WPF的“特定线程”行为,但整个事情是一个很好的阅读.
If multiple operations complete at once for the same application,AspNetSynchronizationContext will ensure that they execute one at a time. They may execute on any thread,but that thread will have the identity and culture of the original page.
Async,Await和ConfigureAwait的关系
【转自】https://mp.weixin.qq.com/s/h10V-FshGoaQUWFPfy-azg
在.NET Framework 4.5中,async / await关键字已添加到该版本中,简化多线程操作,以使异步编程更易于使用。为了最大化利用资源而不挂起UI,你应该尽可能地尝试使用异步编程。虽然async / await让异步编程更简单,但是有一些你可能不知道的细节和注意的地方
新关键字
微软在.NET框架中添加了async和await关键字。但是,使用它们,方法的返回类型应为Task类型。(我们将在稍后讨论例外情况)为了使用await关键字,您必须在方法定义中使用async。如果你在方法定义中放入async,你应该在主体方法的某个地方至少有一处await关键字,如果你缺少他,你通常会收到Visual Studio的一个警告。
以下是代码中的示例:
1 public async Task ExecuteAsync(UpdateCarCommand request, CancellationToken token = default)
2 {
3 using (var context = _contextFactory.Create())
4 {
5 var entity = context.Cars.FirstOrDefault(a => a.Id == request.Id);
6 // Mapping logic
7 await context.SaveChangesAsync(token);
8 }
9 }
如果要从异步方法返回某些内容,可以使用Task的泛型。像以下这样(如果你想返回受影响的行数)
1 public async Task<int> ExecuteAsync(UpdateCarCommand request, CancellationToken token = default)
2 {
3 using (var context = _contextFactory.Create())
4 {
5 var entity = context.Cars.FirstOrDefault(a => a.Id == request.Id);
6 // Mapping logic
7 return await context.SaveChangesAsync(token);
8 }
9 }
async.await给我们带来了什么?
虽然使用这个看起来很简单,但是它有什么帮助呢?最后,所有这些操作都是在等待数据库返回结果时(在本例中)让其他请求使用当前线程。当您向数据库、磁盘、internet等外部源发出可能需要一段时间才能运行的请求时,我们可以使用async/ wait让其他请求使用这个线程。这样,我们就不会有空闲的“worker”(线程)在那里等待完成其他任务。这就像去快餐店一样,在你点完菜之后,其他人不会点任何东西,直到你吃完为止。使用async/ await,其他人可以在你点完菜之后下他们的订单,并且可以同时处理多个订单。
它不能做什么?
这里需要注意的一件事是async/await并不是并行/多核编程。当您使用async/await时,只处理该线程,并让其他线程使用它。代码的作用类似于“同步”,因为您可以在await之后以本方法继续执行代码。因此,如果在一个方法中有四个await,则必须等到每个方法都完成后才能调用下一个方法。因此,您必须使用任务库或任何您喜欢的方法生成新线程,以使它们并行运行。但是,您也可以让每个线程使用async/wait,这样它们就不会阻塞资源了!
ConfigureAwait(false)能做什么呢?
默认情况下,当您使用async/await时,它将在开始请求的原始线程上继续运行(状态机)。但是,如果当前另一个长时间运行的进程已经接管了该线程,那么你就不得不等待它完成。要避免这个问题,可以使用ConfigureAwait的方法和false参数。当你用这个方法的时候,这将告诉Task它可以在任何可用的线程上恢复自己继续运行,而不是等待最初创建它的线程。这将加快响应速度并避免许多死锁。
但是,这里有一点点损失。当您在另一个线程上继续时,线程同步上下文将丢失,因为状态机改变。这里最大的损失是你会失去归属于线程的Culture和Language,其中包含了国家语言时区信息,以及来自原始线程的HttpContext.Current之类的信息,因此,如果您不需要以此来做多语系或操作任何HttpContext类型设置,则可以安全地进行此方法的调用。注意:如果需要language/culture,可以始终在await之前存储当前相关状态值,然后在await新线程之后重新应用它。
以下是ConfigureAwait(false)的示例:
1 public async Task<int> ExecuteAsync(UpdateCarCommand request, CancellationToken token = default)
2 {
3 using (var context = _contextFactory.Create())
4 {
5 var entity = context.Cars.FirstOrDefault(a => a.Id == request.Id);
6 // Mapping logic
7 return await context.SaveChangesAsync(token).CongifureAwait(false);
8 }
9 }
注意事项
同步 -->异步
如果要使用async/await,需要注意一些事情。您可能遇到的最大问题是处理异步方法请求同步方法。如果你开发一个新项目,通常可以将async/await从上到下贯穿于整个方法链中,而不需要做太多工作。但是,如果你在外层是同步的,并且必须调用异步库,那么就会出现一些有隐患的操作。如果一不小心,便会引发大批量的死锁
如果有同步方法调用异步方法,则必须使用ConfigureAwait(false)。如果不这样做,就会立即掉进死锁陷阱。发生的情况是主线程将调用async方法,最终会阻塞这个线程,直到那个async方法完成。然而,一旦异步方法完成,它必须等待原始调用者完成后才能继续。他们都在等待对方完成,而且永远不会。通过在调用中使用configurewait (false), async方法将能够在另一个线程上完成自己操作,而不关心自己的状态机的位置,并通知原始线程它已经完成。进行这个调用的最佳实践如下:
1 [HttpPut]
2 public IActionResult Put([FromBody]UpdateCommand command) =>
3 _responseMediator.ExecuteAsync(command).ConfigureAwait(false).GetAwaiter().GetResult();
.NET Standard与ConfigureAwait(false)
在.NETCore中,微软删除了导致我们在任何地方都需要ConfigureAwait(false)的SynchronizationContext。因此,ASP.NETCore应用程序在技术上不需要任何ConfigureAwait(false)逻辑,因为它是多余的。但是,如果在开发有一个使用.NETStandard的库,那么强烈建议仍然使用.ConfigureAwait(false)。在.NETCore中,这自动是无效的。但是如果有.NETFramework的人最终使用这个库并同步调用它,那么它们将会遇到一堆麻烦。但是随着.NET5是由.NETCore构建的,所以未来大多都是.NetCore调用.Netstadard,你如果不准备让.NetFramework调用你的standard库,大可不必兼容。
ConfigureAwait(false) 贯穿始终
如果同步调用有可能调用您的异步方法,那么在整个调用堆栈的每个异步调用上,您都将被迫设置. configureAwait (false) !如果不这样做,就会导致另一个死锁。这里的问题是,每个async/ await对于调用它的当前方法都是本地的。因此,调用链的每个异async/await都可能最终在不同的线程上恢复。如果一个同步调用一路向下,遇到一个没有configurewait(false)的任务,那么这个任务将尝试等待顶部的原始线程完成,然后才能继续。虽然这最终会让你感到心累,因为要检查所有调用是否设置此属性。
开销
虽然async/ await可以极大地增加应用程序一次处理的请求数量,但是使用它是有代价的。每个async/ await调用最终都将创建一个小状态机来跟踪所有信息。虽然这个开销很小,但是如果滥用async/ await,则会导致速度变慢。只有当线程不得不等待结果时,才应该等待它。
Async Void
虽然几乎所有的async / await方法都应返回某种类型的Task,但此规则有一个例外:有时,您可以使用async void。但是,当您使用它时,调用者实际上不会等待该任务完成后才能恢复自己。它实际上是一种即发即忘的东西。有两种情况你想要使用它。
第一种情况是事件处理程序,如WPF或WinForms中的按钮单击。默认情况下,事件处理程序的定义必须为void。如果你把一个任务放在那里,程序将无法编译,并且返回某些东西的事件会感觉很奇怪。如果该按钮调用异步async,则必须执行async void才能使其正常工作。幸运的是,这是我们想要的,因为这种使用不会阻塞UI。
第二个是请求你不介意等待获得结果的东西。最常见的示例是发送日志邮件,但不想等待它完成或者不关心它是否完成。
然而,对于这两种情况,都有一些缺点。首先,调用方法不能try/catch调用中的任何异常。它最终将进入AppDomain UnhandledException事件。不过,如果在实际的async void方法中放入一个try catch,就可以有效地防止这种情况发生。另一个问题是调用者永远不会知道它何时结束,因为它不返回任何东西。因此,如果你关心什么时候完成某个Task,那么实际上需要返回一个Task。
探讨.NetCore中异步注意事项
在.NetCore中已经剔除了SynchronizationContext,剔除他的主要原因主要是性能和进一步简化操作
在.NetCore中我们不用继续关心异步同步混用情况下,是否哪里没有设置ConfigureAwait(false) 会导致的死锁问题,因为在.netcore中的async/await 可能在任何线程上执行,并且可能并行运行!
以下代码为例:
1 private HttpClient _client = new HttpClient();
2
3 async Task<List<string>> GetBothAsync(string url1, string url2)
4 {
5 var result = new List<string>();
6 var task1 = GetOneAsync(result, url1);
7 var task2 = GetOneAsync(result, url2);
8 await Task.WhenAll(task1, task2);
9 return result;
10 }
11
12 async Task GetOneAsync(List<string> result, string url)
13 {
14 var data = await _client.GetStringAsync(url);
15 result.Add(data);
16 }
它下载两个字符串并将它们放入一个List中。此代码在旧版ASP.NET(.NetFramework)中工作正常,由于请求处设置了await,请求上下文一次只允许一个连接.
其中result.Add(data)
一次只能由一个线程执行,因为它在请求上下文中执行。
但是,这个相同的代码在ASP.NET Core上是不安全的; 具体地说,该result.Add(data)
行可以由两个线程同时执行,而不保护共享List<string>
。
所以在.Netcore中要特别注意异步代码在并行执行情况下引发的问题
参考:https://stackoverflow.com/questions/31186354/async-await-where-is-continuation-of-awaitable-part-of-method-performed
今天关于c# – 即使使用ConfigureAwait,也会在WebAPI死锁中同步调用异步方法(false)和c# invoke 同步的分享就到这里,希望大家有所收获,若想了解更多关于# ConfigureAwait常见问题解答、.NET/C# 使用async和await关键字调用异步方法、asp.net-mvc-4 – 从ASP.NET MVC Action调用ConfigureAwait、Async,Await和ConfigureAwait的关系等相关知识,可以在本站进行查询。
本文标签: