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.
I wrote a unit(ish) test and it seems the code posted above does work as expected. I see the debug statements when I cancel the process and the registration is disposed.
public class TestProcessRunner
{
[Fact]
public void TestRegistrationDisposedOnCancel()
{
var cts = new CancellationTokenSource();
var token = cts.Token;
string fileName = "ping";
string args = "google.com";
var task = ProcessRunner.RunProcessAsync(fileName, args, token);
cts.Cancel();
task.Wait();
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.