我有一个 С# Web API 应用程序,它接受请求(例如来自网站)来解决数学问题。由于解决此类问题可能需要很长时间,因此我为它们使用了一个单独的控制台应用程序,该应用程序从 Web api 启动,并通过剪贴板传输数据。此外,我设置了超时(通过 CancellationToken),如果控制台应用程序没有时间完成其工作,我会使用 Kill 函数强制终止它。 这是代码:
// create an instance of the Process object:
using Process prs = new Process();
// fill data:
prs.StartInfo.FileName = processPath;
prs.StartInfo.Arguments = inputJson;
prs.StartInfo.UseShellExecute = false;
prs.StartInfo.RedirectStandardOutput = true;
// start console app which solves math:
prs.Start();
// save moment of time when app start working:
DateTime processStartTime = DateTime.Now;
// create obje - create cancellation token every 15 seconds:
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(10));
CancellationToken tenSecondsToken = cancellationTokenSource.Token;
// asynchronously read data from the stream:
Task<string> result = prs.StandardOutput.ReadToEndAsync(tenSecondsToken);
try
{
// wait for 10 seconds for console app to finish:
await prs.WaitForExitAsync(tenSecondsToken);
// if we got here - its success!
// calculate how long it took to complete the application:
TimeSpan spentTime = DateTime.Now - processStartTime;
// return the result:
return result.Result;
}
catch (Exception exception)
{
// here, the console application did not have time to shut down in 10 seconds,
// and WaitForExitAsync throws an exception:
TimeSpan spentTimeForApplication = DateTime.Now - processStartTime;
// save moment in time before we started "killing" the process:
DateTime processKillStartTime = DateTime.Now;
// kill console app and all its child processes:
prs.Kill(true);
// time to spent to terminate the process:
TimeSpan timeToKill = DateTime.Now - processKillStartTime;
// возвращаем результат:
return string.Empty;
}
我使用 seq 进行日志记录。 登录顺序 WaitForExitAsync 函数运行了 2 分钟(从 09:30:47.949 到 09:32:50.084),而不是 10 秒。 控制台应用程序运行了近 8 分钟(从 09:32:50.084 到 09:40:14.533)。 问题:为什么 CancellationToken 并不总是在设定的时间后触发,并且 Kill 控制台应用程序可能需要很长时间?可能是什么原因? 或者也许有一些更好的解决方案来执行和停止长时间运行的任务?
我在互联网上没有找到答案。有人可以帮助我吗?
我不太喜欢下面的解决方案,它看起来不合理地麻烦,但我之所以这样做是因为当触发超时时,带有令牌的 Process.WaitForExitAsync(CancellationToken) 的标准调用没有被取消。也就是说,我启动进程的主线程冻结了。
最初我以为问题出在外部进程中,但我用 Thread.Sleep(time) 编写了一个简单的控制台应用程序,其中超时大于主代码中的超时。在主代码中,当前没有工作并且没有完成该过程,正如我所期望的那样。这就是这个解决方案出现的原因。在其中,我们在一个单独的线程中等待该进程,该线程只是在后台运行。在主线程中(假设它是一个任务),我们在等待时使用ManualResetEvent将其挂起。在此解决方案中,没有读取流程输出的具体实现,因为有两种情况 - 实现输出中数据出现的事件或重定向输出并在流程结束后读取它们。就我而言,这段代码完全按照我的需要工作 - 要么进程成功结束,要么在 5 分钟后被终止(时间是抽象的,但它比最慢执行的时间长几倍)。
//Creating
using var process = new Process();
process.StartInfo = new ProcessStartInfo(path, arguments)
{
WorkingDirectory = workingDirectory,
//You should decide how will you read data from outpus
//RedirectStandardError = true,
//RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
};
using var manualEvent = new ManualResetEvent(false);
var waitTime = System.TimeSpan.FromMinutes(5);
using var cts = new CancellationTokenSource(waitTime);
try
{
//Starting process...
if (!process.Start())
{
//Process wasn't started
return false;
}
//Process started
using var t = Task.Run(async () =>
{
//Waiting process executed
await process.WaitForExitAsync();
manualEvent.Set();
}).ContinueWith((e) =>
{
if(e.Exception != null)
{
//write to log exception
}
}).WaitAsync(cts.Token);
var wait = manualEvent.WaitOne(waitTime);
if (!wait)
{
process.Kill();
//Process killed after wainting for 5 minutes
//probably you should call [WaitForExit][1], but sometime it redundrant
//if you killed process, there is no resone to read any process outputs
}
if (!t.IsCompleted)
{
//Process stuck on killing?, cancel token...
cts.Cancel(true);
// waiting for task complete to avoid exception from it on dispose, time to wait can be redused
await Task.Delay(1000);
}
//If you redirected outputs, and process wasn't killed, here you reading them
return process.ExitCode == 0;
}
catch (Exception ex)
{
//write to log exception
return false;
}
finally
{
//process ends
}
我在 stackoverflow 上发现了有关进程冻结的类似问题。他们建议重定向进程输出流,并确保在终止结束时读取它们。但就我而言,该进程已启动,出现在任务管理器中,但实际上并未执行,无论是否重定向输出流。对我来说最大的问题是冻结只发生在某些计算机上,而在大多数计算机上一切正常。