I've gotten this type of thing working in the past with a BackgroundWorker, but I want to use the new async/await approach of .NET 4.5. I may be barking up the wrong tree. Please advise.
Goal: Create a component that will do some long-running work and show a modal form with a progress bar as it's doing the work. The component will get the handle to a window to block interaction while it's executing the long-running work.
Status: See the code below. I thought I was doing well until I tried interacting with the windows. If I leave things alone (i.e. don't touch!), everything runs "perfectly", but if I do so much as click on either window the program hangs after the long-running work ends. Actual interactions (dragging) are ignored as though the UI thread is blocked.
Questions: Can my code be fixed fairly easily? If so, how? Or, should I be using a different approach (e.g. BackgroundWorker)?
Code (Form1 is a standard form with a ProgressBar and a public method, UpdateProgress, that sets the ProgressBar's Value):
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Starting..");
var mgr = new Manager();
mgr.GoAsync();
Console.WriteLine("..Ended");
Console.ReadKey();
}
}
class Manager
{
private static Form1 _progressForm;
public async void GoAsync()
{
var owner = new Win32Window(Process.GetCurrentProcess().MainWindowHandle);
_progressForm = new Form1();
_progressForm.Show(owner);
await Go();
_progressForm.Hide();
}
private async Task<bool> Go()
{
var job = new LongJob();
job.OnProgress += job_OnProgress;
job.Spin();
return true;
}
void job_OnProgress(int percent)
{
_progressForm.UpdateProgress(percent);
}
}
class LongJob
{
public event Progressed OnProgress;
public delegate void Progressed(int percent);
public void Spin()
{
for (var i = 1; i <= 100; i++)
{
Thread.Sleep(25);
if (OnProgress != null)
{
OnProgress(i);
}
}
}
}
class Win32Window : IWin32Window
{
private readonly IntPtr _hwnd;
public Win32Window(IntPtr handle)
{
_hwnd = handle;
}
public IntPtr Handle
{
get
{
return _hwnd;
}
}
}
}
You can start an async operation from the UI thread, await it without blocking the UI thread, and naturally resume on the UI thread when it's done. This is a very powerful feature, and most of the time you don't even need to think about it; it “just works”.
await can only be used in async functions. It is used for calling an async function and waits for it to resolve or reject. await blocks the execution of the code within the async function in which it is located.
The main benefits of asynchronous programming using async / await include the following: Increase the performance and responsiveness of your application, particularly when you have long-running operations that do not require to block the execution.
The async
and await
keywords do not mean "run on a background thread." I have an async
/await
intro on my blog that describes what they do mean. You must explicitly place CPU-bound operations on a background thread, e.g., Task.Run
.
Also, the Task-based Asynchronous Pattern documentation describes the common approaches with async
code, e.g., progress reporting.
class Manager
{
private static Form1 _progressForm;
public async Task GoAsync()
{
var owner = new Win32Window(Process.GetCurrentProcess().MainWindowHandle);
_progressForm = new Form1();
_progressForm.Show(owner);
var progress = new Progress<int>(value => _progressForm.UpdateProgress(value));
await Go(progress);
_progressForm.Hide();
}
private Task<bool> Go(IProgress<int> progress)
{
return Task.Run(() =>
{
var job = new LongJob();
job.Spin(progress);
return true;
});
}
}
class LongJob
{
public void Spin(IProgress<int> progress)
{
for (var i = 1; i <= 100; i++)
{
Thread.Sleep(25);
if (progress != null)
{
progress.Report(i);
}
}
}
}
Note that the Progress<T>
type properly handles thread marshaling, so there's no need for marshaling within Form1.UpdateProgress
.
@StephenCleary's answer is correct. Though, I had to make a little modification to his answer to get the behavior what I think OP wants.
public void GoAsync() //no longer async as it blocks on Appication.Run
{
var owner = new Win32Window(Process.GetCurrentProcess().MainWindowHandle);
_progressForm = new Form1();
var progress = new Progress<int>(value => _progressForm.UpdateProgress(value));
_progressForm.Activated += async (sender, args) =>
{
await Go(progress);
_progressForm.Close();
};
Application.Run(_progressForm);
}
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