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=http://schemas.microsoft.com/


         winfx/2006/xaml/presentation


  xmlns:x=”http://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 = new Process();


 


        // 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=http://schemas.microsoft.com/


         winfx/2006/xaml/presentation


  xmlns:x=http://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 –>


      <TextBox Name=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.

Comments (3)

  1. Kamran says:

    Just so you know, don't do txtOutput.Text += blah because that made my app climb to 1.6GB of memory and threw an OutOfMemoryException, plus it would cause any new updates to the textbox to block for a few milliseconds, causing hiccups in UI responsiveness.

    It is much more efficient to use txtOutput.AppendText(blah) as that only increased my memory by only 10MB then went back down.

  2. Kamran – Thanks for the tip!  It's an important thing to keep in mind when working with large amounts of text.