Reputation: 41
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
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
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
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
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
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