Is There Any Async Equivalent of Process.Start

Is there any async equivalent of Process.Start?

Process.Start() only starts the process, it doesn't wait until it finishes, so it doesn't make much sense to make it async. If you still want to do it, you can do something like await Task.Run(() => Process.Start(fileName)).

But, if you want to asynchronously wait for the process to finish, you can use the Exited event together with TaskCompletionSource:

static Task<int> RunProcessAsync(string fileName)
{
var tcs = new TaskCompletionSource<int>();

var process = new Process
{
StartInfo = { FileName = fileName },
EnableRaisingEvents = true
};

process.Exited += (sender, args) =>
{
tcs.SetResult(process.ExitCode);
process.Dispose();
};

process.Start();

return tcs.Task;
}

Async process start and wait for it to finish

The .NET 5 introduced the new API Process.WaitForExitAsync, that allows to wait asynchronously for the completion of a process. It offers the same functionality with the existing Process.WaitForExit, with the only difference being that the waiting is asynchronous, so it does not block the calling thread.

Usage example:

private async void button1_Click(object sender, EventArgs e)
{
string filePath = Path.Combine
(
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
Guid.NewGuid().ToString() + ".txt"
);
File.WriteAllText(filePath, "Hello World!");
try
{
using Process process = new();
process.StartInfo.FileName = "Notepad.exe";
process.StartInfo.Arguments = filePath;
process.Start();
await process.WaitForExitAsync();
}
finally
{
File.Delete(filePath);
}
MessageBox.Show("Done!");
}

In the above example the UI remains responsive while the user interacts with the opened file. The UI thread would be blocked if the WaitForExit had been used instead.

Process.WaitForExit() asynchronously

process.EnableRaisingEvents = true;

process.Exited += [EventHandler]

Asynchronous method in a C# class that executes a process


  1. You don't need the async on your method signature, because you don't use await. It's enough to return a Task. The caller may await that Task - or not, it has nothing to do with your method.

  2. Don't use the async keyword on that lambda and don't use the asynchronous ReadToEnd inside that lambda. It's hard to predict what happens if you return from that event handler before it's really finished. And anyway you want to finish that method. It's called when the process exited, there is no need to make this async.

  3. Here it's the same as in (2). I think it's ok to do this "synchronously" inside this event handler. It will only block this handler, but the handler is called after the process has exited, so I guess it's ok for you.

  4. Your exception handling looks OK, but I would add another try/catch block inside the Exited event handler. But that's not based on knowledge, rather on experience that everywhere something can go wrong :)


For a better way to get the standard and error output, I suggest to subscribe to the ErrorDataReceived and OutputDataReceived events and fill StringBuilders with the received data.

In your method, declare two StringBuilders:

StringBuilder outputBuilder = new StringBuilder();
StringBuilder errorBuilder = new StringBuilder();

And subscribe to the events right after you instantiated process:

process.OutputDataReceived += (sender, e) => outputBuilder.AppendLine(e.Data);
process.ErrorDataReceived += (sender, e) => errorBuilder.AppendLine(e.Data);

Then you only need to call these two methods right after you called process.Start() (it won't work before, because the stdout and stderr are not yet opened):

process.Start();
process.BeginErrorReadLine();
process.BeginOutputReadLine();

In your Exited event handler you can then call outputBuilder.ToString() (or errorBuilder.ToString() respectively) instead of ReadToEnd and everything should work fine.

Unfortunatly there is a drawback, too: if the process is very very fast, your Exited handler may theoritically be called before those Begin*ReadLine calls. Don't know how to handle that, but it's not very likely to happen.

Entity Framework and Process.Start prevents async method call from executing


    private void button1_Click(object sender, EventArgs e)
{
new Thread(BackgroundWorker).Start();
}

private void BackgroundWorker()
{
List<SimpleTestTable> list = new PlainTestDBEntities().SimpleTestTables.ToList();

new Thread(StartProcess).Start();

while (true)
{
// Wait for the external Process to complete its work.
}
}

private void StartProcess()
{
Thread.Sleep(2000);

Console.WriteLine("I am executed totally fine.");

Process.Start("calc.exe");

Console.WriteLine("I am now always executed because there is no UI handler interfering with Entity Framework and Process.Start");
}

This solves the problem. After trying the first example in a Console Application I saw that it works just fine. So thanks to the hint in one of the friendly comments about the UI handler, I figured, that the UI handler must complete it's work first in order for all the threads to run properly.

But it still does not answer why the phenomena occurs in the first place. Why does Entity Framework, Click Handler and Process.Start block each other in the first example? And you can test that easily, set up a simple project with an Entity Framework table, load as mentioned in the first example. As soon as you remove either the Entity Framework code, the while() or the Process.Start it works. But not all of them together.

Would be very interesting to know, why exactly is that.

Is there a point to use async/await on methods that return a value?

Don't be so selfish - you're not the only one that exists :)

