如何在C#中限制函数的执行时间?

20

我有一个问题。我正在编写一个基准测试,并且有一个函数,该函数完成时间有两种情况,一种是2秒钟,另一种是大约5分钟(取决于输入数据)。我想在执行超过3秒钟时停止该函数...

我该怎么做呢?

非常感谢!


是的,这是重复的,基准测试以与任何其他线程停止相同的方式结束。 - Alan Turing
9个回答

64

好的,我也有同样的问题,在阅读了这里和参考博客中的所有答案后,我选择了这个方案:

它允许我在限定时间内执行任何代码块,声明包装方法。

    public static bool ExecuteWithTimeLimit(TimeSpan timeSpan, Action codeBlock)
    {
        try
        {
            Task task = Task.Factory.StartNew(() => codeBlock());
            task.Wait(timeSpan);
            return task.IsCompleted;
        }
        catch (AggregateException ae)
        {
            throw ae.InnerExceptions[0];
        }   
    }

然后使用它来包装任何像这样的代码块

    // code here

    bool Completed = ExecuteWithTimeLimit(TimeSpan.FromMilliseconds(1000), () =>
    {
         //
         // Write your time bounded code here
         // 
    });

    //More code

7
据我理解,如果 action codeBlock 在 timeSpan 结束后仍在运行,这将不会停止它。在您进行多个代码并行运行的基准测试情景中,这可能会极大地改变您的结果。 - Andreas Reiff
2
我认为你需要编写Task task = new Task(codeBlock); task.Wait(timeSpan); task.Start(); return task.IsCompleted;的代码,因为使用你现在的代码,你是在启动方法并告诉它等待x次。但实际上,分配任务、等待并启动任务才是更好的方法。 - Lost_In_Library
完美运行。 - Swapnil Thube

8
最好的方法是让您的函数经常检查其执行时间,以决定在执行时间过长时停止它。
如果不是这种情况,请在单独的线程中运行该函数。在主线程中启动一个3秒计时器。当计时器到期时,使用Thread.Abort()结束单独的线程(当然,除非函数已经完成)。有关函数文档中示例代码和使用注意事项,请参见链接:Thread.Abort()

确实这样要容易得多。如果你真的需要以不友好的方式终止线程,使用线程是我知道的唯一方法。 - gjvdkamp
是的,这似乎是最简单和最直接的方法。谢谢。 - Novellizator

5
private static int LongRunningMethod()
{
    var r = new Random();

    var randomNumber = r.Next(1, 10);

    var delayInMilliseconds = randomNumber * 1000;

    Task.Delay(delayInMilliseconds).Wait();

    return randomNumber;
}

var task = Task.Run(() =>
{
    return LongRunningMethod();
});

bool isCompletedSuccessfully = task.Wait(TimeSpan.FromMilliseconds(3000));

if (isCompletedSuccessfully)
{
    return task.Result;
}
else
{
    throw new TimeoutException("The function has taken longer than the maximum time allowed.");
}

这对我很有用!

来源:https://jeremylindsayni.wordpress.com/2016/05/28/how-to-set-a-maximum-time-to-allow-a-c-function-to-run-for/

4
在C#中停止函数的最好方法是使用函数中的return关键字,但如何知道何时使用return关键字来在至少持续3秒后中断函数?System.DiagnosticsStopwatch类是答案。这个持续时间在2秒到5分钟之间(取决于输入数据)的复杂函数逻辑上使用了许多循环,甚至可能会使用递归,因此我的解决方案是,在该函数的第一行代码中,使用new关键字创建Stopwatch实例,并通过调用Stopwatch类的Start()函数启动它,在每个循环和循环开始时添加以下代码:
if (stopwatch.ElapsedMilliseconds >= 3000) { 
     stopwatch.Stop();
     // or 
     stopwatch.Reset(); 
     return;
 } 

(提示:您可以手动输入一次,复制Ctrl + C,然后只需粘贴Ctrl + V即可)。如果该函数使用递归,则为了节省内存,在代码开头将Stopwatch全局实例化而不是作为本地实例化,并在代码开头开始运行它。您可以使用Stopwatch类的IsRunning来确定这一点。之后询问经过的时间是否超过3秒钟,如果是(true),则停止或重置Stopwatch,并使用return关键字停止递归循环,非常适合使用递归而持续时间较长的函数。就是这样。正如您所看到的,这非常简单,我测试了这种解决方案,结果显示它确实有效!您也可以尝试一下!


