我想更好地了解如何从异步操作更新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,这样当我再次单击按钮时我可以再次更新它。
有人可以解释一下我做错了什么吗?
答案 0 :(得分:4)
发明async-await语法的原因之一是因为当使用ContinueWith
等函数连接任务时很难遵循指令序列。
如果使用async-await,则很少需要使用ContinueWith
之类的语句。在await
之后,线程已经在等待之后继续执行语句。
如果单击该按钮,则要调用ExcecuteMethodAsync
。此功能需要IProgress,因为它希望定期报告进度。你想异步调用这个函数,所以每当函数必须等待某个东西时,它都不会等待,但是会将控制返回给你,这样你就可以做其他事情而不是真正等待,直到你遇到await,in在哪种情况下,你的来电者会继续处理,直到他遇到等待等等。
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。此函数将创建A Progress对象并订阅其Report事件。请注意,这仍然是您的UI线程。然后它调用ExecuteMethodAsync,它将定期引发事件Report,由OnProgressReported处理。
因为ExecuteMethodAsync是异步的,所以你可以确定它中存在等待的地方。这意味着无论何时必须等待,控制权都会返回到调用者CallMethodAsync
,直到遇到await,在这种情况下会立即进行。
控制将调用堆栈调到调用者,这是button1_click,它会立即遇到await,因此控制会上升到调用堆栈等。
所有这些控件都具有相同的上下文:就好像它们是同一个线程一样。
一篇帮助我理解async-await的文章是this interview with Eric Lippert.在中间寻找异步等待
另一个帮助我学习良好实践的知识是this article by the ever so helpful Stephen Cleary和Async/Await - Best Practices in Asynchronous Programming也是Stephen Cleary
答案 1 :(得分:-1)
您的问题正在发生,因为 ExecuteMethodAsync(...)实际上不是异步的。
在while循环之前添加以下内容以使其异步
await Task.Delay(1);
或将代码的某些同步部分(例如while循环)封装到a中:
await Task.Run(() => { ... });
或(最好的),在函数的开头添加以下内容:
await Task.Yield(); // Make us async right away