完成任务时手动捕获并应用SynchronizationContext

时间:2016-12-07 07:36:18

标签: c# multithreading asynchronous async-await

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

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

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

2 个答案:

答案 0 :(得分:3)

SetResult无法保证可以拨打任何电话。因此,这不可靠。

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

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

恢复旧的上下文以免打扰任何事情。

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

答案 1 :(得分:0)

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

public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
{
    ...
    var task = HandleRequestAsync(...);

    Task.Run(async () => { await task; }).GetAwaiter().GetResult();
    ...
}

我甚至不记得为什么我这样做了(这是一年多以前),但它肯定是 THE 愚蠢的部分,我一直在寻找最后一对几天!

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