【问题标题】:Stopping a process using CancellationToken使用 CancellationToken 停止进程
【发布时间】:2016-06-20 21:20:42
【问题描述】:

我正在尝试为从调用线程异步运行的情况启用Kill 选项,例如Process。我的示例看起来像@svick 的答案here。我正在尝试在此post 中实施@svick 的建议。但是当我在 UI 中单击 Kill 时,它似乎什么也没做(即,该过程像往常一样简单地运行到完成)。

根据@TimCopenhaver 的回复,这是意料之中的。但是,如果我将其注释掉,它仍然没有做任何事情,这一次是因为 CancellationTokenSource 对象 cts 为空,这是出乎意料的,因为我在尝试之前在 CaseDispatcher 类的 Dispatch 方法中实例化了它杀死它。这是我的代码 sn-p:

UI类:

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类:

    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类:

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 类(这里是我调用 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;
}

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

【问题讨论】:

  • ctoken.Register 返回一个应该在 Exited 事件处理程序中处理的对象。
  • 这应该基本上可以工作(除了一些可能不会导致您立即出现问题的竞争条件)。在 cts.Cancel 和 process.Kill 上放置断点,看看哪里出错了。平分。
  • 你能显示你的 IsRunning 属性的代码 CaseDispatcher 吗?这是在哪里设置的?
  • @TimCopenhaver:我刚刚包含了访问器。好点,看起来我什至没有进入杀戮。可能是因为此时尚未分配私有任务对象(结果为空),因为批处理显然尚未完成。无论用户何时调用 Kill ,我都会调用 cts.Cancel() ,看看它是否能让我更进一步。
  • @usr:你说得对,我测试它工作正常,现在我的问题主要是外围问题,正如 Tim Copenhaver 所指出的那样

标签: c# asynchronous process kill


【解决方案1】:

我相信 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 属性。

【讨论】:

  • 从我目前了解到的情况来看,对于使用 Process 的批处理,我们必须使用 TaskCompletionSource 从调用线程异步运行,对吗?这意味着如果我们想杀死这样的进程,就没有办法解决这个问题。另外,当我调试它时,我注意到 Kill 方法中的 cts 也是 null ,因此,即使实例化了 cts ,它也没有进入,这对我来说是出乎意料的。有什么想法吗?
  • 您仍然可以取消,但是无法检查正在运行的任务的状态没有解决方法。对于 cts 为空的第二个问题,我怀疑 GetCaseDispatcher 中存在错误。如果 CaseDispatcher 实例未正确缓存,您可能会得到一个没有设置属性的新实例。
  • 我也有同样的怀疑,通过查看代码,看起来你又是对的。我继承的这个框架看起来非常有问题。将尝试修复并报告。
猜你喜欢
  • 2016-03-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-05
  • 1970-01-01
  • 2019-12-13
相关资源
最近更新 更多