简体   繁体   中英

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. 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.1256133: Start task 1

00:00:00.1257378: Start task 2

00:00:00.1260298: Start task 3

00:00:00.1261045: Start task 4

00:00:00.1261440: Start task 5

00:00:00.1261740: Start task 6

00:00:00.1262064: Start task 7

00:00:00.1262301: Start task 8

00:00:00.1262523: Start task 9

End program

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

so, while the expected output is "done = true" for all of the calls, some of them received a timeout. 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. 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;

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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