Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Async/Await with a WinForms ProgressBar

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;
        }
    }
}
}
like image 292
Todd Sprang Avatar asked Jul 31 '13 13:07

Todd Sprang


People also ask

Can an async method run on the UI thread of a Windows Forms app?

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”.

What is the difference between Sync and await?

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.

Does async await improve performance?

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.


2 Answers

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.

like image 54
Stephen Cleary Avatar answered Oct 14 '22 18:10

Stephen Cleary


@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);
}
like image 28
YK1 Avatar answered Oct 14 '22 20:10

YK1