最佳答案应该简单明了,直截了当。你的内容很好,但是你应该考虑修改一下。 - WolfLink

3

您可以使用分支/合并模式,在任务并行库中,这是通过Task.WaitAll()实现的。

using System.Threading.Tasks;

void CutoffAfterThreeSeconds() {

    // start function on seperate thread
    CancellationTokenSource cts = new CancellationTokenSource();
    Task loop = Task.Factory.StartNew(() => Loop(cts.Token));

    // wait for max 3 seconds
    if(Task.WaitAll(new Task[]{loop}, 3000)){
       // Loop finished withion 3 seconds
    } else {
       // it did not finish within 3 seconds
       cts.Cancel();           
    }        
}

// this one takes forever
void Loop() {
    while (!ct.IsCancellationRequested) {
        // your loop goes here
    }
    Console.WriteLine("Got Cancelled");
}

这将启动另一个任务在单独的线程上,然后等待3000毫秒以便完成。如果在超时时间内完成,则返回true,否则返回false,所以您可以用它来决定接下来做什么。

您可以使用CancellationToken与其他线程通信,告诉它结果不再需要,以便优雅地停止。

祝好 Gert-Jan


好的,看起来很简单和实用 :) 最后一个问题:我如何以某种方式取消正在运行的Loop()? - Novellizator
这有点取决于你在做什么...它是某种循环(其中可以检查取消标记?)还是调用一些无法更改的其他代码?这决定了策略。如果长时间运行的操作是您的代码,则检查CancellationToken。如果是其他代码,则可能需要不太友好并真正终止线程。但这可能会导致不一致的状态。我将使用取消标记更新示例。 - gjvdkamp
嗨,我没有好好考虑。如果可以更改您正在调用的代码实现,请查看https://dev59.com/KWs05IYBdhLWcg3wJuvp#7413836。如果您无法更改实现,则此方法也不起作用。我会再次更改示例。 - gjvdkamp
TPL 中实际上没有强制取消任务的方法。我想最简单的方法实际上是在你调用的方法内部检查你忙碌了多长时间并在那里取消。否则,使用上述方法并使用取消令牌。 - gjvdkamp
@gjvdkamp 如果循环的执行时间少于3000,你如何获取结果? - Guillermo Varini

1

可以在单独的线程中执行函数,并使用Thread.Join(millisecondsTimeout)限制其执行:

using System.Threading;

Thread workThread = new Thread(DoFunc);
workThread.Start(param);

if (!workThread.Join(3000))
{
    // DoFunc() took longer than 3 seconds. Thread was aborted
}

private void DoFunc(object param)
{
    // do some long work
}

0
在线程中运行此函数,并在3秒后终止它,或者在函数内部检查经过的时间(我认为循环在那里)。

1
这只会将函数转换为一个检查循环,没有其他功能。杀死线程也不是百分之百保证的。它在退出函数之前不会被杀死。 - Maxim V. Pavlov

0
使用操作系统回调和高性能计数器,然后终止线程(如果存在)。

API方式杀死托管线程是否确保即时(在下一个处理器迭代中)终止线程? - Maxim V. Pavlov

-1

因为C#和.net框架不是实时环境,你无法保证哪怕是3秒的计数。 即使你接近这个时间,你仍然需要在方法中的每次调用之前调用if(timeSpan > TimeSpan.FromSeconds(3) then goto endindentifier;

所有这些都是错误的,所以我认为没有可靠的方法可以做到。虽然你可以尝试这个解决方案。

https://web.archive.org/web/20140222210133/http://kossovsky.net/index.php/2009/07/csharp-how-to-limit-method-execution-time

但我不会在 .net 应用程序中做这样的事情。


嗨,Max。微软Windows也不是实时操作系统。 - Alan Turing
您可以使用Win32和Interop在Windows上使用高性能计数器回调。 - Alan Turing
@Artur,我之所以提到.NET而不是Windows,是因为C#也可以通过Mono在基于Unix的操作系统上执行,而我不确定所有支持Mono的操作系统是否都是实时的。可能可以组装一个实时系统,但Mono很可能永远不会是实时的,就像.NET一样,因为目前没有这个必要。 - Maxim V. Pavlov
我们正在谈论将3秒和5分钟之间的差异。这并不是一个实时操作系统的要求! - Serge Wautier
@Serge - 我有点让这个请求悄悄地过去了。然后 Limex 的方法,Kossovsky 先生在他的博客中展示得很好。 - Maxim V. Pavlov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接