【问题标题】:Manually capturing and applying SynchronizationContext when completing a Task完成任务时手动捕获和应用 SynchronizationContext
【发布时间】:2016-12-07 07:36:17
【问题描述】:

我遇到了挂起等待的问题(描述为here)。在研究期间,我发现在我的TaskCompletionSource 上调用SetResult 实际上会在调用SetResult 的线程的上下文中调用等待继续(这在this answer 中也有一些相关的问题)。在我的例子中,这是一个与启动 await 的线程(一个 ASP.NET 请求线程)不同的线程(一个线程池工作线程)。

虽然我仍然不确定为什么这会导致挂起,但我决定尝试将 SetResult 强制转换为原始上下文。我在请求线程上进入等待之前存储了SynchronizationContext.Current 的值,并在调用SetResult 之前通过SynchronizationContext.SetSynchronizationContext 在工作线程中手动应用它。这解决了问题,我现在可以等待所有异步方法,而无需指定 ConfigureAwait(false)

我的问题是:这是手动捕获和应用SynchronizationContext 的合理且正确的方法吗? FWIW,我尝试先用SetResult 代表做一个简单的Post(),但这仍然导致挂起。我显然有点脱离了我的舒适区......请帮助我了解发生了什么!

【问题讨论】:

    标签: c# multithreading asynchronous async-await


    【解决方案1】:

    SetResult 不保证会调用任何东西。因此,这是不可靠的。

    您需要在捕获同步上下文时切换它。这里的一个常见痛点是WebClient,它在启动 Web 请求时捕获上下文。所以你的代码看起来像这样:

    SetContext(newContext);
    new WebClient().DownloadAsync(...);
    SetContext(oldContext);
    

    恢复旧的上下文不打扰任何东西。

    换句话说,问题在于延续代码,而不是调用SetResult的代码。

    【讨论】:

    • 关于恢复上下文的好点!关于 SetResult 的保证(或缺乏)......你能扩展一下吗? 观察到的效果是手动应用的上下文从工作线程流回延续就好了,所以必须由SetResult,AFAIU来完成。如果这只是巧合,正如你所暗示的那样,我很想知道更多关于坚韧不拔的细节!
    • TPL 有几个地方内联代码以提高效率。这真糟糕。见github.com/dotnet/corefx/issues/2454。如果您修改线程本地状态并具有此调用链: A=>SetResult=>B 那么 B 将看到该状态。如果链是 A=>SetResult=>QueueToThreadPool(B) 那么 B 将看到干净的状态。这是否解释了您所看到的?=
    • 我不能肯定地说...我的链实际上是 A=>QueueToThreadPool=>SetResult=>B 并且我现在确定 SetResult 不会直接调用延续因为调用成功并且工作线程没有阻塞。唯一(对我可见)阻塞的是等待 A。将 RunContinuationsAsynchronously 传递给 TaskCompletionSource 似乎没有任何效果,与调用 Post/Send 相同。您之前提到问题仍在继续中......如果它甚至从未执行过(该块发生在等待行上),可能会出现什么样的问题?
    • 我开始感到困惑。您可以发布演示该问题的可执行代码吗?或者至少接近了?
    • 是的,我最初的问题的另一位受访者已经提出了这一点......我会尽快找到相关部分。请继续关注!
    【解决方案2】:

    令我尴尬的是,我完全忽略了我的 HTTP 处理程序是从一个小的基类派生的,它以一种非常可疑的方式实现了 IAsyncHttpHandler,以便添加对异步处理程序的支持:

    public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
    {
        ...
        var task = HandleRequestAsync(...);
    
        Task.Run(async () => { await task; }).GetAwaiter().GetResult();
        ...
    }
    

    我什至不记得我最初为什么这样做(那是一年多以前),但这绝对是我过去几天一直在寻找的愚蠢部分!

    将处理程序基类更改为 .NET 4.6 的 HttpTaskAsyncHandler 摆脱了挂起。很抱歉浪费了大家的时间! :(

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-04-09
      • 2021-09-30
      • 1970-01-01
      • 1970-01-01
      • 2019-10-25
      • 1970-01-01
      • 2011-06-29
      相关资源
      最近更新 更多