任务在指定时间后未取消

问题描述 投票:0回答:1

我有一个 С# 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 控制台应用程序可能需要很长时间?可能是什么原因? 或者也许有一些更好的解决方案来执行和停止长时间运行的任务?

我在互联网上没有找到答案。有人可以帮助我吗?

c#
1个回答
0
投票

我不太喜欢下面的解决方案,它看起来不合理地麻烦,但我之所以这样做是因为当触发超时时,带有令牌的 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 上发现了有关进程冻结的类似问题。他们建议重定向进程输出流,并确保在终止结束时读取它们。但就我而言,该进程已启动,出现在任务管理器中,但实际上并未执行,无论是否重定向输出流。对我来说最大的问题是冻结只发生在某些计算机上,而在大多数计算机上一切正常。

© www.soinside.com 2019 - 2024. All rights reserved.