Indeed, it looks like you only need one thread.
Here is the helper class that I created for this kind of thing. Here's how you use it:
class MyPeriodicTasks : PeriodicMultiple
{
protected override TimeSpan FirstInterval { get { return TimeSpan.FromSeconds(30); } }
public MyPeriodicTasks()
{
Tasks = new[] {
new Task { Action = task1, MinInterval = TimeSpan.FromMinutes(5) },
new Task { Action = task2, MinInterval = TimeSpan.FromMinutes(15) },
};
}
private void task1() { }
private void task2() { }
}
Then, to run the tasks:
var tasks = new MyPeriodicTasks();
tasks.Start();
:
tasks.Shutdown();
(, Start backgroundThread: true, Shutdown, -)
:
public abstract class Periodic
{
private Thread _thread;
private CancellationTokenSource _cancellation;
private ManualResetEvent _exited;
protected abstract TimeSpan FirstInterval { get; }
protected abstract TimeSpan SubsequentInterval { get; }
protected abstract void PeriodicActivity();
protected virtual void LastActivity() { }
public bool IsRunning { get { return _cancellation != null && !_cancellation.IsCancellationRequested; } }
public virtual void Start(bool backgroundThread = false)
{
if (_thread != null)
throw new InvalidOperationException(string.Format("\"Start\" called multiple times ({0})", GetType().Name));
_exited = new ManualResetEvent(false);
_cancellation = new CancellationTokenSource();
_thread = new Thread(threadProc) { IsBackground = backgroundThread };
_thread.Start();
}
private volatile bool _periodicActivityRunning = false;
public virtual bool Shutdown(bool waitForExit)
{
if (waitForExit && _periodicActivityRunning && Thread.CurrentThread.ManagedThreadId == _thread.ManagedThreadId)
throw new InvalidOperationException("Cannot call Shutdown(true) from within PeriodicActivity() on the same thread (this would cause a deadlock).");
if (_cancellation == null || _cancellation.IsCancellationRequested)
return false;
_cancellation.Cancel();
if (waitForExit)
_exited.WaitOne();
return true;
}
private void threadProc()
{
try
{
_cancellation.Token.WaitHandle.WaitOne(FirstInterval);
while (!_cancellation.IsCancellationRequested)
{
_periodicActivityRunning = true;
PeriodicActivity();
_periodicActivityRunning = false;
_cancellation.Token.WaitHandle.WaitOne(SubsequentInterval);
}
}
finally
{
try { LastActivity(); }
finally { _exited.Set(); }
}
}
}
public abstract class PeriodicMultiple : Periodic
{
protected sealed class Task
{
public Action Action;
public TimeSpan MinInterval;
public DateTime LastExecuted;
public TimeSpan DelayedBy()
{
if (LastExecuted == default(DateTime))
return TimeSpan.FromDays(1000) - MinInterval;
else
return (DateTime.UtcNow - LastExecuted) - MinInterval;
}
}
protected override TimeSpan SubsequentInterval { get { return TimeSpan.FromSeconds(1); } }
protected IList<Task> Tasks;
protected sealed override void PeriodicActivity()
{
TimeSpan maxDelay = TimeSpan.MinValue;
Task maxDelayTask = null;
foreach (var task in Tasks)
{
var delayedBy = task.DelayedBy();
if (maxDelay < delayedBy && delayedBy > TimeSpan.Zero)
{
maxDelay = delayedBy;
maxDelayTask = task;
}
}
if (maxDelayTask != null)
{
maxDelayTask.LastExecuted = DateTime.UtcNow;
maxDelayTask.Action();
}
}
}
, 1 , , . 1- , , , 15 , 30 ( SubsequentInterval).
, !