首页 > 解决方案 > 处置已注册的 CancellationTokenRegistration 以终止以异步方法启动的进程

问题描述

我正在使用一种async方法来启动Process(调用raspistill)。该代码非常标准,但就像这个问题中的开发人员一样:如何通过 CancellationToken 停止异步进程?,我希望能够使用CancellationToken. 例如,我的 ASP.NET 核心 API 端点之一启动了一个需要关闭的 MJPEG 流。

我已经接受了上述问题的答案,并编写了以下 ProcessRunner 类:

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;
        }
    } 
}

我的问题是registration,一旦流程结束或被取消,这是否会根据需要进行处理?

还有没有更好的解决问题的方法?

我想 CancellationTokenSource 也是在 ASP.NET API 请求完成后处理的吗?如果是这种情况,则可能不需要清理注册。

标签: c#async-awaitcancellationcancellation-token

解决方案


我写了一个单元(ish)测试,上面发布的代码似乎按预期工作。当我取消进程并处理注册时,我会看到调试语句。

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();
    }
}

推荐阅读