简体   繁体   中英

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?

The Copy function doesn't need to interact with the UI but the Progress function does.

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.

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 :

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:

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

  1. async / await is all about not blocking a thread - any thread - when dealing with 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.
  2. You are getting that error because you are not using async / await properly (regardless the above). 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.

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.

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.

Check out this article on reporting progress with async/await, I think it will help out.

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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