使用 CancellationToken 停止进程

Stopping a process using CancellationToken

我正在尝试为从调用线程异步运行的案例启用 Kill 选项,作为 Process。我的示例看起来像@svick 的回答 here. I am trying to implement the recommendation by @svick in this 。但是当我点击 UI 中的 Kill 时,它似乎什么也没做(即,该过程像往常一样简单地运行到完成)。

根据@TimCopenhaver 的回复,这是预期的。但是如果我注释掉它,它仍然没有做任何事情,这次是因为 CancellationTokenSource 对象 cts 是空的,这是意想不到的,因为我在 Dispatch 的方法中实例化了它CaseDispatcher class 在尝试杀死它之前。这是我的代码片段:

UI class:

private void OnKillCase(object sender, EventArgs args)
{
    foreach (var case in Cases)
    {
        Args caseArgs = CaseAPI.GetCaseArguments(case);
        CaseDispatcher dispatcher = CaseAPI.GetCaseDispatcher(case);
        dispatcher.Kill();
        CaseAPI.Dispose(caseArgs); 
    }
}

CaseDispatcher class:

    private Task<bool> task;
    private CancellationTokenSource cts;
    public override bool IsRunning
    {
        get { return task != null && task.Status == TaskStatus.Running; }
    }
    public override void Kill()
    {
        //if (!IsRunning)
        //{
        //    return;
        //}
        if (cts != null)
        {
            cts.Cancel();
        }
    }
    public override async Task<bool> Dispatch()
    {
        cts = new CancellationTokenSource();
        task = CaseAPI.Dispatch(Arguments, cts.Token);
        return await task;
    }

CaseAPI class:

public static async Task<bool> Dispatch(CaseArgs args, CancellationToken ctoken)
{
    bool ok = true;
    BatchEngine engine = new BatchEngine()
        {
            Spec = somespec,
            CaseName = args.CaseName,
            CaseDirectory = args.CaseDirectory
        };
    ok &= await engine.ExecuteAsync(ctoken);
    return ok;
}

BatchEngine class(这里是我调用 CancellationToken Register 方法的地方,但不确定确切的位置,假设它很重要):

public virtual Task<bool> ExecuteAsync(CancellationToken ctoken)
{
    var tcs = new TaskCompletionSource<bool>();
    string exe = Spec.GetExecutablePath();
    string args = string.Format("--input={0} {1}", Input, ConfigFile);

    try
    {
        var process = new Process
        {
            EnableRaisingEvents = true,
            StartInfo =
            {
                UseShellExecute = false,
                FileName = exe,
                Arguments = args,
                CreateNoWindow = true,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                WorkingDirectory = CaseDirectory
            }
        };
        ctoken.Register(() =>
            { 
                process.Kill();
                process.Dispose();
                tcs.SetResult(false);
            });
        process.Exited += (sender, arguments) =>
        {
            if (process.ExitCode != 0)
            {
                string errorMessage = process.StandardError.ReadToEnd();
                tcs.SetResult(false);
                tcs.SetException(new InvalidOperationException("The batch process did not exit correctly. Error message: " + errorMessage));
            }
            else
            {
                File.WriteAllText(LogFile, process.StandardOutput.ReadToEnd());
                tcs.SetResult(true);
            }
            process.Dispose();
        };
        process.Start();
    }
    catch (Exception e)
    {
        Logger.InfoOutputWindow(e.Message);
        tcs.SetResult(false);
        return tcs.Task;
    }
    return tcs.Task;
}

感谢您的关注并感谢您对此的任何想法。

我认为 IsRunning 属性 是问题所在。因为 TaskCompletionSource 并不真正知道您启动了外部进程,所以它停留在 WaitingForActivation 状态。这里有一个简化的例子来演示:

var tsc = new TaskCompletionSource<int>();

Task.Factory.StartNew(() =>
{
    Thread.Sleep(10000);
    tsc.SetResult(10);
});

var tmp = tsc.Task;

TaskStatus status = tmp.Status;
while (status != TaskStatus.RanToCompletion)
{
    status = tmp.Status;
    Thread.Sleep(1000);
    Console.WriteLine(status);
}

注意它会一直说 WaitingForActivation 直到它切换到 RanToCompletion。有关这方面的更多讨论,请参阅 this answer。简而言之,如果任务是由 TaskCompletionSource 创建的,它永远不会进入 运行 状态。您必须自己管理 IsRunning 属性。