VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > Python基础教程 >
  • # ConfigureAwait常见问题解答(2)

将毫无意义,因为线程在此之后继续在该方法中执行代码,并且仍在与之前相同的上下文中执行。

一个值得注意的例外是,如果您知道第一个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。

如果这对您很重要,或者您觉得这里有新的有趣的想法,我鼓励您为这些或新的讨论贡献自己的想法。


相关教程