如何在 "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 软件工程师),所以您可以相信他在这个主题上所说的一切。

在这里,我修改了他的示例代码来完成此操作。首先推导出自己的SynchronizationContextclass:

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!
}