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