Thou Shalt Not Break the Golden Rule of Windows Multithreading. Or, Why the Dispatcher Rocks.

I’m in the middle of building a fun little WPF standalone Windows application that needs to build a project from the command-line, using msbuild.exe.

To do this, I’m using System.Diagnostics.Process to execute msbuild.exe in another process. The basic code looks like the following:

 

<!-- MainWindow – MARKUP -->

<Window
  xmlns="https://schemas.microsoft.com/

         winfx/2006/xaml/presentation"

  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

  x:Class="DispatcherConsoleOutputSample.MainWindow"

  Title="Dispatcher Console Output Sample"

  Width="400" Height="200">

 

    <DockPanel>

      <Button Click = "buildAppButton_Click" ... >

        Build an App

      </Button>

    </DockPanel>

 

</Window>

 

// MainWindow - CODEBEHIND

using System.Diagnostics; // Process, ProcessStartInfo

using System.Windows; // Window, RoutedEventArgs

public partial class MainWindow : Window

{

public MainWindow()

{

InitializeComponent();

}

void buildAppButton_Click(

object sender, RoutedEventArgs e)

{

// Create a new process

Process process = newProcess();

// Configure the process to run msbuild.exe to

// build AProject.csproj

process.StartInfo.FileName = @"...\msbuild.exe";

process.StartInfo.Arguments = @"...\AProject.csproj";

// Start the process

process.Start();

}

}

When the application runs and the button is clicked, the code creates a new process in which msbuild.exe is run to compile a C# project. The following figure shows the application in action.

 

But I didn’t really want the msbuild application’s console output going to a different window. In this case, it was going to be much easier and nicer for a user to see the output appear in the application window. Consequently, the application needed a little update, which included:

1) Hiding the console window in which msbuild was running.

2) Acquiring the console output from msbuild.

3) Displaying the console output from the application window.

Fortunately, this was easy to configure. Here are the updates:

<!-- MainWindow – MARKUP -->

<Window

  xmlns="https://schemas.microsoft.com/

         winfx/2006/xaml/presentation"

  xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

  x:Class="DispatcherConsoleOutputSample.MainWindow"

  Title="Dispatcher Console Output Sample"

  Width="400" Height="200">

 

    <DockPanel>

      <Button Click = "buildAppButton_Click" ... >

        Build an App

      </Button>

      <!-- TextBox control to show the console output -->

      < TextBoxName = "outputTextBox" ... />

    </DockPanel>

 

</Window>

 

// MainWindow – CODEBEHIND

using System.Diagnostics; // ..., DataReceivedEventHandler

using System.Windows; // Window, RoutedEventArgs

public partial class MainWindow : Window

{

    public MainWindow()

    {

        InitializeComponent();

    }

    void buildAppButton_Click(

      object sender, RoutedEventArgs e)

    {

        // Create a new process

        Process process = new Process();

        // Configure the process to run msbuild.exe to

        // build AProject.csproj

        process.StartInfo.FileName = @"...\msbuild.exe";

        process.StartInfo.Arguments = @"...\AProject.csproj";

        // Don't show the console window

        process.StartInfo.CreateNoWindow = true;

        process.StartInfo.UseShellExecute = false;

        // Capture redirected console output

        process.StartInfo.RedirectStandardOutput = true;

        process.OutputDataReceived +=

process_OutputDataReceived;

     // Start the process

        process.Start();

        // Read the console output as its generated by msbuild

        process.BeginOutputReadLine();

    }

    void process_OutputDataReceived(

      object sender, DataReceivedEventArgs e)

    {

        // Display console output in text box

        this.outputTextBox.Text += e.Data;

    }

}

While easy to configure, and seemingly intuitive, there was one little issue that is called out in the following figure:

Thou Shalt not Break the Golden Rule of Windows Multithreading.

The problem is that the OutputDataReceived event handler is running on a different thread than the UI (or main) thread. Essentially this is because the event is raised by the process in which msbuild is running. And, as Visual Studio’s debugger was kind enough to point out, this code is violating the golden rule of multithreading:

Thou shalt only update UI using code that runs on the same thread as the UI.

To obey the golden rule, the data needs to be shuttled back to the UI thread so we can update the TextBox on the UI thread, rather than from some other thread.

 In WPF, a quick, simple, and nice solution is to use the Dispatcher. Each WPF application will have at least one thread, the UI thread, and a corresponding Dispatcher object associated with it. One of the Dispatcher’s capabilities is to manage a queue of work items for a particular thread. (You can find out more about the Dispatcher’s capabilities here.) All we need to do is to send a work item to the Dispatcher on the UI thread for safe processing. This entails the following tweaks to the code:

// MainWindow – CODEBEHIND

using System.Diagnostics; // ..., DataReceivedEventHandler
using System.Windows; // Window, RoutedEventArgs
using System.Windows.Threading; // Dispatcher,  
                                // DispatcherPriority
public partial class MainWindow : Window
{
    public MainWindow()
    {
      InitializeComponent();
    }

    // NOT HANDLED ON UI THREAD

    void process_OutputDataReceived(

      object sender, DataReceivedEventArgs e)

    {

        // Display console output in text box

        // NOT ALLOWED FROM A NON-UI THREAD

        // this.outputTextBox.Text += e.Data;

        // Console output received from process

        // running msbuild

        // FORWARD TO UI THREAD

        this.Dispatcher.BeginInvoke(

            DispatcherPriority.Normal,

            (DispatcherOperationCallback)delegate(object arg)

            {

                // HANDLED ON UI THREAD

                this.outputTextBox.Text += e.Data + "\r\n";

                return null;

            },

            null);

    }

}

This code does three things. First, it creates a work item that it sends to the Dispatcher by calling Dispatcher.BeginInvoke. Second, it assigns the work item a normal priority. Third, it provides a delegate that the Dispatcher calls when it processes this work item. In this case, I’m using an anonymous method, although that’s not required. Also, I’m using the DispatcherOperationCallback delegate, since it’s already exists in the .NET Framework, but feel free to use the most appropriate delegate.

The resulting updates work as expected, and as demonstrated by the following figure.

Why the Dispatcher Rocks

In WPF, when (and if) you need to send messages across threads, your first choice should be the Dispatcher.