C# 在UI上的同步方法中运行异步委托是否安全?
如果我有一个使用同步方法的应用程序,在UI线程上调用如下所示的异步方法是否安全,或者是否存在问题或潜在的死锁情况?我知道呼叫等待显然会引起问题,但我觉得这可能会解决问题C# 在UI上的同步方法中运行异步委托是否安全?,c#,async-await,C#,Async Await,如果我有一个使用同步方法的应用程序,在UI线程上调用如下所示的异步方法是否安全,或者是否存在问题或潜在的死锁情况?我知道呼叫等待显然会引起问题,但我觉得这可能会解决问题 public void MyMainMethod(){ var getResult = Task.Run(async () => { await getResultAsync(); }).Result; myLabel.Text = getResult; } 我可以成功地在UI线程上运行而不会出现问题,但我觉得我
public void MyMainMethod(){
var getResult = Task.Run(async () => { await getResultAsync(); }).Result;
myLabel.Text = getResult;
}
我可以成功地在UI线程上运行而不会出现问题,但我觉得我可能遗漏了一些东西。我知道我可以使用Task并继续,但在本例中,我希望在退出同步方法之前等待async方法的结果
更新/澄清
在上面的示例中,假设MyMainMethod是一个重写的方法或属性等,并且不能修改为异步 最好通过dispatcher调用ui更新
Task task = LoadTask();
task.ContinueWith(t =>
Dispatcher.BeginInvoke(() => UpdateUI()));
public async Task LoadTask()
{
Task getdata =
Task.Factory.StartNew(() =>
{
Sleep(3000);
});
await getdata;
return;
}
让我们看看您的代码:
public void MyMainMethod(){
var getResult = Task.Run(async () => { await getResultAsync(); }).Result;
myLabel.Text = getResult;
}
无论getResultAsync
内部发生了什么,当调用task.Result
时,此代码都会阻塞UI线程。在大多数情况下,这已经是错误的
此外,您的getResultAsync
是async
这一事实表明它内部已经有了一个异步操作。没有理由用Task.Run
来包装它,除非在getResultAsync
中混合执行CPU和IO绑定的任务。即使如此,也可能没有必要(有关更多详细信息,请参阅)
您可以使用confireawait(false)
控制wait
中的getResultAsync
继续上下文,并应尽可能避免死锁和冗余上下文切换
因此,代码可以简化为:
public void MyMainMethod(){
var getResult = getResultAsync().Result;
myLabel.Text = getResult;
}
它仍然会阻塞UI。为了避免阻塞,您需要使其异步
。请参阅Stephen Cleary的《异步》一路走来
如果无法将其修改为async
(如问题更新中所述),则上述内容是您所能获得的最好结果事实上,它仍然可能导致死锁,具体取决于getResultAsync
内部的情况,而不执行任务。运行。为避免死锁,您不应尝试使用同步调用(如control.Invoke
insidegetResultAsync
)访问UI线程,或wait
使用TaskScheduler.FromCurrentSynchronizationContext
在UI线程上调度的任何任务
但是,通常可以并希望将这样的代码重新分解为异步版本:
public async Task MyMainMethod(){
var getResult = await getResultAsync();
myLabel.Text = getResult;
}
您可以从应用程序的顶级入口点调用它,如UI事件处理程序:
async void Button_Click(object sender, EventArg e)
{
try
{
await MyMainMethod();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
谢谢你的建议。在大多数情况下,我理解这是可能要做的,但在本例中,我希望在存在同步方法之前等待异步方法完成。我更新了上面的示例。“此外,getResultAsync是异步的这一事实表明它内部已经有一个异步操作。没有理由用Task.Run来包装它”-只有正确实现了getResultAsync
,这才是准确的。如果它是一个async
方法,其中包含await
语句,而不包含ConfigureAwait(false)
,则在TaskScheduler上启动getResultsAsync
。默认的和取消包装(据我所知,Task.Run
就是这样做的)可能会使您免于死锁。否则答案很好。@KirillShlenskiy,这是一个很好的观点,我链接以获取更多详细信息确实使用了ConfigureAwait(false)
。OTOH,如果它被称为Task.Run(lambda).Result
并且在lambda
中的任何地方,他执行类似于control.Invoke的操作,就会出现死锁。总之,这取决于getResultsAsync
中的内容。我真的很惊讶这只有5张赞成票。。。这是我见过的最好的解释之一。谢谢我不同意你的澄清。有办法让<代码>异步代码>代码工作,你只需要退后一步,考虑一下。如果方法实现是async
,则基本签名应返回任务
。我有和的博客条目。异步代码上的同步阻塞不应该是必须的;几乎总是有更好的解决方案。