为什么要等待Task.Run()不返回同一线程?

时间:2020-05-20 23:40:15

标签: c# async-await

我遇到了一种情况,使我觉得自己不像我想象的那样理解异步/等待机制。

我有一个Windows桌面应用程序,该应用程序主要是WPF,但使用WinForms主机显示第三方COM对象。加载COM对象的过程非常缓慢,因此我一直在尝试将这些对象的创建和初始化移至一个任务,以在发生该工作时释放UI,但是我发现在某些情况下,等待Task.Run()返回,它不在UI线程上。结果,当任务返回后更改WinForms主机上的Visible属性时,由于跨线程调用而引发了该错误。

调用函数如下:

public async Task<bool> LoadPreview(string filePath)
{
    bool result;

    try
    {
        await _semaphore.WaitAsync();
        result = await Task.Run(() => CreateAndInitializePreviewer(filePath));

        if (result)
        {
            Visible = false; // <-- occasionally crashes because I'm not on the UI thread
            _currentHandler.DoPreview();
            Visible = true;
        }
    }
    finally
    {
        _semaphore.Release();
    }

    return result;
}

CreateAndInitializePreviewer中的代码没有任何异步/等待调用。我已经验证了在调用Task.Run()之前,我始终在UI线程上。

关于我应该寻找的任何建议都会导致await Task.Run()返回到另一个线程?任何想法都表示赞赏。

1 个答案:

答案 0 :(得分:4)

加载COM对象的过程非常缓慢,因此我一直在尝试将这些对象的创建和初始化转移到任务中,以在发生工作时释放UI

除非您的COM对象是自由线程的(这不太可能),否则这可能行不通。如果要从UI线程中删除该工作,则可能需要创建一个单独的STA线程来保存COM对象,并封送对该线程的调用/从该线程封送调用-这意味着对该COM的 all 调用对象,因为它将存在于另一个STA线程中。 It's fairly straightforward in WPF to create a second UI (STA) thread;相当困难,但在WinForms中仍然可以实现。

关于我应该寻找的任何建议都会导致等待的Task.Run()返回到另一个线程?

是的。 await will capture SynchronizationContext.Current(如果不是null,并且在这种情况下不应null。它应该是DispatcherSynchronizationContext(通过向WPF调度程序发送一条消息继续执行)的实例,或者是WinFormsSynchronizationContext(通过向WinForms winproc发送一条消息继续执行)的实例。

我最初的猜测是,由于WinForms-in-WPF体系结构,SynchronizationContext.Current发生了一些奇怪的事情。