如何在异步方法中实现调用所需的 UI 模式



假设我有一个尝试处理多线程环境的Form;因此,在完成任何UI修改之前,它会检查它是否在UI线程上运行:

partial class SomeForm : Form
{
    public void DoSomethingToUserInterface()
    {
        if (InvokeRequired)
        {
            BeginInvoke(delegate { DoSomethingToUserInterface() });
        }
        else
        {
            … // do the actual work (e.g. manipulate the form or its elements)
        }
    }
}

现在假设我在该方法的部分中执行一些冗长的操作;因此,我想使用 async/await 使其异步。

鉴于我应该更改方法签名以返回Task而不是void(以便可以捕获异常),我将如何实现执行BeginInvoke的部分?它应该返回什么?

public async Task DoSomethingToUserInterfaceAsync()
{
    if (InvokeRequired)
    {
        // what do I put here?
    }
    {
        … // like before (but can now use `await` expressions)
    }
}

async-await与自定义awaiter(如通用TaskAwaiter)一起使用时,将为您隐式捕获SynchronizationContext。稍后,当异步方法完成时,将使用 SynchronizationContext.Post 将延续(await 之后的任何代码)封送回相同的同步上下文。

这完全消除了使用InvokeRequired和其他技术来维护 UI 线程工作的需要。为此,您必须将方法调用一直跟踪到顶级调用,并重构它们以可能使用async-await

但是,要按原样解决特定问题,您可以做的是在Form初始化时捕获WinFormSynchronizationContext

partial class SomeForm : Form
{
    private TaskScheduler _uiTaskScheduler;
    public SomeForm()
    {
        _uiTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
    }
}

稍后当您要await时使用它:

if (InvokeRequired)
{
    Task uiTask = new Task(() => DoSomethingToUserInterface());
    uiTask.RunSynchronously(_uiTaskScheduler);
}
else
{
    // Do async work
}

您可以使用 TaskScheduler.FromCurrentSynchronizationContext 获取当前同步上下文的任务计划程序(对于 UI 线程),并将其存储在字段中供以后使用。

然后,当您有兴趣在 UI 线程中启动任何任务时,您必须将uiScheduler传递给 StartNew 方法,以便 TPL 将在提供的计划程序(在本例中为 UI 线程)中计划任务。

无论如何,您决定在UI线程中运行这些东西,因此只需将其安排到UIScheduler,您无需检查InvokeRequired

public async Task DoSomethingToUserInterfaceAsync()
{
    await Task.Factory.StartNew(() => DoSomethingToUserInterface(), CancellationToken.None, TaskCreationOptions.None, uiScheduler);
    ...
}

若要检索 UI 计划程序,可以使用以下代码

private TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();

注意:TaskScheduler.FromCurrentSynchronizationContext应该只从 UI 线程调用,这一点非常重要,否则,它将引发异常,或者你会得到一些其他SynchronizationContextTaskScheduler,这些不会满足你的需要。

另请注意,如果您已从 UI 线程本身启动异步操作,则不需要上述任何魔法。 await 将在已启动的上下文中恢复。

最新更新