{"id":1141,"date":"2014-01-13T13:00:03","date_gmt":"2014-01-13T13:00:03","guid":{"rendered":"http:\/\/writeasync.net\/?p=1141"},"modified":"2014-01-06T03:41:53","modified_gmt":"2014-01-06T03:41:53","slug":"providing-async-functionality-for-system-diagnostics-process","status":"publish","type":"post","link":"http:\/\/writeasync.net\/?p=1141","title":{"rendered":"Providing async functionality for System.Diagnostics.Process"},"content":{"rendered":"<p>If you need to interact with processes in .NET, you could do worse than <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.diagnostics.process(v=vs.110).aspx\"><code>System.Diagnostics.Process<\/code><\/a>. But you could also do a <em>bit<\/em> better, especially since <code>Process<\/code> provides no out-of-the-box asynchronous methods. But we can fix that!<\/p>\n<p>Let&#8217;s devise a <code>ProcessEx<\/code> class that provides async versions of the <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/0w4h05yb(v=vs.110).aspx\"><code>Start<\/code><\/a> and <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.diagnostics.process.waitforexit(v=vs.110).aspx\"><code>WaitForExit<\/code><\/a> methods. Here is the class overview:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic sealed class ProcessEx : IDisposable\r\n{\r\n    private ProcessEx(Process process);\r\n\r\n    public Process Inner { get; }\r\n\r\n    public static Task&lt;ProcessEx&gt; StartAsync(ProcessStartInfo psi);\r\n\r\n    public Task WaitForExitAsync();\r\n\r\n    public Task&lt;bool&gt; WaitForExitAsync(TimeSpan timeout);\r\n\r\n    public void Dispose();\r\n}\r\n<\/pre>\n<p>The constructor is private since we will only support instantiating <code>ProcessEx<\/code> via the <code>StartAsync<\/code> factory method:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic static Task&lt;ProcessEx&gt; StartAsync(ProcessStartInfo psi)\r\n{\r\n    return Task.Factory.StartNew(i =&gt; new ProcessEx(Process.Start((ProcessStartInfo)i)), psi);\r\n}\r\n<\/pre>\n<p>Unfortunately, there is no Windows API to asynchronously start a process so we are stuck offloading the blocking <code>Process.Start<\/code> call to a worker thread. Okay, so what do we do now that we have our hands on a <code>Process<\/code> instance? Well, we need to ensure that <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.diagnostics.process.enableraisingevents(v=vs.110).aspx\"><code>EnableRaisingEvents<\/code><\/a> is set to <code>true<\/code> so that we can be notified via the <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.diagnostics.process.exited(v=vs.110).aspx\"><code>Exited<\/code><\/a> event when the process exits. This will enable us to build a simple async version of <code>WaitForExit<\/code>, using our old friend <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/dd449174(v=vs.110).aspx\"><code>TaskCompletionSource<\/code><\/a>. Here is the relevant code:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\nprivate readonly Process process;\r\nprivate readonly TaskCompletionSource&lt;bool&gt; exited;\r\n\r\nprivate ProcessEx(Process process)\r\n{\r\n    this.process = process;\r\n    this.exited = new TaskCompletionSource&lt;bool&gt;();\r\n    this.process.EnableRaisingEvents = true;\r\n    this.process.Exited += this.OnProcessExited;\r\n    if (this.process.HasExited)\r\n    {\r\n        this.exited.TrySetResult(false);\r\n    }\r\n}\r\n\r\npublic Process Inner\r\n{\r\n    get { return this.process; }\r\n}\r\n\r\npublic Task WaitForExitAsync()\r\n{\r\n    return this.exited.Task;\r\n}\r\n\r\nprivate void OnProcessExited(object sender, EventArgs e)\r\n{\r\n    this.exited.TrySetResult(false);\r\n}\r\n<\/pre>\n<p>Note the use of <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/dd449176(v=vs.110).aspx\"><code>TrySetResult<\/code><\/a> rather than <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/dd449202(v=vs.110).aspx\"><code>SetResult<\/code><\/a> and the explicit check of <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.diagnostics.process.hasexited(v=vs.110).aspx\"><code>HasExited<\/code><\/a>. This is to handle two potential race conditions:<\/p>\n<ol>\n<li>The <code>Exited<\/code> event may never be raised since the process could have already exited by the time we subscribe.\n<\/li>\n<li>The check of <code>HasExited<\/code> may occur at the same time the <code>OnProcessExited<\/code> handler is invoked.<\/li>\n<\/ol>\n<p>Let&#8217;s get <code>Dispose<\/code> out of the way. To avoid <code>WaitForExitAsync<\/code> from blocking forever, we&#8217;ll attempt to complete it with an <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.objectdisposedexception(v=vs.110).aspx\"><code>ObjectDisposedException<\/code><\/a>:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic void Dispose()\r\n{\r\n    this.Dispose(true);\r\n    GC.SuppressFinalize(this);\r\n}\r\n\r\nprivate void Dispose(bool disposing)\r\n{\r\n    if (disposing)\r\n    {\r\n        this.process.EnableRaisingEvents = false;\r\n        this.process.Exited -= this.OnProcessExited;\r\n        this.exited.TrySetException(new ObjectDisposedException(&quot;ProcessEx&quot;));\r\n        this.process.Dispose();\r\n    }\r\n}\r\n<\/pre>\n<p>Now we need to implement the overload of <code>WaitForExitAsync<\/code> that accepts a timeout: <\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\npublic async Task&lt;bool&gt; WaitForExitAsync(TimeSpan timeout)\r\n{\r\n    TaskCompletionSource&lt;bool&gt; tcs = new TaskCompletionSource&lt;bool&gt;();\r\n    using (CancellationTokenSource cts = new CancellationTokenSource())\r\n    {\r\n        Task exitedTask = this.exited.Task;\r\n        Task completedTask;\r\n        using (cts.Token.Register(o =&gt; ((TaskCompletionSource&lt;bool&gt;)o).SetResult(false), tcs))\r\n        {\r\n            cts.CancelAfter(timeout);\r\n            completedTask = await Task.WhenAny(tcs.Task, exitedTask);\r\n        }\r\n\r\n        bool result = false;\r\n        if (completedTask == exitedTask)\r\n        {\r\n            await exitedTask;\r\n            result = true;\r\n        }\r\n\r\n        return result;\r\n    }\r\n}\r\n<\/pre>\n<p>Basically, we need to await the result of two operations, one tracking the timeout (<code>tcs<\/code> above) and the other tracking process exit (<code>this.exited<\/code>). The timeout task will only complete if the timeout expires before the process actually exits. To manage this, we use <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/hh194893(v=vs.110).aspx\"><code>CancellationTokenSource.CancelAfter<\/code><\/a> and a <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.threading.cancellationtokenregistration(v=vs.110).aspx\"><code>CancellationTokenRegistration<\/code><\/a> which will complete the timeout task. (Note that if the <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.threading.cancellationtoken(v=vs.110).aspx\"><code>CancellationToken<\/code><\/a> is already in a canceled state, <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/dd321663(v=vs.110).aspx\"><code>Register<\/code><\/a> will run the callback immediately, so there is no race here.) We use <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/hh194796(v=vs.110).aspx\"><code>Task.WhenAny<\/code><\/a> to notify us when <em>either<\/em> task completes. (Since we are good citizens, we <a href=\"http:\/\/msdn.microsoft.com\/en-us\/library\/system.threading.cancellationtokenregistration.dispose(v=vs.110).aspx\"><code>Dispose<\/code><\/a> the registration after this point since we don&#8217;t need it anymore.) If the first task to complete indicates that the process exited, we await the task (in case it completed with an exception) and return true. Otherwise, the process is still running as far as we know and we return false.<\/p>\n<p>That&#8217;s all there is to it. Here is a simple use case of creating a file, opening a Notepad window to display it, and waiting for the user to close it:<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\r\ninternal sealed class Program\r\n{\r\n    private static void Main(string&#x5B;] args)\r\n    {\r\n        MainAsync().Wait();\r\n    }\r\n\r\n    private static async Task MainAsync()\r\n    {\r\n        string fileName = Environment.ExpandEnvironmentVariables(@&quot;%TEMP%\\SampleFile.txt&quot;);\r\n        using (FileStream stream = CreateAsyncStream(fileName))\r\n        {\r\n            string text = &quot;Close this window to unblock app.&quot;;\r\n            byte&#x5B;] buffer = Encoding.ASCII.GetBytes(text);\r\n            await stream.WriteAsync(buffer, 0, buffer.Length);\r\n        }\r\n\r\n        using (ProcessEx process = await ProcessEx.StartAsync(new ProcessStartInfo(&quot;notepad.exe&quot;, fileName)))\r\n        {\r\n            bool exited;\r\n            do\r\n            {\r\n                Console.WriteLine(&quot;Waiting for process to exit...&quot;);\r\n                exited = await process.WaitForExitAsync(TimeSpan.FromSeconds(1.0d));\r\n            }\r\n            while (!exited);\r\n        }\r\n\r\n        Console.WriteLine(&quot;Done.&quot;);\r\n    }\r\n\r\n    private static FileStream CreateAsyncStream(string fileName)\r\n    {\r\n        return new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.Read, 65536);\r\n    }\r\n}\r\n<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>If you need to interact with processes in .NET, you could do worse than System.Diagnostics.Process. But you could also do a bit better, especially since Process provides no out-of-the-box asynchronous methods. But we can fix that! Let&#8217;s devise a ProcessEx&hellip; <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[21],"tags":[],"class_list":["post-1141","post","type-post","status-publish","format-standard","hentry","category-async"],"_links":{"self":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/1141","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1141"}],"version-history":[{"count":0,"href":"http:\/\/writeasync.net\/index.php?rest_route=\/wp\/v2\/posts\/1141\/revisions"}],"wp:attachment":[{"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1141"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1141"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/writeasync.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1141"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}