Synchronous or asynchronous continuation upon TaskCompletionSource.TrySetResult?
async-await, asynchronous, c#, multithreading, task-parallel-library
Solution
I don't think there is a documented way to predict synchronous/asynchronous behaviour of `SetResult` in advance. If you want to explicitly impose asynchronous continuation, the `Task.Run(() => tcs.SetResult())` idea proposed by @Damien_The_Unbeliever is simple and universal.
However, if you really would like to reduce thread switching and still force asynchrony, you could wrap `tcs.SetResult` with a custom dumb `SynchronizationContext`. The only purpose of it would be its uniqueness as compared to the context of `await tcs.Task` (and to that of any other continuations possibly registered on `tcs.Task`). That would cause asynchronous continuation(s) on the consumer side of `TaskCompletionSource`, either via `SynchronizationContext.Post` or on a pool thread (if there's no synchronization context on the consumer side).
The test app:
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WinForms_21845495
{
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
this.Load += async (s, e) =>
{
// test on WindowsFormsSynchronizationContext
await RequestNotificationAsync(notifyAsync: false);
Debug.WriteLine(String.Empty);
await RequestNotificationAsync(notifyAsync: true);
Debug.WriteLine(String.Empty);
// test on a pool thread
await Task.Run(() => RequestNotificationAsync(notifyAsync: false));
Debug.WriteLine(String.Empty);
await Task.Run(() => RequestNotificationAsync(notifyAsync: true));
Debug.WriteLine(String.Empty);
};
}
async Task RegisterNotification(TaskCompletionSource<object> tcs, bool notifyAsync)
{
await Task.Delay(500);
Debug.WriteLine("A.before");
if (notifyAsync)
{
tcs.SetResultAsync(null);
}
else
{
tcs.SetResult(null);
}
Debug.WriteLine("A.after");
}
async Task RequestNotificationAsync(bool notifyAsync)
{
var tcs = new TaskCompletionSource<object>();
var task = this.RegisterNotification(tcs, notifyAsync);
Debug.WriteLine("B.before");
var data = await tcs.Task;
// do not yeild
Thread.Sleep(500);
Debug.WriteLine("B.after");
// yeild
await Task.Delay(500);
}
}
public static class TaskExt
{
static public void SetResultAsync<T>(this TaskCompletionSource<T> tcs, T result)
{
FakeSynchronizationContext.Execute(() => tcs.SetResult(result));
}
// FakeSynchronizationContext
class FakeSynchronizationContext : SynchronizationContext
{
private static readonly ThreadLocal<FakeSynchronizationContext> s_context =
new ThreadLocal<FakeSynchronizationContext>(() => new FakeSynchronizationContext());
private FakeSynchronizationContext() { }
public static FakeSynchronizationContext Instance { get { return s_context.Value; } }
public static void Execute(Action action)
{
var savedContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(FakeSynchronizationContext.Instance);
try
{
action();
}
finally
{
SynchronizationContext.SetSynchronizationContext(savedContext);
}
}
// SynchronizationContext methods
public override SynchronizationContext CreateCopy()
{
return this;
}
public override void OperationStarted()
{
throw new NotImplementedException("OperationStarted");
}
public override void OperationCompleted()
{
throw new NotImplementedException("OperationCompleted");
}
public override void Post(SendOrPostCallback d, object state)
{
throw new NotImplementedException("Post");
}
public override void Send(SendOrPostCallback d, object state)
{
throw new NotImplementedException("Send");
}
}
}
}
The output:
B.before
A.before
B.after
A.after
B.before
A.before
A.after
B.after
B.before
A.before
B.after
A.after
B.before
A.before
A.after
B.after
Problem
How to tell if the continuation initiated by `TaskCompletionSource.TrySetResult` is going to be executed synchronously or asynchronously? For example: ``` // class A void RegisterNotification(TaskCompletionSource<object> tcs) { this.source.NotificationEvent += (s, eData) => { Debug.WriteLine("A.before"); tcs.TrySetResult(eData.Result); Debug.WriteLine("A.after"); DoProcessingA(); }; } // class B async Task RequestNotificationAsync() { var tcs = new TaskCompletionSource<object>(); this.a.RegisterNotification(tcs); Debug.WriteLine("B.before"); var data = await tcs.Task; Debug.WriteLine("B.after"); DoProcessingB(); } ``` If `NotificationEvent` is fired on a thread with the different synchronization context from that of where `await tcs.Task` took place, the debug output will be: ``` B.before A.before A.after B.after ``` That is, the `await tcs.Task` continuation is executed asynchronously. If it is fired on the same synchronization context (or if there is no synchronization context in both places), the output will be: ``` B.before A.before B.after A.after ``` That is, the continuation is executed synchronously. Is there a way to predict this order inside `RegisterNotification`? I could save `SynchronizationContext.Current` inside `RegisterNotification` and compare it later when I call `tcs.TrySetResult`. But that not necessarily mean that `await tcs.Task` will take place on the context I saved. In theory, if I could predict this, I might be able to use it to diagnose and prevent potential deadlocks.