Monitor a Process Asynchronously - More DelegateMarshaler Tricks
In my last post, I wrote about a nifty DelegateMarshaler class that I’d run across on Kevin’s blog. Figuring out how to update the UI from another thread has always been a tricky thing, but using the DelegateMarshaler class and anonymous methods really makes the code clean and easy to read. You don’t even need to use a BackgroundWorker, which was by far the easiest solution up to this point.
Recently I needed to add some functionality to a program such that it started an external command-line application and printed the output to a TextBox in the window.
The obvious solution is to just call the external program and pipe the output into a text file, then read the file. Unfortunately I couldn’t do this since I actually needed to see the output in real time as the external program was executing.
Running an external process is a simple matter using the System.Diagnostics.Process class. There are lots of good examples around the web showing how to use it. Much less well-known is the ability to hook into events fired whenever the external process prints a line to the console. This is exactly what I needed — I want to attach event handlers to these events and update the UI when they are called. I just need to be careful to do this on the correct thread.
I made a demo project to try out my idea of using the DelegateMarshaler instead of a BackgroundWorker to accomplish this. My main form just takes a command and executes it, showing the output in the lower pane:
When I click on the Run button, I want to execute some code on another thread from the ThreadPool (using ThreadPoolHelper), start the external process, attach a handler to the event that gets fired when a new line is written to the console, and add that line to the lower text box (from the UI thread). Seems simple enough.
private void btnRun_Click(object sender, EventArgs e)
{
txtOutput.Clear();
DelegateMarshaler uiThread = DelegateMarshaler.Create();
ThreadPoolHelper.QueueUserWorkItem(() =>
{
Process p = new Process();
p.StartInfo.FileName = "cmd.exe";
p.StartInfo.Arguments = "/c " + txtCmd.Text;
p.StartInfo.UseShellExecute = false;
p.StartInfo.CreateNoWindow = true;
p.StartInfo.RedirectStandardOutput = true;
p.OutputDataReceived += (o, args) =>
{
string text = args.Data + Environment.NewLine;
uiThread.Invoke(() =>
{
txtOutput.AppendText(text);
});
};
p.Start();
p.BeginOutputReadLine();
p.WaitForExit();
});
}
You simply can’t get much better than that. I didn’t have to define any other classes or even write any other methods. Everything to accomplish the concurrent operation is self-contained in the btnRun_Click method. And, it reads exactly like how I thought about implementing it, almost like a book.
Here’s a screenshot of the result:
If I type in something that takes a bit longer to run (e.g. dir C:\Windows\), I can even move the window around as the text box is being updated asynchronously. The DelegateMarshaler and anonymous methods make it much easier to write responsive UIs.
I think I’ll be using this pattern in the future
Hope someone finds that useful!
Edit: I posted a zip file containing the sample code over on the Code Samples page. http://thevalerios.net/matt/code-samples/
You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.
August 9th, 2008 at 5:57 pm
Very nice!
August 29th, 2008 at 10:29 am
Way cool. I want to expand it. I am writing a ClearCase client for the masses, right now I fire an ugly command window that has pauses so you can see the errors and text inputs for some yes/no and other simple console input.
I want to add a much smaller (3-line high) stderr window, and a single line with a Send button so someone can send text to stdin. I would simply deactivate the run button and input command line.
However, I do not understand this construct:
p.OutputDataReceived += (o, args) =>
{
string text = args.Data + Environment.NewLine;
uiThread.Invoke(() =>
{
txtOutput.AppendText(text);
});
};
What does this (o,args) => { block } syntax mean?
I’ll submit my code if I can get it to work.
August 29th, 2008 at 10:41 am
Hey Bill,
Glad you like it
That block of code is using a lot of the syntactic sugar that C# 3.0 lets you do. Anything of the form (parm1,parm2…) => {…; return ___;} is an anonymous method. It’s just a method that takes two parameters and returns something (or not). The compiler can infer the type of the arguments and the return value.
In that example, I could have defined a method “private void OnOutputDataReceived(object sender, EventArgs args)” and then done “p.OutputDataReceived += OnOutputDataReceived;”. This is still shorthand — the verbose way of saying this is to do “p.OutputDataReceived += new OutputDataReceivedEventHandler(OnOutputDataReceived);”
They’re all equivalent in terms of the compiled code, but hooking up anonymous methods directly to the events can come in handy.
You’ll probably need the ErrorDataReceived event: http://msdn.microsoft.com/en-us/library/system.diagnostics.process.errordatareceived.aspx
You could also just hook into this event and add the text to the same text box if you wanted. (i.e. put but stderr and stdout into the same box).
As for piping text into the input stream, I think you can hook up a StreamWriter to the StandardInput stream: http://msdn.microsoft.com/en-us/library/system.diagnostics.process.standardinput.aspx
Hope that helps! I’d be really interested in seeing your final demo showing how to read/write to all 3 streams
August 29th, 2008 at 11:07 am
Hey, Matt,
Thanks for the quick response and the helpful description.
I have started a project and got stuck on some dot-net framework things, as well as a dependency.
I can’t find System.Linq, I think this is a > 2.0 thing.
What is this:
using ThreadingUtilities;
Many thanks for the help!
August 29th, 2008 at 11:11 am
Hey Bill,
You can probably get rid of the System.Linq using statement since that is part of .NET 3.0. I don’t think I’m using any Linq in that project. ThreadingUtilities is the other DLL project in the download that provides the typesafe wrappers to ThreadPool.QueueUserWorkItem (as ThreadPoolHelper.QueueUserWorkItem). I have a couple posts around here on the blog explaining how that works.
Good luck!
August 29th, 2008 at 11:45 am
Hi Matt,
I commented out the linq. Will establish a reference to the DLL. Ran into several other anonymous methods. I assume that this:
ThreadPoolHelper.QueueUserWorkItem(() =>
{
can also be replaced by
private void queueUserWorker() { }
ThreadPoolHelper.QueueUserWorkItem(queueUserWorker);
correct?
August 29th, 2008 at 11:46 am
Looks like I need to re-do the threadingutilities for 2.0, cracking open that code now.
August 29th, 2008 at 12:11 pm
public void Invoke(Action action)
gives the error “Using the generic type ‘System.Action’ requires ‘1′ type arguments CS0305
I am really out of my depth here. I will come back to this when I have a bit more breathing room. It is unfortunate that National Instruments is slow to upgrade the dot-net framework used with their projects, I can fully understand any reluctance to help with down-grading working code.