c# - 使用 Task.Result 时防止 UI 冻结
问题描述
我正在调用 Task.Run(() => DoSomething()).Result 这会导致 UI 冻结并且它发生是因为正在使用“.Result”。我需要 Result 因为我想返回值。
我不想让 StartSomething 方法异步,因为我不想等待 StartSomething 方法。我希望等待发生在 DoSomething()。
所以基本上我需要一个由同步方法调用的异步方法,而不会冻结 UI。另外,我想将异步方法中的值返回到按钮单击的顶层。
可以改进此代码还是有其他解决方案?
private TaskCompletionSource<bool> TaskCompletion = null;
private void Button_Click(object sender, RoutedEventArgs e)
{
bool k = StartSomething();
}
private bool StartSomething()
{
return Task.Run(() => DoSomething()).Result;
}
private async Task<bool> DoSomething()
{
TaskCompletion = new TaskCompletionSource<bool>();
await Task.WhenAny(TaskCompletion.Task, Task.Delay(3000));
MessageBox.Show("DoSomething");
return true;
}
解决方案
方法StartSomething()
对我没有意义。它开始一个新的Task
,然后只是同步地等待.Result
这个任务的结果(),这实际上是没有用的——它几乎[*]与直接调用相同DoSomething()
。也DoSomething()
已经是异步的,所以你不需要Task
为它开始一个新的。
看起来你根本不需要StartSomething()
方法。如果您制作Button_Click
handler async
,您可以await DoSomething()
直接直接:
private TaskCompletionSource<bool> TaskCompletion = null;
private async void Button_Click(object sender, RoutedEventArgs e)
{
bool k = await DoSomething();
}
private async Task<bool> DoSomething()
{
TaskCompletion = new TaskCompletionSource<bool>();
await Task.WhenAny(TaskCompletion.Task, Task.Delay(3000));
MessageBox.Show("DoSomething");
return true;
}
编辑:
虽然一直使用异步解决方案(如上所示)是 IMO 的首选方式,但如果您真的无法将调用代码更改为async
,我可以考虑两种async
方法从同步方法调用方法而不阻塞 UI。首先是手动设置这样的延续任务:
private void Button_Click(object sender, RoutedEventArgs e)
{
DoSomething().ContinueWith((task) =>
{
bool k = task.Result;
// use the result
},
// TaskScheduler argument is needed only if the continuation task
// must run on the UI thread (eg. because it access UI elements).
// Otherwise this argument can be omitted.
TaskScheduler.FromCurrentSynchronizationContext());
// Method can exit before DoSomething().Result becomes
// available, which keep UI responsive
}
因此,您基本上将同步方法(一个拆分而不是每个拆分await
)拆分为几个部分(延续 lambda 方法),由.ContinueWith
. 这类似于await
引擎盖下的操作。问题在于,与await
(生成漂亮而干净的代码)不同的是,您的代码将充满这些延续 lambda。using
当你添加异常处理块、块等时,情况会变得更糟。
第二种方法是使用嵌套循环,例如。Stephen Toub 的WaitWithNestedMessageLoop扩展方法:
static T WaitWithNestedMessageLoop<T>(this Task<T> task)
{
var nested = new DispatcherFrame();
task.ContinueWith(_ => nested.Continue = false, TaskScheduler.Default);
Dispatcher.PushFrame(nested);
return task.Result;
}
嵌套循环是非常先进的技术(我实际上从未使用过),除非您必须这样做,否则我不建议使用它。
[*]在异常处理、执行线程等方面存在差异,但这些与本题无关。
推荐阅读
- python - 在python mysql中将图像插入数据库
- android - 如何更正更新 Android .apk?
- android - 在android中解析节点json
- javascript - 如何根据顺序中剩余的文本框对齐密码框
- javascript - 在登录页面从数据库中检索选择框的值
- stanford-nlp - 使用 Stanford CoreNLP 中的 Simple API,有没有办法获得多令牌实体提及?
- hibernate - 即使外键在 HQL 中为空也返回数据
- php - Laravel 中添加标签的解决方案
- android - 解析失败:Lorg/jsoup/Jsoup;
- c# - If-Statement 没有被观察到