我们假设我有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)
}
}
答案 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线程本身启动了异步操作,则不需要任何上述魔法。等待将在启动的环境中恢复。