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