在“等待”之后,如何在上一个线程上下文中恢复任务?

时间:2019-04-18 12:38:34

标签: c# async-await task-parallel-library

在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线程。

如何做到这一点?

1 个答案:

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