我正在尝试使用async / await调用运行操作时更新进度条,但在更新进度条时UI会冻结。
基本上,当使用BackgroundWorker时,这是一个非常简单的要求,但现在使用async / await调用似乎有点复杂。
首先,异步/等待使用可以像BackgroundWorker一样填充进度条吗?这两种方法之间有什么共同之处吗?
在我的用户界面上,我有一个进度条和一个按钮,当点击按钮时,进度条开始更新,但UI冻结,这不应该发生,因为async / await应该以“并发”的方式工作。 如果我使用BackgroundWorked执行此操作,则UI不会冻结。
有人可以解释一下我做错了什么,为了在进度条更新时保持UI响应,我怎么能修改下面的代码? 在更新进度条时,异步/等待使用的行为可以像BackgroundWorker那样吗?
以下是我的代码:
private async void button1_Click(object sender, EventArgs e)
{
await CallMethodAsync();
}
private async Task CallMethodAsync()
{
this.progressBar1.Value = 0;
this.progressBar1.Maximum = 1000000;
var progressbar1 = new Progress<double>();
progressbar1.ProgressChanged += Progressbar1_ProgressChanged;
await ExecuteMethodAsync(progressbar1);
}
private async Task ExecuteMethodAsync(IProgress<double> progress = null)
{
await Task.Run(new Action(() => {
double percentComplete = 0;
bool done = false;
while (!done)
{
if (progress != null)
{
progress.Report(percentComplete);
}
percentComplete += 1;
if (percentComplete == 1000000)
{
done = true;
}
}
}));
}
private void Progressbar1_ProgressChanged(object sender, double e)
{
this.progressBar1.Increment(1);
}
答案 0 :(得分:1)
你的代码基本上没问题。唯一的问题是它没有做任何实际的工作,所以它花费所有的时间来尝试更新UI。在我的计算机上,整个过程在几秒钟内完成,但由于UI忙于处理来自进度的更新,因此几乎没有时间用于其他用户交互。例如,如果我拖动窗口,窗口位置将在任务循环执行期间仅更新一次或两次。
在实际情况中,您可以在主要工作块之间更频繁地更新进度。您可以通过摆脱循环外的Task.Run()
并在循环内使用Task.Delay()
代表您长期运行的工作,在您已获得的代码中更好地模拟这一点。在实际情况中,您可以将Task.Delay()
替换为Task.Run()
来执行个人工作组件。
当然,如果你这样做,你不再需要Progress<T>
课程。它确实是一个有用的类,但通常在使用async
/ await
时,工作和进度更新可以交替使用,允许您返回UI线程以使用进度更新await
而不是必须通过Progress<T>
。如果您按照我的建议更改代码示例,则看起来更像是:
private async void button1_Click(object sender, EventArgs e)
{
await CallMethodAsync();
}
private async Task CallMethodAsync()
{
this.progressBar1.Value = 0;
this.progressBar1.Maximum = 1000;
await ExecuteMethodAsync();
}
private async Task ExecuteMethodAsync()
{
for (int percentComplete = 0; percentComplete < 1000; percentComplete++)
{
await Task.Delay(10);
progressBar1.Increment(1);
}
}
甚至不需要Progressbar1_ProgressChanged()
方法,其余代码的 lot 更简单。因为我使用await
,所以我可以直接使用progressBar1
对象,而不是使用像Progress<T>
这样的跨线程调用机制
请注意,在上面,我只等了10毫秒。这是关于Windows线程调度程序的限制 - 它不能比这更精确地调度线程 - 但是仍然足够慢以使UI线程跟上更新。一个真实世界的场景可能比这更长,大约100毫秒。
即使有相对较高的更新频率,也可以顺利地进行UI交互,例如拖动窗口。