如何在异步方法中实现InvokeRequired UI模式?

时间:2014-11-21 08:25:25

标签: c# winforms async-await c#-5.0 invokerequired

我们假设我有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)
    }
}

2 个答案:

答案 0 :(得分:3)

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

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

但是,要解决具体问题,您可以执行的操作是在WinFormSynchronizationContext初始化时捕获Form

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
}

答案 1 :(得分:1)

您可以使用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();

注意:仅从UI线程调用TaskScheduler.FromCurrentSynchronizationContext非常重要,否则,它会抛出异常,或者你会得到一个TaskScheduler其他SynchronizationContext不会做你需要的。

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