2017-07-06 76 views
0

我想更好地了解如何从异步操作更新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); 
    } 

有了这个实施进度条并不是在所有的,即使我把更新的“等待()”上应该更新进度条的值的操作。

如果我删除这部分代码:

progressBar1.Invoke(new Action(() => { progressBar1.Value = 0; })); 

进度条被更新,但它仍然是所有的时间这样的,我想将其设置回0,一旦它被完全充满,这样我当我再次点击按钮时,可以再次更新它。

有人可以请解释我做错了什么?

+1

你应该得到一个编译器警告*“CS1998 C#这个异步方法缺少'await'操作符,并且会同步运行,考虑使用'await'操作符来等待非阻塞API调用或'await Task.Run(.. )'在后台线程上执行CPU绑定的工作。“*。你做错的事情是导致编译器警告,并且警告甚至会告诉你如何解决它。 –

+1

此外,你不应该混合[ContinueWith和异步](https://blog.stephencleary.com/2013/10/continuewith-is-dangerous-too.html)(但这不会导致你的问题) –

+1

检出https ://stackoverflow.com/questions/11500563/winform-multithreading-use-backgroundworker-or-not –

回答

3

发明了异步等待语法的原因之一是因为当使用诸如ContinueWith之类的函数连接任务时很难遵循指令序列。

如果使用异步等待,很少有必要使用像ContinueWith这样的语句。在await之后,线程在await之后已经继续执行语句。

如果点击该按钮,则要拨打ExcecuteMethodAsync。此功能需要IProgress,因为它想定期报告进度。你想异步地调用这个函数,所以无论函数什么时候需要等待什么,它都不会真的等待,但是将控制返回给你,这样你就可以做其他的事情而不是真的在等待,直到遇到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,它将定期引发由OnProgressReported处理的事件报告。

因为ExecuteMethodAsync是异步的,所以你可以确定有某处等待着它。这意味着无论什么时候它必须等待,控制权返回给调用者,即CallMethodAsync,直到遇到await,在这种情况下是立即。

控制进入调用堆栈向上给呼叫者,这是的button1_Click,在那里它立即遇到AWAIT,所以控制进入到调用堆栈等

所有这些控件具有相同的上下文:它是作为如果它们是相同的线程。

的文章,对我帮助很大,了解异步的await是中间为异步this interview with Eric Lippert.搜索某个地方等待

另一个articel是帮了我很多学习好的做法是this article by the ever so helpful Stephen ClearyAsync/Await - Best Practices in Asynchronous Programming也由斯蒂芬·克利