C# 在完成任务时手动捕获和应用SynchronizationContext

C# 在完成任务时手动捕获和应用SynchronizationContext,c#,multithreading,asynchronous,async-await,C#,Multithreading,Asynchronous,Async Await,我在等待(描述)时遇到了问题。在研究过程中,我发现在我的TaskCompletionSource上调用SetResult实际上是在调用SetResult的线程上下文中调用waiting continuation(这也在一个有点相关的问题中阐述)。在我的例子中,这是一个不同的线程(线程池工作线程),与启动wait的线程(ASP.NET请求线程)不同 虽然我仍然不确定这会导致挂起的原因,但我决定尝试将SetResult强制到原始上下文中。在请求线程上输入wait之前,我存储了Synchronizat

我在等待(描述)时遇到了问题。在研究过程中,我发现在我的
TaskCompletionSource
上调用
SetResult
实际上是在调用
SetResult
的线程上下文中调用waiting continuation(这也在一个有点相关的问题中阐述)。在我的例子中,这是一个不同的线程(线程池工作线程),与启动wait的线程(ASP.NET请求线程)不同

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


我的问题是:这是手动捕获和应用同步上下文的合理且正确的方法吗?FWIW,我尝试先用
SetResult
委托执行一个简单的
Post()
,但仍然导致挂起。我显然有点超出了我的舒适区。。。请帮我了解发生了什么事

SetResult
不保证调用任何内容。因此,这是不可靠的

您需要在捕获同步上下文的位置切换它。这里的一个常见问题是
WebClient
,它在启动web请求时捕获上下文。因此,您的代码如下所示:

SetContext(newContext);
new WebClient().DownloadAsync(...);
SetContext(oldContext);
恢复旧上下文以不干扰任何内容


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

的代码。令我尴尬的是,我完全忽略了我的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
消除了挂起。对不起,浪费了大家的时间!:(

关于恢复上下文的好观点!关于SetResult的保证(或缺少)…请您展开一点,好吗?观察到的效果是,手动应用的上下文从工作线程流回延续线程,因此它必须由SetResult AFAIU完成。如果这仅仅是巧合,正如您所暗示的,我很想知道更多关于细节的细节!TPL有一些代码内联的地方为了提高效率。这太糟糕了。请参阅。如果您修改线程本地状态并拥有以下调用链:A=>SetResult=>B,则B将看到该状态。如果该链是A=>SetResult=>QueueToThreadPool(B)然后B将看到干净的状态。这是否解释了您看到的情况?=我不能肯定地说任何一种方式…我的链实际上是A=>QueueToThreadPool=>SetResult=>B,我现在确定SetResult不会直接调用continuation,因为调用成功并且工作线程没有阻塞。唯一的事情(对我来说是可见的)阻塞是等待A。将RunContinuationsAsynchronously传递给TaskCompletionSource似乎没有任何效果,与调用Post/Send一样。您之前提到的问题是在继续中…如果从未执行,可能会出现什么样的问题(阻塞发生在等待行上)?我开始感到困惑了。你能发布可执行代码来演示这个问题吗?或者至少能接近这个问题?是的,我最初问题的另一个回答者已经提出了这一点…我会尽快找出相关部分。请继续关注!