Do you know that situation? You have a WinForm application and you want to do some long running operation and you don't want to block the GUI in the meantime...
From the beginning of the .NET era three patterns for asynchronous programming were developed. All are well described at MSDN: Asynchronous Programming Patterns. In short they are:
To be honest, each of them has own disadvantages. Which they are I'll explain in the future (maybe). But two are shared in common:
How to solve these problems?
Finally, to the core of this article: how to write synchronous code which runs in background and updates the GUI controls in a nice way?
Use the LINQ and behold:
private void button1_Click(object sender, EventArgs e) { // run the method in a background thread RunInBackground(DoSomething); } /// <summary> /// Do some work. This can be run synchronously or asynchronously without problems. /// </summary> private void DoSomething() { // updates GUI safely, but note that this call will block GUI InvokeIfNeeded(() => label1.Text = "Running..."); // do something Thread.Sleep(1000); // updates GUI safely InvokeIfNeeded(() => { label1.Text = "In the middle of processing..."; label1.Text = "I am working hard!"; }); // do something else Thread.Sleep(1000); // updates GUI safely InvokeIfNeeded(() => label1.Text = "Finished"); } private void button2_Click(object sender, EventArgs e) { // do something label1.Text = "Starting"; // start processing of the following code in a background thread // note that the RunInBackground method returns "immediately" // it doesn't execute the code, it only starts a background thread // execution of the code is scheduled for future RunInBackground(() => { // do something Thread.Sleep(1000); // updates GUI safely InvokeIfNeeded(() => label1.Text = "Finished"); }); // do something else, but note that this call is executed // before the code above is even started (almost sure) // more over this call will block GUI again Thread.Sleep(1000); }
The same in VB.NET:
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click ' run the method in a background thread RunInBackground(AddressOf DoSomething) End Sub ''' <summary> ''' Do some work. This can be run synchronously or asynchronously without problems. ''' </summary> Private Sub DoSomething() ' updates GUI safely, but note that this call will block GUI InvokeIfNeeded(Sub() Label1.Text = "Running...") ' do something Thread.Sleep(1000) ' updates GUI safely InvokeIfNeeded( Sub() Label1.Text = "In the middle of processing..." Label1.Text = "I am working hard!" End Sub) ' do something else Thread.Sleep(1000) ' updates GUI safely InvokeIfNeeded(Sub() Label1.Text = "Finished") End Sub Private Sub Button2_Click(sender As System.Object, e As System.EventArgs) Handles Button2.Click ' do something Label1.Text = "Starting" ' start processing of the following code in a background thread ' note that the RunInBackground method returns "immediately" ' it doesn't execute the code, it only starts a background thread ' execution of the code is scheduled for future RunInBackground( Sub() ' do something Thread.Sleep(1000) ' updates GUI safely InvokeIfNeeded(Sub() Label1.Text = "Finished") End Sub) ' do something else, but note that this call is executed ' before the code above is even started (almost sure) ' more over this call will block GUI again Thread.Sleep(1000) End Sub
Isn't it nice? The only thing left is to define the RunInBackground and InvokeIfNeeded methods. Here they are:
using System.ComponentModel; ... /// <summary> /// Invokes specified action if required. Executed action will block GUI. /// </summary> /// <param name="action">An action to invoke.</param> private void InvokeIfNeeded(Action action) { if (InvokeRequired) Invoke(action); else action(); } /// <summary> /// Starts the action in a background thread. /// </summary> /// <param name="action">An action to invoke.</param> private void RunInBackground(Action action) { BackgroundWorker worker = new BackgroundWorker(); worker.DoWork += (s, e) => action(); worker.RunWorkerCompleted += (s, e) => { // process unhandled exceptions if (e.Error != null) { InvokeIfNeeded(() => { // show error in dialog box MessageBox.Show(string.Format( "An exception occured in a background thread.\n{0}", e.Error)); }); } }; worker.RunWorkerAsync(); }
Also for VB.NET:
Imports System.ComponentModel ... ''' <summary> ''' Invokes specified action if required. ''' </summary> ''' <param name="action">An action to invoke.</param> Private Sub InvokeIfNeeded(ByVal action As Action) If InvokeRequired Then Invoke(action) Else action() End If End Sub ''' <summary> ''' Starts the action in a background thread. ''' </summary> ''' <param name="action">An action to run.</param> Private Sub RunInBackground(ByVal action As Action) Dim worker = New BackgroundWorker() AddHandler worker.DoWork, Sub() action() AddHandler worker.RunWorkerCompleted, Sub(s As Object, e As RunWorkerCompletedEventArgs) ' process unhandled exceptions If e.Error IsNot Nothing Then InvokeIfNeeded( Sub() ' show error in dialog box MessageBox.Show(String.Format( "An exception occured in a background thread.{0}{1}", vbNewLine, e.Error)) End Sub) End If End Sub worker.RunWorkerAsync() End Sub
If you don't want to have the InvokeIfNeeded and RunInBackground methods on each WinForm, you can add System.Windows.Forms.Control to the parameters and do it like this:
private void InvokeIfNeeded(Control control, Action action) { if (control.InvokeRequired) control.Invoke(action); else action(); } ... InvokeIfNeeded(this, () => label1.Text = "Running...");
And VB.NET:
Private Sub InvokeIfNeeded(ByVal control As Control, ByVal action As Action) If control.InvokeRequired Then control.Invoke(action) Else action() End If End Sub ... InvokeIfNeeded(Me, Sub() Label1.Text = "Running...")
Any comments are welcome (you can send them to lukas.matyska@rebex.net).