简体   繁体   English

异步进度条更新

[英]Async Progress Bar Update

I am trying to use an async await to update a progress bar on my WinForm based on a copy operation, but the progress bar will only update when the Copy function has finished, and then throws an exception that it can't update as it's not on the same thread? 我试图使用async await根据复制操作更新我的WinForm上的进度条,但进度条只会在Copy功能完成时更新,然后抛出一个无法更新的异常,因为它不是在同一个线程上?

The Copy function doesn't need to interact with the UI but the Progress function does. 复制功能不需要与UI交互,但Progress功能可以。

The UI isn't blocked though, so it would appear the async part is working as expected, it's just interacting with the UI thread that isn't. 虽然UI没有被阻止,但是看起来异步部分正在按预期工作,它只是与UI线程进行交互。

long fileProgress = 0;
long totalProgress = 0;
bool complete = false;

CopyFileEx.CopyFileCallbackAction callback(FileInfo source, FileInfo destination, object state, long totalFileSize, long totalBytesTransferred)
{
      fileProgress = totalBytesTransferred;
      totalProgress = totalFileSize;
      return CopyFileEx.CopyFileCallbackAction.Continue;
}

async Task Progress()
{
      await Task.Run(() =>
      {
           while (!complete)
           {
                if (fileProgress != 0 && totalProgress != 0)
                {
                     fileProgressBar.Value = (int)(fileProgress / totalProgress) * 100;
                }
           }
      });
}

private async void startButton_Click(object sender, EventArgs e)
{
      Copy();
      await Progress();
      MessageBox.Show("Done");
}

void Copy()
{
      Task.Run(() =>
      {
           CopyFileEx.FileRoutines.CopyFile(new FileInfo(@"C:\_USB\Fear.rar"), new FileInfo(@"H:\Fear.rar"), CopyFileEx.CopyFileOptions.All, callback, null);
           complete = true;
      });
}

You need to use IProgress<T> here : 你需要在这里使用IProgress<T>

private async void startButton_Click(object sender, EventArgs e)
{
      var progress = new Progress<int>(percent =>
      {
         fileProgressBar.Value = percent;
      });

      await Copy(progress);

      MessageBox.Show("Done");
}

void Copy(IProgress<int> progress)
{
      Task.Run(() =>
      {
           CopyFileEx.FileRoutines.CopyFile(new FileInfo(@"C:\_USB\Fear.rar"), new FileInfo(@"H:\Fear.rar"), CopyFileEx.CopyFileOptions.All, callback, null,progress);
           complete = true;
      });
}

and your callback method can report the progress of IProgress<T> like: 并且您的回调方法可以报告IProgress<T>的进度,如:

CopyFileEx.CopyFileCallbackAction callback(FileInfo source, FileInfo destination, object state, long totalFileSize, long totalBytesTransferred,IProgress<int> progress)
{
      fileProgress = totalBytesTransferred;
      totalProgress = totalFileSize;
      progress.Report(Convert.ToInt32(fileProgress/totalProgress));
      return CopyFileEx.CopyFileCallbackAction.Continue;
}

You can look at this very good article by Stephen Cleary 你可以看看Stephen Cleary的这篇非常好的文章

  1. async / await is all about not blocking a thread - any thread - when dealing with I/O. async / await就是在处理I / O时没有阻塞线程 - 任何线程。 Putting a blocking I/O call inside Task.Run() (like you did in Copy() ) doesn't avoid blocking - it just create a Task which some other thread will later pick up, just to find it itself gets blocked when it hits the blocking CopyFileEx.FileRoutines.CopyFile() method. Task.Run()放置一个阻塞 I / O调用(就像你在Copy()所做的那样)并没有避免阻塞 - 它只是创建一个其他线程稍后会选择的任务,只是发现它本身被阻塞了它命中了阻塞的CopyFileEx.FileRoutines.CopyFile()方法。
  2. You are getting that error because you are not using async / await properly (regardless the above). 您收到该错误是因为您没有正确使用async / await (无论如何)。 Think about which thread is trying to modify the UI object fileProgressBar : the random threadpool thread that picks up the Task you create on Task.Run() gets to execute fileProgressBar.Value = ... , which will obviously throw. 考虑哪个线程正在尝试修改UI对象fileProgressBar :随机线程fileProgressBar线程获取您在Task.Run()上创建的任务获取执行fileProgressBar.Value = ... ,这显然会抛出。

This is one way to avoid this situation: 这是避免这种情况的一种方法:

async Task Progress()
{
      await Task.Run(() =>
      {
           //A random threadpool thread executes the following:
           while (!complete)
           {
                if (fileProgress != 0 && totalProgress != 0)
                { 
                    //Here you signal the UI thread to execute the action:
                    fileProgressBar.Invoke(new Action(() => 
                    { 
                        //This is done by the UI thread:
                        fileProgressBar.Value = (int)(fileProgress / totalProgress) * 100 
                    }));
                }
           }
      });
}

private async void startButton_Click(object sender, EventArgs e)
{
      await Copy();
      await Progress();
      MessageBox.Show("Done");  //here we're on the UI thread.
}

async Task Copy()
{
    //You need find an async API for file copy, and System.IO has a lot to offer.
    //Also, there is no reason to create a Task for MyAsyncFileCopyMethod - the UI
    // will not wait (blocked) for the operation to complete if you use await:
    await MyAsyncFileCopyMethod();
    complete = true;
}

When using async/await I use the IProgress and Progress implementations, which abstract away some of the callback details. 当使用async / await时,我使用IProgress和Progress实现,它们抽象出一些回调细节。

Im pretty sure what you have there isn't working because it's being run in a background thread via the Task.Run() call, so it can't really access the UI controls which are in the UI thread context. 我很确定你有没有工作,因为它是通过Task.Run()调用在后台线程中运行的,所以它无法真正访问UI线程上下文中的UI控件。

Check out this article on reporting progress with async/await, I think it will help out. 查看这篇关于使用async / await报告进度的文章,我认为它会有所帮助。

http://blog.stephencleary.com/2012/02/reporting-progress-from-async-tasks.html http://blog.stephencleary.com/2012/02/reporting-progress-from-async-tasks.html

In your current implementation if you wanted it to work with the callback I think I would just update the progress bar directly in your callback method instead of checking status of the progress variables in a loop, which is going to block your UI when you take it out of the background thread in order to actually access the progress bar. 在你当前的实现中,如果你想让它与回调一起工作,我想我只是直接在你的回调方法中更新进度条,而不是在循环中检查进度变量的状态,当你接受它时会阻止你的UI超出后台线程以便实际访问进度条。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM