简体   繁体   English

如何限制方法的实际执行时间

[英]How to limit the actual EXECUTION time of a method

While there are many examples showing how to impose a timeout on a method using RX, the timeout is on the time the caller is waiting for the method to complete.虽然有很多示例展示了如何使用 RX 对方法施加超时,但超时是调用者等待方法完成的时间。 When there are many tasks on the system the method may get the timeout exception before it even starts running.当系统上有很多任务时,该方法甚至可能在开始运行之前就收到超时异常。

Here is a sample program:这是一个示例程序:

using System;
using System.Diagnostics;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using System.Threading;
using System.Threading.Tasks;

namespace AsyncTasksStuff
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Program program = new Program();
            sw.Start();

            int numOfTasks = 10;

            for (int i = 0; i < numOfTasks; i++)
            {
                program.SyncMethodWrapper(i, 1000);
            }
            Console.WriteLine("End program");
            Console.ReadKey();
        }

        private static Stopwatch sw = new Stopwatch();

        private void SyncMethod(int millis, int id)
        {
            //Console.WriteLine(id + " start");
            Thread.Sleep(millis);
            //Console.WriteLine(id + " end");
        }

        private async void SyncMethodWrapper(int id, int millis)
        {
            Console.WriteLine($"{sw.Elapsed}: Start task {id}");
            bool done = false;

            var task = Task.Run(() =>
            {
                SyncMethod(millis, id);
                done = true;
            }
                );

            try
            {
                await task.ToObservable().Timeout(TimeSpan.FromMilliseconds(millis+100)).ToTask().ConfigureAwait(false);
            }
            catch (Exception)
            {
            }
            Console.WriteLine($"{sw.Elapsed}: ID = {id} done = {done}");
        }
    }
}

and here is a sample output:这是一个示例输出:

00:00:00.0143282: Start task 0 00:00:00.0143282:开始任务0

00:00:00.1256133: Start task 1 00:00:00.1256133:开始任务 1

00:00:00.1257378: Start task 2 00:00:00.1257378:开始任务 2

00:00:00.1260298: Start task 3 00:00:00.1260298:开始任务 3

00:00:00.1261045: Start task 4 00:00:00.1261045:开始任务 4

00:00:00.1261440: Start task 5 00:00:00.1261440:开始任务 5

00:00:00.1261740: Start task 6 00:00:00.1261740:开始任务 6

00:00:00.1262064: Start task 7 00:00:00.1262064:开始任务 7

00:00:00.1262301: Start task 8 00:00:00.1262301:开始任务8

00:00:00.1262523: Start task 9 00:00:00.1262523:开始任务 9

End program结束程序

00:00:01.0564493: ID = 0 done = True 00:00:01.0564493:ID = 0 完成 = 真

00:00:01.1264120: ID = 3 done = True 00:00:01.1264120:ID = 3 完成 = 真

00:00:01.1264120: ID = 1 done = True 00:00:01.1264120:ID = 1 完成 = 真

00:00:01.1264120: ID = 2 done = True 00:00:01.1264120:ID = 2 完成 = 真

00:00:02.0474572: ID = 4 done = True 00:00:02.0474572:ID = 4 完成 = 真

00:00:02.0580636: ID = 9 done = False 00:00:02.0580636:ID = 9 完成 = 假

00:00:02.0588118: ID = 8 done = False 00:00:02.0588118: ID = 8 完成 = 假

00:00:02.0591877: ID = 7 done = False 00:00:02.0591877:ID = 7 完成 = 假

00:00:02.0594568: ID = 6 done = False 00:00:02.0594568:ID = 6 完成 = 假

00:00:02.0597286: ID = 5 done = False 00:00:02.0597286:ID = 5 完成 = 假

so, while the expected output is "done = true" for all of the calls, some of them received a timeout.因此,虽然所有调用的预期输出都是“done = true”,但其中一些调用会收到超时。 If I understand correctly this happens because the Timeout is measured from the time the caller begins waiting (which makes a lot of sense since usually this is what we want to limit).如果我理解正确的话,这是因为超时是从调用者开始等待的时间开始测量的(这很有意义,因为通常这是我们想要限制的)。

However, I would like to limit the actual execution time of the method (the time since the scheduler actually starts executing the task).但是,我想限制该方法的实际执行时间(自调度程序实际开始执行任务以来的时间)。

What is the right way of limiting the actual execution time of a method (without rewriting the synchronous method)?限制方法实际执行时间的正确方法是什么(不重写同步方法)?

