我最近一直在开发一些库,我偶然发现了一个困扰我的问题,因为我似乎找不到发生这种情况的原因。
我举个例子:
public partial class Form1 : Form
{
TaskCompletionSource<bool> _completionTask;
public Form1()
{
InitializeComponent();
}
//When we click a button, this happens.
private async void start_Click(object sender, EventArgs e)
{
_completionTask = new TaskCompletionSource<bool>();
_ = Task.Run(() => Work());
await WaitForCompletion();
Debug.WriteLine("Successfully Left Await!");
}
private void Work()
{
for (int i = 0; i < 100000; i++)
{
BeginInvoke(() =>
{
label1.Text = i.ToString();
});
}
_completionTask.SetResult(true);
}
private async Task WaitForCompletion()
{
await _completionTask.Task;
}
}
我们这里有一个 Windows 窗体内非常简单的代码。我们有一个在单击按钮时运行的方法。此方法运行“Task.Run()”内的“Work”方法,然后等待“_completionTask”任务。
在整个过程结束时,我们应该在控制台中看到调试“成功离开等待!”。有趣的是 - 它永远不会到达它......我猜这是因为“BeginInvoke”垃圾邮件使 UI 线程太忙?如果将“BeginInvoke”更改为“Invoke”,它就会起作用。同样,您可以输入 '_completionTask.SetResult(true);'进入“BeginInvoke”,它也会起作用!
我的问题是 - 有谁真正知道这背后的真正原因,也许有一些关于如何正确处理这种情况的解释?
async/await
取代 BeginInvoke
的需要。也没有理由使用 TaskCompletionSource。如果一个方法需要执行一些繁重的任务但仍然更新 UI,它应该使用例如 Task.Run
执行异步部分并等待任务。 await
执行返回到 UI 线程后,可以更新 GUI。
这意味着问题的代码可以替换为:
private async void start_Click(object sender, EventArgs e)
{
await DoWork();
Debug.WriteLine("Successfully Left Await!");
}
private async Task DoWork()
{
for (int i = 0; i < 100000; i++)
{
await Task.Run(()=>DoSomeStuff());
label1.Text = i.ToString();
}
}
或者,如果实际工作是异步的,例如进行 HTTP 调用:
private async Task DoWork()
{
for (int i = 0; i < 100000; i++)
{
var message=await httpClient.GetStringAsync(url);
label1.Text = message;
}
}
另一种选择是使用 Progress 任务从后台线程(例如从对 UI 一无所知的库)回调 UI。顾名思义,它通常用于报告进度。回调在创建
Progress<T>
对象的线程上执行,但任何线程都可以调用 Report
方法:
private async void start_Click(object sender, EventArgs e)
{
var progress=new Progress<string>(i=>label1.Text=i.ToString());
await Task.Run(()=>DoWork(progress);
Debug.WriteLine("Successfully Left Await!");
}
private async Task DoWork(IProgress<int> progress)
{
for (int i = 0; i < 100000; i++)
{
DoSomeStuff();
progress.Report(i);
}
}
在这种情况下,整个
DoWork
方法在后台运行。