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;
});
}
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. 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;
}
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With