What your code is doing is effectively queuing up the execution of the tasks and it sets the .Timeout(TimeSpan.FromMilliseconds(millis + 100)) at the beginning of all of the tasks.您的代码所做的是有效地将任务的执行排队,并在所有任务的开头设置.Timeout(TimeSpan.FromMilliseconds(millis + 100)) So when the tasks accumulate each of the Thread.Sleep(millis);所以当任务累积每个Thread.Sleep(millis); calls, the total time eventually exceeds the timeout and you start getting "done = False" .调用,总时间最终超过超时,你开始得到"done = False"

But what you're really trying to do is "limit the actual EXECUTION time of a method".但是您真正想做的是“限制方法的实际执行时间”。 That's not happening.这不会发生。 There are two ways to limit the execution time of a method.有两种方法可以限制方法的执行时间。 (1) Thread.Abort() BAD! (1) Thread.Abort()糟糕! BAD!糟糕! BAD!糟糕! Never do this.永远不要这样做。 as it can corrupt the run-time state of the .NET framework.因为它会破坏 .NET 框架的运行时状态。 The only time it is OK is if you are trying to crash out of your app.只有当您试图从应用程序中崩溃时才可以。 (2) pass a cancellation token (or equivalent) to the method and have the method watch for a cancellation request. (2) 将取消令牌(或等价物)传递给该方法,并让该方法监视取消请求。

Now, as for the Rx .Timeout method.现在,至于 Rx .Timeout方法。 Take this example:拿这个例子:

Observable
    .Start(() => SomeMethodThatTakesBetween1And3Seconds())
    .Timeout(TimeSpan.FromSeconds(2.0))
    .Subscribe(
        x => Console.WriteLine(x),
        ex => Console.WriteLine(ex.Message),
        () => Console.WriteLine("Done."));

The call to SomeMethodThatTakesBetween1And3Seconds() starts when the subscription is made and the method runs to completion regardless of the timeout .SomeMethodThatTakesBetween1And3Seconds()的调用在进行订阅时开始,并且无论超时如何,该方法都会运行到完成

If the method returns before the timeout then the value x is returned.如果该方法在超时之前返回,则返回值x If it times-out the the the exception is returned as an error - but the SomeMethodThatTakesBetween1And3Seconds() method continues to run to completion.如果超时,则异常将作为错误返回 - 但SomeMethodThatTakesBetween1And3Seconds()方法继续运行直至完成。

Now, just as a side note, you don't have to turn an observable into a task to await it.现在,作为旁注,您不必将 observable 转换为等待它的任务。 Observables are already awaitable. Observables 已经是可等待的。 You can just call await task.ToObservable().Timeout(TimeSpan.FromMilliseconds(millis+100));您可以调用await task.ToObservable().Timeout(TimeSpan.FromMilliseconds(millis+100)); . .


Here's some code that might help you out:以下是一些可能对您有所帮助的代码:

var random = new Random();
var query =
    from i in Observable.Range(0, 100)
    from s in Observable.Start(() =>
        {
            Thread.Sleep(random.Next(0, 1000));
            return i;
        }).Timeout(TimeSpan.FromSeconds(0.25), Observable.Return(-1))
    where s != -1
    select s;

The way I found to force a return from a method after a time period from the actual start of the method is a brute force approach:我发现在方法实际启动后的一段时间后强制从方法返回的方法是一种蛮力方法:

    private async void SyncMethodWrapper(int id, int millis)
    {
        Console.WriteLine($"Start task {id}");
        SemaphoreSlim ss = new SemaphoreSlim(0);
        bool done = false;
        System.Timers.Timer timer = null;

        Stopwatch sw = null;
        var task = Task.Run(() =>
        {
            timer = new System.Timers.Timer {Interval = millis + 100};
            sw = new Stopwatch();
            timer.Elapsed += (sender, args) =>
            {
                try
                {
                    ss.Release(1);
                    Console.WriteLine($"Timeout {id}");
                }
                catch (Exception)
                {
                }
            };
            timer.Start();
            sw.Start();
            Console.WriteLine($"start timer {id}");
            SyncMethod(millis, id);
            done = true;
            ss.Release(1);
            //Console.WriteLine("done");
        }
            );

        await ss.WaitAsync().ConfigureAwait(false);

        Console.WriteLine($"ID = {id} done = {done} elapsed = {sw.Elapsed}");

        ss.Dispose();
        timer.Dispose();
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM