我有一个方法是“部分”异步的,意思是一个代码路径异步运行,另一个同步运行,我现在不能使同步部分异步,虽然我可能在将来可以。
public async Task UpdateSomethingAsync(){
if (ConditionIsMet){
await DoSomethingAsync;
}else{
DoSomethingSynchronous;
}
}
DoSomethingAsync和DoSomethingSynchronous都是I/O绑定的。使用“await”从Winforms UI线程调用此方法会导致它在采用Synchronous路径时阻塞UI线程,这是预期的。
private async void MyDropDownBox_DropDownClosed(object sender, EventArgs e)
{
//This blocks if the DoSomethingSynchronous path is taken, causing UI to
//become unresponsive.
await UpdateSomethingAsync();
}
所以我去了Stephen Cleary的博客。他的建议(虽然是CPU绑定代码而不是I/O绑定)是用Task.Run运行该方法,就好像它是完全同步的一样,同时记录该方法是“部分”异步的。但是,DoSomethingSynchronous引发的事件现在会导致异常,我相信这是因为它们现在处于与UI不同的线程上。
private async void MyDropDownBox_DropDownClosed(object sender, EventArgs e)
{
//This no longer blocks, but events will not marshal back to UI Thread
//causing an exception.
await Task.Run(()=> UpdateSomethingAsync());
}
如何解决此问题?
6条答案
按热度按时间zd287kbt1#
不要更新UI、绑定到
UpdateSomethingAsync
内部UI的任何模型或它调用的任何方法。创建一个class
,它将保存更新UI所需的数据,并从UpdateSomethingAsync
返回该类的示例。DoSomethingAsync
将返回一个Task<ThatClassYouCreated>
,而DoSomethingSynchronous
只返回ThatClassYouCreated
的一个示例。然后,在await
UpdateSomethingAsync
之后,返回MyDropDownBox_DropDownClosed
,使用UpdateSomethingAsync
返回的示例更新UI或模型。jdg4fx2g2#
在事件处理程序中,可以使用
Invoke()
更新UI,如下所示:我不知道是谁一直在做这件事,但我在这篇文章下面的评论一直被删除。
这回答了问题的标题,即:
如何从任务.运行回UI线程封送事件?
当您从不同的线程接收到事件时,这是一种非常有效的更新UI的方法。
ssgvzors3#
如果您声明“[..]
DoSomethingSynchronous
[is] I/O bound”,您也可以通过将DoSomethingSynchronous
中的IO绑定操作 Package 在Task.Run
中来使其异步。如果
DoSomethingSynchronous
是这样的你可以改写成。
.ConfigureAwait(true)
可以省略,但是确保在等待之后的代码将在原始同步上下文(即UI线程)中被调度。然后,您显然需要重命名方法等,但是如果有一天您可以在
DoSomethingSynchronous
中使用真正的asycn IO,那么这将使代码更易于维护6gpjuf904#
由于
UpdateSomethingAsync
需要访问UI上下文,因此不应将其 Package 在Task.Run
调用中(您很少需要从Task.Run
调用async
方法,通常只有在方法实现不正确且无法修复时才需要调用)。相反,
DoSomethingSynchronous
应该是您从Task.Run
调用的东西。毕竟,该方法的目的是在线程池线程中异步运行同步方法。因此,仅将其用于您希望在线程池线程中运行的同步方法,而不是(假定的)需要访问UI上下文的异步方法。wf82jlnq5#
WinUI 3遵循以下方法。
lg40wkob6#
我想我会在做更多的研究后自己回答这个问题。大多数其他的答案在某种程度上是正确的,但不一定能一下子解释整个问题,所以我会在这里总结一下。
这个问题的第一个片段是按事件方式工作的,但是如果使用UpdateSomethingAsync中的Synchronous路径,则会阻塞。事件工作是因为“await”自动捕获UI线程的SynchronizationContext(这是关键),这样,从UpdateSomethingAsync引发的任何事件都会通过SynchronizationContext整理回UI。这只是使用async/await的正常方式:
Task.Run的工作方式大致相同,* 如果您不使用它来运行异步方法的话。* 换句话说,它不会阻塞,并且仍然会向UI线程发送事件,因为UpdateSomethingAsync被替换为Synchronous方法。这只是Task.Run的正常用法:
然而,问题中的原始代码 * 是 * Async,因此上面的内容不适用。问题提出了以下代码片段作为可能的解决方案,但当事件引发时,它会错误地输出对UI的非法跨线程调用,* 因为我们正在使用Task.Run来调用Async方法,并且由于某种原因,这不会设置SynchronizationContext:*
看起来起作用的是使用Task.Factory.StartNew将UI同步上下文分配给使用TaskScheduler.FromCurrentSynchronizationContext的任务,如下所示:
同样有效的方法是简单地将DoSomethingSynchronous Package 在Task.Run中,这种方法非常简单,但对调用者来说有点“谎言”:
我认为这是一个小谎言,因为该方法并不是真正的完全异步的,因为它派生了一个线程池线程,但可能永远不会给调用者带来问题。
希望这是有道理的。如果其中任何一个被证明是不正确的请让我知道,但这是我的测试所揭示的。