cservice
cservice

Reputation: 41

Running task in loop

I have a function which can take 5-60 seconds to run, and I need to run it for every 10 seconds but it should be started only when the previously started function finished running, my code for now is

Action myAction = new Action(() =>
    {
        Debug.WriteLine("just testing");
        Thread.Sleep(15000);
    });
Task myTask = Task.Factory.StartNew(myAction, _cts.Token);
Timer myTimer = new Timer(state =>
    {
        if (myTask.IsCompleted)
        {
            myTask = Task.Factory.StartNew(myAction, _cts.Token);
        }
    }, null, 10000, 10000);

Everything is working fine but I wonder if there is a better solution for my problem? Or is there a possibility to not create a new task (Task.Factory.StartNew) but just using the one used by myTimer?

Upvotes: 3

Views: 3213

Answers (5)

Pablo Montilla
Pablo Montilla

Reputation: 3050

Another possibility, if you are adventurous, would be to use Rx:

Observable.Timer(TimeSpan.FromSeconds(10)).TakeUntilCanceled(cancel).Subscribe(_ => myAction);

Using the TakeUntilCanceled extension:

public static class CancellationTokenXs
{
    public static IObservable<T>
    TakeUntilCanceled<T>(this IObservable<T> source, CancellationToken cancellationToken)
    {
        var subject = new Subject<Unit>();
        cancellationToken.Register(() => subject.OnNext(new Unit()), true);
        return source.TakeUntil(subject);
    }
}

Upvotes: 1

Thebigcheeze
Thebigcheeze

Reputation: 3478

A much better idea would be to, instead of trying to call it every 10 seconds, rely on a callback on task completion, as an example in the following code:

        DateTime sinceExec = DateTime.Now;
        BackgroundWorker bgWorker = new BackgroundWorker();
        bgWorker.DoWork += (bgSender, bgArgs) =>
        {
            sinceExec = DateTime.Now;
            Debug.WriteLine("Test!");
            Thread.Sleep(5000);
        };
        bgWorker.RunWorkerCompleted += (bgSender, bgArgs) =>
        {
            // it didn't take 10000 milliseconds
            if ((DateTime.Now - sinceExec).Milliseconds < 10000) 
            {
                //Calculate time to wait
                TimeSpan timeToWait = (DateTime.Now - sinceExec);

                // wait that amount of time
                Thread.Sleep(timeToWait);
            }
            //Re-execute the worker
            bgWorker.RunWorkerAsync();
        };

        bgWorker.RunWorkerAsync();

The BackgroundWorker class functions such that the event handler DoWork is executed when RunWorkerAsync() is called and RunWorkerCompleted is invoked when DoWork completes.

Upvotes: 0

Chris Pritchard
Chris Pritchard

Reputation: 114

There is a great open source task scheduler called Quartz.net. You can find it at http://quartznet.sourceforge.net/

It supports the specific scenario you mentioned. It is a very robust solution with good extensibility.

Upvotes: 1

Matt Kline
Matt Kline

Reputation: 10477

You can use a lock statement. A lock statement creates a critical section, only one of which can be run at once for a given object.

Use an object both your main thread and your task thread can have access to as the mutex lock. Surrounding both the task function's code and the line that starts the task with the lock statement will accomplish your goal. The task function will acquire the lock and will not release it until it has finished, and the creation function will wait to acquire the lock before it creates another task.

Action myAction = new Action(() =>
{
    lock(this)
    {
        Debug.WriteLine("just testing");
        Thread.Sleep(15000);
    }
});

And in your code that kicks off the action,

lock(myAction)
{
    Task.Factory.StartNew(myAction, _cts.Token)
}

Upvotes: -1

Pablo Montilla
Pablo Montilla

Reputation: 3050

You can use ContinueWith():

Task.Factory.StartNew(myAction, _cts.Token).ContinueWith(_ => myAction);

Look for it's overloads, it has many options to control on which cases to run the continuation.

Upvotes: 4

Related Questions