Cross-thread operations with PostSharp

When you try to inform User Interface (UI) about the background operation progress or completion, you can not do it from the background thread.

UI doesn’t like to be informed about anything from a different thread: you’ll get “System.InvalidOperationException: Cross-thread operation not valid: Control ‘xxx’ accessed from a thread other than the thread it was created on.” exception from WinForms control, if you try:

public void ShowStatus(ApplicationStatus status)
{
    this._lblServiceAddress.Text = "Connected to: "
        + status.WebServiceAddress;
    this._lblUserId.Text = "Working as: "
        + status.UserId;
}

The easiest sollution is to use BeginInvoke method on the control Control or Form:

public void ShowStatus(ApplicationStatus status)
{
    this.BeginInvoke((MethodInvoker)(() =>
        {
            this._lblServiceAddress.Text = "Connected to: "
                + status.WebServiceAddress;
            this._lblUserId.Text = "Working as: "
                + status.UserId;
        }));
}

Well, it’s fun to write this once, but if you have many operations done in background sooner or later you’d like to have something nicer. Like an attribute for example:

[ThreadAccessibleUI]
public void ShowStatus(ApplicationStatus status)
{
    this._lblServiceAddress.Text = "Connected to: " + status.WebServiceAddress;
    this._lblUserId.Text = "Working as: " + status.UserId;
}

Here’s the attribute implementation of such attribute using PostSharp 1.5:

/// <summary>
/// PostSharp attribute.
/// Use it to mark Control's methods that
/// are invoked from background thread.
/// </summary>
/// <remarks>
/// Be careful as BeginInvoke uses the message queue.
/// This means that the interface will be refreshed
/// when application has a chance to process its messages.
/// </remarks>
[AttributeUsage(AttributeTargets.Method)]
[Serializable] // required by PostSharp
public class ThreadAccessibleUIAttribute : OnMethodInvocationAspect
{
    public override void OnInvocation(
        MethodInvocationEventArgs eventArgs)
    {
        Control control = eventArgs.Instance as Control;
        if (control == null)
            throw new ApplicationException(
                "ThreadAccessibleUIAttribute" +
                "can be applied only to methods on Control class");

        // The form may be closed before
        // this method is called from another thread.
        if (control.Created == false)
            return;

        control.BeginInvoke((MethodInvoker)eventArgs.Proceed);
    }
};

Tags:  

Questions?

Consider using our Q&A forum for asking questions.

5 Responses to “Cross-thread operations with PostSharp”

  1. The Morning Brew - Chris Alcock | The Morning Brew #526 Says:

    […] Cross-thread operations with PostSharp – Using PostSharp to inject code on method start to provide an easy means of calling methods on Winforms controls from any thread and ensuring that the call is made with BeginInvoke […]

  2. Andreas Hallberg Says:

    Great stuff. But if you’re worried about BeginInvoke’s async nature, why not just use control.Invoke in the aspect implementation instead?

  3. Andreas Hallberg Says:

    … and now I answer my own question: http://kristofverbiest.blogspot.com/2007/02/avoid-invoke-prefer-begininvoke.html

    🙂

  4. Blog » Blog Archive » SynchronizationContext and WinForms Says:

    […] Use Control.BeginInvoke with PostSharp […]

  5. hartson23 Says:

    Hi – I am definitely delighted to discover this. cool job!