如何判断TaskCompletionSource.TrySetResult
启动的延续是同步执行还是异步执行?
例如:
// 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();
}
如果在具有与发生NotificationEvent
的同步上下文不同的同步上下文的线程上触发await tcs.Task
,则调试输出将为:
B.before A.before A.after B.after
也就是说,await tcs.Task
延续是异步执行的。如果在同一个同步上下文中触发(或者两个地方都没有同步上下文),则输出为:
B.before A.before B.after A.after
也就是说,继续是同步执行的。
有没有办法在RegisterNotification
内预测此订单?
我可以将SynchronizationContext.Current
保存在RegisterNotification
内,稍后在我致电tcs.TrySetResult
时进行比较。但这并不一定意味着await tcs.Task
将在我保存的上下文中发生。
理论上,如果我可以预测到这一点,我或许可以用它来诊断和防止潜在的死锁。
答案 0 :(得分:4)
我认为没有一种文档化的方法可以提前预测SetResult
的同步/异步行为。如果你想明确强加异步延续,那么@Damien_The_Unbeliever提出的Task.Run(() => tcs.SetResult())
想法是简单而通用的。
但是,如果确实希望减少线程切换并仍然强制异步,则可以使用自定义哑tcs.SetResult
包裹SynchronizationContext
。与await tcs.Task
的上下文(以及可能在tcs.Task
上注册的任何其他延续的上下文)相比,它的唯一目的是它的唯一性。这将导致TaskCompletionSource
的消费者端的异步延续,通过SynchronizationContext.Post
或池线程(如果消费者端没有同步上下文)。
测试应用:
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");
}
}
}
}
输出:
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