如何在 "await" 之后恢复前一个线程上下文中的任务?
How can I resume task in the previous thread context after an "await"?
在 Winforms/WPF 中,以下代码有效:
var id = Thread.CurrentThread.ManagedThreadId;
await DoAsync();
var @equals = id == Thread.CurrentThread.ManagedThreadId; //TRUE
我知道 await DoAsync().ConfigureAwait(false)
将在另一个线程中恢复 。
但是,这种 WinForms/WPF 行为如何在控制台应用程序中完成?在控制台应用程序中,以上条件将 return FALSE,无论我是否使用 ConfigureAwait(true/false)
。 我的应用不是控制台,只是行为相同。
我有几个 classes 使用方法 Task<IInterface> MyMethod()
实现 IMyInterface
并且在我的起点中我需要从 STA 线程开始,所以我创建了一个像这样的 STA 线程
public static Task<TResult> Start<TResult>(Func<TResult> action, ApartmentState state, CancellationToken cancellation)
{
var completion = new TaskCompletionSource<TResult>();
var thread = new Thread(() =>
{
try
{
completion.SetResult(action());
}
catch (Exception ex)
{
completion.SetException(ex);
}
});
thread.IsBackground = true;
thread.SetApartmentState(state);
if (cancellation.IsCancellationRequested)
completion.SetCanceled();
else
thread.Start();
return completion.Task;
}
所以我必须确保在每个实现 IMyInterface
的 class 中它恢复到开始时创建的 STA 线程。
如何才能做到这一点?
正如我在上面的评论中提到的,this article 是回答这个问题的重要资源。作者 Stephen Toub 是这方面的主要专家之一(事实上,他是 Microsoft 的 .NET 软件工程师),所以您可以相信他在这个主题上所说的一切。
在这里,我修改了他的示例代码来完成此操作。首先推导出自己的SynchronizationContext
class:
private sealed class SingleThreadSynchronizationContext : SynchronizationContext
{
private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> _queue =
new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
public override void Post(SendOrPostCallback d, object state)
=> _queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
public void RunOnCurrentThread()
{
KeyValuePair<SendOrPostCallback, object> workItem;
while (_queue.TryTake(out workItem, Timeout.Infinite))
workItem.Key(workItem.Value);
}
public void Complete() => _queue.CompleteAdding();
}
然后创建一个专门的消息泵class:
public class AsyncPump
{
public static void Run(Func<Task> func)
{
var prevCtx = SynchronizationContext.Current;
try
{
var syncCtx = new SingleThreadSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(syncCtx);
var t = func();
t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);
syncCtx.RunOnCurrentThread();
t.GetAwaiter().GetResult();
}
finally
{ SynchronizationContext.SetSynchronizationContext(prevCtx); }
}
}
那么你可以这样使用它:
[STAThread]
private static void Main(string[] args)
{
AsyncPump.Run(async () =>
{
await Task.Delay(2000);
});
// We're still on the Main thread!
}
在 Winforms/WPF 中,以下代码有效:
var id = Thread.CurrentThread.ManagedThreadId;
await DoAsync();
var @equals = id == Thread.CurrentThread.ManagedThreadId; //TRUE
我知道 await DoAsync().ConfigureAwait(false)
将在另一个线程中恢复 。
但是,这种 WinForms/WPF 行为如何在控制台应用程序中完成?在控制台应用程序中,以上条件将 return FALSE,无论我是否使用 ConfigureAwait(true/false)
。 我的应用不是控制台,只是行为相同。
我有几个 classes 使用方法 Task<IInterface> MyMethod()
实现 IMyInterface
并且在我的起点中我需要从 STA 线程开始,所以我创建了一个像这样的 STA 线程
public static Task<TResult> Start<TResult>(Func<TResult> action, ApartmentState state, CancellationToken cancellation)
{
var completion = new TaskCompletionSource<TResult>();
var thread = new Thread(() =>
{
try
{
completion.SetResult(action());
}
catch (Exception ex)
{
completion.SetException(ex);
}
});
thread.IsBackground = true;
thread.SetApartmentState(state);
if (cancellation.IsCancellationRequested)
completion.SetCanceled();
else
thread.Start();
return completion.Task;
}
所以我必须确保在每个实现 IMyInterface
的 class 中它恢复到开始时创建的 STA 线程。
如何才能做到这一点?
正如我在上面的评论中提到的,this article 是回答这个问题的重要资源。作者 Stephen Toub 是这方面的主要专家之一(事实上,他是 Microsoft 的 .NET 软件工程师),所以您可以相信他在这个主题上所说的一切。
在这里,我修改了他的示例代码来完成此操作。首先推导出自己的SynchronizationContext
class:
private sealed class SingleThreadSynchronizationContext : SynchronizationContext
{
private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> _queue =
new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
public override void Post(SendOrPostCallback d, object state)
=> _queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
public void RunOnCurrentThread()
{
KeyValuePair<SendOrPostCallback, object> workItem;
while (_queue.TryTake(out workItem, Timeout.Infinite))
workItem.Key(workItem.Value);
}
public void Complete() => _queue.CompleteAdding();
}
然后创建一个专门的消息泵class:
public class AsyncPump
{
public static void Run(Func<Task> func)
{
var prevCtx = SynchronizationContext.Current;
try
{
var syncCtx = new SingleThreadSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(syncCtx);
var t = func();
t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);
syncCtx.RunOnCurrentThread();
t.GetAwaiter().GetResult();
}
finally
{ SynchronizationContext.SetSynchronizationContext(prevCtx); }
}
}
那么你可以这样使用它:
[STAThread]
private static void Main(string[] args)
{
AsyncPump.Run(async () =>
{
await Task.Delay(2000);
});
// We're still on the Main thread!
}