I'm using an async method to start a Process (to call raspistill). The code is pretty standard, but like the developer in this question: How can I stop async Process by CancellationToken?, I'd like to be able to cancel the process with a CancellationToken. As an example, one of my ASP.NET core API endpoints starts a MJPEG stream that will need to be closed.
I've taken on board the answer to the above question, and written the following ProcessRunner class:
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using NLog;
namespace RabbieCam.Utils
{
/// <summary>
/// Run process as asynchronous task.
/// </summary>
public static class ProcessRunner
{
private static Logger logger = LogManager.GetCurrentClassLogger();
/// <summary>
/// Run process as asynchronous task.
/// </summary>
public static async Task<int> RunProcessAsync(string fileName,
string args,
CancellationToken token)
{
CancellationTokenRegistration registration;
using (var process = new Process
{
StartInfo =
{
FileName = fileName,
Arguments = args,
UseShellExecute = false,
CreateNoWindow = true,
RedirectStandardOutput = true,
RedirectStandardError = true
},
EnableRaisingEvents = true
})
{
var exitCode = await RunProcessAsync(
process,
token,
out registration
).ConfigureAwait(false);
logger.Debug($"Process {fileName} ended");
registration.Dispose();
logger.Debug("Registration disposed");
return exitCode;
}
}
/// <summary>
/// Run process asynchronously.
/// </summary>
private static Task<int> RunProcessAsync(
Process process,
CancellationToken token,
out CancellationTokenRegistration registration
)
{
var tcs = new TaskCompletionSource<int>();
process.Exited += (s, ea) => tcs.SetResult(process.ExitCode);
// process.OutputDataReceived += (s, ea) => // TODO if needed
// process.ErrorDataReceived += (s, ea) => // TODO if needed
registration = token.Register(() =>
{
try
{
process.Kill();
}
catch (InvalidOperationException e)
{
logger.Error($"Failed to kill process: {e.Message}");
}
}
);
bool started = process.Start();
if (!started)
{
registration.Dispose();
throw new InvalidOperationException(
"Could not start process: " + process
);
}
process.BeginOutputReadLine();
process.BeginErrorReadLine();
return tcs.Task;
}
}
}
My question is will this dispose the registration as desired once the process has ended or been cancelled?
Also is there a better solution to the problem?
I guess also is the CancellationTokenSource disposed after the ASP.NET API request is completed? If that is the case it may not be necessary to clean up the registration.