以非阻塞方式调用 TaskCompletionSource.SetResult [英] Calling TaskCompletionSource.SetResult in a non blocking manner
问题描述
我发现 TaskCompletionSource.SetResult();
在返回之前调用等待任务的代码.就我而言,这会导致死锁.
I've discovered that TaskCompletionSource.SetResult();
invokes the code awaiting the task before returning. In my case that result in a deadlock.
这是在普通Thread
void ReceiverRun()
while (true)
{
var msg = ReadNextMessage();
TaskCompletionSource<Response> task = requests[msg.RequestID];
if(msg.Error == null)
task.SetResult(msg);
else
task.SetException(new Exception(msg.Error));
}
}
代码的异步"部分看起来像这样.
The "async" part of the code looks something like this.
await SendAwaitResponse("first message");
SendAwaitResponse("second message").Wait();
Wait 实际上嵌套在非异步调用中.
The Wait is actually nested inside non-async calls.
SendAwaitResponse(简化)
The SendAwaitResponse(simplified)
public static Task<Response> SendAwaitResponse(string msg)
{
var t = new TaskCompletionSource<Response>();
requests.Add(GetID(msg), t);
stream.Write(msg);
return t.Task;
}
我的假设是第二个 SendAwaitResponse 将在 ThreadPool 线程中执行,但它会在为 ReceiverRun 创建的线程中继续执行.
My assumption was that the second SendAwaitResponse would execute in a ThreadPool thread but it continues in the thread created for ReceiverRun.
有没有办法在不继续等待的代码的情况下设置任务的结果?
Is there anyway to set the result of a task without continuing its awaited code?
该应用程序是一个控制台应用程序.
推荐答案
我发现 TaskCompletionSource.SetResult();在返回之前调用等待任务的代码.就我而言,这会导致死锁.
I've discovered that TaskCompletionSource.SetResult(); invokes the code awaiting the task before returning. In my case that result in a deadlock.
是的,我有一篇博文 对此进行记录(AFAIK 未在 MSDN 上记录).发生僵局的原因有两个:
Yes, I have a blog post documenting this (AFAIK it's not documented on MSDN). The deadlock happens because of two things:
async
和阻塞代码的混合(即,async
方法正在调用Wait
).- 使用
TaskContinuationOptions.ExecuteSynchronously
安排任务延续.
- There's a mixture of
async
and blocking code (i.e., anasync
method is callingWait
). - Task continuations are scheduled using
TaskContinuationOptions.ExecuteSynchronously
.
我建议从最简单的解决方案开始:删除第一件事 (1).即,不要混合 async
和 Wait
调用:
I recommend starting with the simplest possible solution: removing the first thing (1). I.e., don't mix async
and Wait
calls:
await SendAwaitResponse("first message");
SendAwaitResponse("second message").Wait();
相反,始终使用 await
:
await SendAwaitResponse("first message");
await SendAwaitResponse("second message");
如果需要,您可以等待
在调用堆栈更上一层的替代点(不是在async
方法中).
If you need to, you can Wait
at an alternative point further up the call stack (not in an async
method).
这是我最推荐的解决方案.但是,如果您想尝试删除第二件事 (2),您可以使用一些技巧:将 SetResult
包装在 Task.Run
中以强制它进入一个单独的线程(我的 AsyncEx library 有 *WithBackgroundContinuations
扩展方法),或者给你的线程一个实际的上下文(比如我的 AsyncContext
类型) 并指定 ConfigureAwait(false)
,这将 导致继续忽略ExecuteSynchronously
标志.
That's my most-recommended solution. However, if you want to try removing the second thing (2), you can do a couple of tricks: either wrap the SetResult
in a Task.Run
to force it onto a separate thread (my AsyncEx library has *WithBackgroundContinuations
extension methods that do exactly this), or give your thread an actual context (such as my AsyncContext
type) and specify ConfigureAwait(false)
, which will cause the continuation to ignore the ExecuteSynchronously
flag.
但是这些解决方案比仅仅分离async
和阻塞代码要复杂得多.
But those solutions are much more complex than just separating the async
and blocking code.
附带说明,请查看 TPL 数据流;听起来你可能会觉得它很有用.
As a side note, take a look at TPL Dataflow; it sounds like you may find it useful.
这篇关于以非阻塞方式调用 TaskCompletionSource.SetResult的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!