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

时间:2017-06-15 06:57:54

标签: c# system.reactive

虽然有许多示例显示如何使用RX对方法施加超时,但超时是在调用方等待方法完成时的时间。当系统上有许多任务时,该方法可能会在开始运行之前获得超时异常。

以下是一个示例程序:

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}");
        }
    }
}

这是一个示例输出:

  

00:00:00.0143282:启动任务0

     

00:00:00.1256133:启动任务1

     

00:00:00.1257378:启动任务2

     

00:00:00.1260298:启动任务3

     

00:00:00.1261045:启动任务4

     

00:00:00.1261440:启动任务5

     

00:00:00.1261740:启动任务6

     

00:00:00.1262064:启动任务7

     

00:00:00.1262301:启动任务8

     

00:00:00.1262523:启动任务9

     

结束程序

     

00:00:01.0564493:ID = 0 done = True

     

00:00:01.1264120:ID = 3 done = True

     

00:00:01.1264120:ID = 1 done = True

     

00:00:01.1264120:ID = 2 done = True

     

00:00:02.0474572:ID = 4 done = True

     

00:00:02.0580636:ID = 9 done = False

     

00:00:02.0588118:ID = 8 done = False

     

00:00:02.0591877:ID = 7 done = False

     

00:00:02.0594568:ID = 6 done = False

     

00:00:02.0597286:ID = 5 done = False

所以,虽然所有调用的预期输出都是“done = true”,但其中一些会收到超时。如果我理解正确,这是因为超时是从调用者开始等待的时间开始测量的(这很有意义,因为通常这是我们想要限制的)。

但是,我想限制方法的实际执行时间(自调度程序实际开始执行任务以来的时间)。

限制方法实际执行时间的正确方法是什么(不重写同步方法)?

2 个答案:

答案 0 :(得分:1)

What your code is doing is effectively queuing up the execution of the the tasks and it sets the .Timeout(TimeSpan.FromMilliseconds(millis + 100)) at the beginning of all of the tasks. So when the tasks accumulate each of the Thread.Sleep(millis); calls, the total time eventually exceeds the timeout and you start getting "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! BAD! BAD! Never do this. as it can corrupt the run-time state of the .NET framework. 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.

Now, as for the Rx .Timeout method. 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.

If the method returns before the timeout then the value x is returned. If it times-out the the the exception is returned as an error - but the SomeMethodThatTakesBetween1And3Seconds() method continues to run to completion.

Now, just as a side note, you don't have to turn an observable into a task to await it. Observables are already awaitable. You can just call 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;

答案 1 :(得分:0)

我发现在方法实际开始后的一段时间后强制从方法返回的方式是一种蛮力方法:

    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();
    }
}