I joke, but that is the point. There are other things to be done! If that code is run synchronously, then the thread would be dormant - doing nothing - while it waits to get the data. That task might only take 100ms, but that's a lot of time in CPU time.

Using async/await allows the thread to go and work on some other code that might need to be run in your application while you're waiting for that task to complete, rather than just sitting around waiting and doing nothing.

It's described really well in a Microsoft article called The Task asynchronous programming model in C#, especially the illustration it uses about making breakfast.

In ASP.NET it is especially important because there is a limited number of threads in the thread pool. So the more you can do with a single thread, the better.

Is there a way to use System.Diagnostics.Process in an IAsyncEnumerator?

Without a minimal, reproducible example, it will be impossible to address your concern completely. But we can deal with the two specific issues you've raised.

First, if your object (such as Process) doesn't support IAsyncDisposable, then just don't use that. Use the synchronous using statement instead.

As far as the yield return in the method goes, if you take a moment you'll probably see that what you tried to write doesn't make any sense. How would the event handler, which is a completely different method, be able to cause the current method to yield a new value? You need the event handler to signal to the current method when the event occurs. You can do this in a variety of ways, but SemaphoreSlim is one of the more straightforward ways.

Putting those together, you might get something like this:

private static async IAsyncEnumerable<string> RunProcessAsync(string args)
{
using (var myProcess = new Process())
{
myProcess.StartInfo.FileName = @"path\to\file.exe";
myProcess.StartInfo.Arguments = args;
myProcess.StartInfo.UseShellExecute = false;
myProcess.StartInfo.RedirectStandardError = true;
myProcess.StartInfo.CreateNoWindow = true;

ConcurrentQueue<string> dataQueue = new ConcurrentQueue<string>();
SemaphoreSlim dataSemaphore = new SemaphoreSlim(0);

myProcess.ErrorDataReceived += (s, e) =>
{
dataQueue.Enqueue(e.Data);
dataSemaphore.Release();
}

myProcess.Start();
myProcess.BeginErrorReadLine();

while (true)
{
await dataSemaphore.WaitAsync();

// Only one consumer, so this will always succeed
dataQueue.TryDequeue(out string data);

if (data == null) break;

yield return data;
}
}
}

Since you didn't provide an actual MCVE, it's not feasible for me to try to reconstruct your scenario from scratch. So the above isn't compiled, never mind tested. But it should show the gist.

Which is, you need to keep your iterator method asynchronous (which means you can't block on a call to WaitForExit()), and you need to somehow move the data received by the ErrorDataReceived event handler back to the iterator method. In the above, I use a thread-safe queue object in conjunction with a semaphore.

The semaphore count gets increased (via Release()) in the event handler each time a line of data is received, which is then consumed by the iterator method by decreasing the semaphore count (via WaitAsync()) and returning the line received.

There are lots of other mechanisms one could use for the producer/consumer aspect here. There's a well-received Q&A here that discusses async-compatible mechanisms, including a custom version of BlockingCollection<T> that supports async operations, and a mention of the BufferBlock<T> class from TPL Dataflow.

Here is an example that uses BufferBlock<T> (which has semantics very similar to BlockingCollection<T> but includes async handling of the consuming code):

static async IAsyncEnumerable<string> RunProcessAsync(string args)
{
using (var process = new Process())
{
myProcess.StartInfo.FileName = @"path\to\file.exe";
process.StartInfo.Arguments = args;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.CreateNoWindow = true;

BufferBlock<string> dataBuffer = new BufferBlock<string>();

process.ErrorDataReceived += (s, e) =>
{
if (e.Data != null)
{
dataBuffer.Post(e.Data);
}
else
{
dataBuffer.Complete();
}
};

process.Start();
process.BeginErrorReadLine();

while (await dataBuffer.OutputAvailableAsync())
{
yield return dataBuffer.Receive();
}
}
}



Related Topics



Leave a reply



Submit