使用 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 属性。
我正在尝试为从调用线程异步运行的案例启用 Kill
选项,作为 Process
。我的示例看起来像@svick 的回答 here. I am trying to implement the recommendation by @svick in this 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 属性。