简体   繁体   中英

Disposing a CancellationTokenRegistration registered to kill a Process started in an async method

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM