在Winforms / WPF中,以下代码有效:
var id = Thread.CurrentThread.ManagedThreadId;
await DoAsync();
var @equals = id == Thread.CurrentThread.ManagedThreadId; //TRUE
我知道await DoAsync().ConfigureAwait(false)
将在另一个线程中恢复。
但是,如何在控制台应用程序中完成WinForms / WPF行为?在控制台应用程序中,无论我是否使用ConfigureAwait(true/false)
,以上情况都会返回 FALSE 。 我的应用不是控制台,只是行为相同。
我有几个使用方法IMyInterface
实现Task<IInterface> MyMethod()
的类,并且从起点开始,我需要在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
的类中,它都恢复到开头创建的STA线程。
如何做到这一点?
答案 0 :(得分:1)
正如我在上面的评论中提到的,this article是回答此问题的好资源。作者Stephen Toub是该技术的领先专家之一(实际上,他是Microsoft .NET上的一名软件工程师),因此您可以相信他在该主题上所说的一切。
在这里,我调整了他的示例代码来完成此操作。首先,派生自己的SynchronizationContext
类:
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();
}
然后创建一个专门的消息泵类:
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!
}