I've discovered that TaskCompletionSource.SetResult();
invokes the code awaiting the task before returning. In my case that result in a deadlock.
This is a simplified version that is started in an ordinary 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();
The Wait is actually nested inside non-async calls.
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; }
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?
The application is a console application.
I've discovered that TaskCompletionSource.SetResult(); invokes the code awaiting the task before returning. In my case that result in a deadlock.
Yes, I have a blog post documenting this (AFAIK it's not documented on MSDN). The deadlock happens because of two things:
async
and blocking code (i.e., an async
method is calling Wait
).TaskContinuationOptions.ExecuteSynchronously
.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();
Instead, use await
consistently:
await SendAwaitResponse("first message"); await SendAwaitResponse("second message");
If you need to, you can Wait
at an alternative point further up the call stack (not in an async
method).
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.
But those solutions are much more complex than just separating the async
and blocking code.
As a side note, take a look at TPL Dataflow; it sounds like you may find it useful.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With