我试图更好地理解如何从异步操作更新 Windows 窗体进度条,但我从中得到了一些意想不到的行为。
基本上我有一个按钮,点击后应该更新进度条,然后在进度条 100% 更新后将其设置回 0。
这是我的代码:
private async void button1_Click(object sender, EventArgs e)
{
await CallMethodAsync().ContinueWith((prevTask) =>
{
prevTask.Wait();
progressBar1.Invoke(new Action(() => { progressBar1.Value = 0; }));
});
}
private static async Task ExecuteMethodAsync(IProgress<double> progress = null)
{
double percentComplete = 0;
bool done = false;
while (!done)
{
if (progress != null)
{
progress.Report(percentComplete);
}
percentComplete += 10;
if(percentComplete == 100)
{
done = true;
}
}
}
private async Task CallMethodAsync()
{
var progress = new Progress<double>();
progress.ProgressChanged += (sender, args) => { progressBar1.Increment(10); };
await ExecuteMethodAsync(progress);
}
有了这个实现,即使我在应该更新进度条值的操作上调用“Wait()”,进度条也根本不会更新。
如果我删除这部分代码:
progressBar1.Invoke(new Action(() => { progressBar1.Value = 0; }));
进度条会更新,但它一直保持这样,我想在它完全填满后将其设置回 0,这样我可以在再次单击按钮时再次更新它。
有人可以解释一下我做错了什么吗?
最佳答案
发明 async-await 语法的原因之一是,当使用 ContinueWith
等函数连接任务时,很难遵循指令序列。
如果您使用 async-await,则很少需要使用像 ContinueWith
这样的语句。在 await
之后,线程已经继续执行 await 之后的语句。
如果单击按钮,您要调用 ExcecuteMethodAsync
。此函数采用 IProgress,因为它要定期报告进度。你想异步调用这个函数,所以每当函数必须等待某事时,它并没有真正等待,而是将控制权返回给你,这样你就可以做其他事情而不是真正等待,直到遇到等待,在这种情况下你的调用者继续处理,直到他遇到 await 等。
async-await 的好处是,在调用异步函数后继续执行的线程与调用线程具有相同的上下文。这意味着您可以将其视为您的原始线程。无需 InvokeRequired,无需使用互斥锁等保护数据。
您的函数可以简化如下:
async Task CallMethodAsync()
{
var progress = new Progress<double>();
progress.ProgressChanged += OnProgressReported;
await ExecuteMethodAsync(progress);
}
private void OnProgressReported(object sender, ...)
{
// because this thread has the context of the main thread no InvokeRequired!
this.progressBar1.Increment(...);
}
private async void button1_Click(object sender, EventArgs e)
{
await CallMethodAsync();
}
因此,当单击按钮时,将调用 CallMethodAsync。此函数将创建一个 Progress 对象并订阅其 Report 事件。请注意,这仍然是您的 UI 线程。然后它调用 ExecuteMethodAsync,这将定期引发事件 Report,该事件由 OnProgressReported 处理。
因为 ExecuteMethodAsync 是异步的,所以您可以确定其中有一个 await。这意味着无论何时必须等待,控制权都会返回给调用者,即 CallMethodAsync
,直到遇到等待,在本例中是立即等待。
Control 在调用堆栈中上升到调用者,即 button1_click,它立即遇到 await,因此 Control 在调用堆栈中上升,等等。
所有这些控件都有相同的上下文:就好像它们是同一个线程。
一篇对我理解 async-await 帮助很大的文章是 this interview with Eric Lippert.在中间某处搜索异步等待
另一篇帮助我学习良好实践的文章是 this article by the ever so helpful Stephen Cleary和 Async/Await - Best Practices in Asynchronous Programming同样由斯蒂芬克利里
关于c# - 从异步方法更新进度条,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44959668/