Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>Really, it sounds like you only need one thread.</p> <p>Here's a helper class that I created for exactly this kind of thing. Here's how you use it:</p> <pre><code>class MyPeriodicTasks : PeriodicMultiple { // The first task will start 30 seconds after this class is instantiated and started: 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() { /* code that gets executed once every 5 minutes */ } private void task2() { /* code that gets executed once every 15 minutes */ } } </code></pre> <p>Then, to start the tasks:</p> <pre><code>var tasks = new MyPeriodicTasks(); tasks.Start(); </code></pre> <p>And during service shutdown:</p> <pre><code>tasks.Shutdown(); </code></pre> <p>(alternatively, call <code>Start</code> with backgroundThread: true, then you don't need to call Shutdown, but then a task may just get terminated right in the middle of doing something)</p> <p>Here's the actual code:</p> <pre><code>/// &lt;summary&gt; /// Encapsulates a class performing a certain activity periodically, which can be initiated once /// and then permanently shut down, but not paused/resumed. The class owns its own separate /// thread, and manages this thread all by itself. The periodic task is executed on this thread. /// &lt;para&gt;The chief differences to &lt;see cref="System.Threading.Timer"/&gt; are as follows. This /// class will never issue overlapping activities, even if an activity takes much longer than the interval; /// the interval is between the end of the previous occurrence of the activity and the start of the next. /// The activity is executed on a foreground thread (by default), and thus will complete once started, /// unless a catastrophic abort occurs. When shutting down the activity, it's possible to wait until the /// last occurrence, if any, has completed fully.&lt;/para&gt; /// &lt;/summary&gt; public abstract class Periodic { private Thread _thread; private CancellationTokenSource _cancellation; private ManualResetEvent _exited; /// &lt;summary&gt; /// Override to indicate how long to wait between the call to &lt;see cref="Start"/&gt; and the first occurrence /// of the periodic activity. /// &lt;/summary&gt; protected abstract TimeSpan FirstInterval { get; } /// &lt;summary&gt; /// Override to indicate how long to wait between second and subsequent occurrences of the periodic activity. /// &lt;/summary&gt; protected abstract TimeSpan SubsequentInterval { get; } /// &lt;summary&gt; /// Override with a method that performs the desired periodic activity. If this method throws an exception /// the thread will terminate, but the &lt;see cref="LastActivity"/&gt; will occur nevertheless. /// &lt;/summary&gt; protected abstract void PeriodicActivity(); /// &lt;summary&gt; /// Override with a method that performs an activity on the same thread as &lt;see cref="PeriodicActivity"/&gt; during /// shutdown, just before signalling that the shutdown is complete. The default implementation of this method /// does nothing. This method is guaranteed to be called during a shutdown, even if the shutdown is due to an /// exception propagating outside of &lt;see cref="PeriodicActivity"/&gt;. /// &lt;/summary&gt; protected virtual void LastActivity() { } /// &lt;summary&gt; /// Returns false before the first call to &lt;see cref="Start"/&gt; and after the first call to &lt;see cref="Shutdown"/&gt;; /// true between them. /// &lt;/summary&gt; public bool IsRunning { get { return _cancellation != null &amp;&amp; !_cancellation.IsCancellationRequested; } } /// &lt;summary&gt; /// Schedules the periodic activity to start occurring. This method may only be called once. /// &lt;/summary&gt; /// &lt;param name="backgroundThread"&gt;By default (false) the class will use a foreground thread, preventing application shutdown until the thread has terminated. If true, a background thread will be created instead.&lt;/param&gt; 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; /// &lt;summary&gt; /// Causes the periodic activity to stop occurring. If called while the activity is being performed, /// will wait until the activity has completed before returning. Ensures that &lt;see cref="IsRunning"/&gt; /// is false once this method returns. /// &lt;/summary&gt; public virtual bool Shutdown(bool waitForExit) { if (waitForExit &amp;&amp; _periodicActivityRunning &amp;&amp; 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(); } } } } /// &lt;summary&gt; /// &lt;para&gt;Encapsulates a class performing multiple related yet independent tasks on the same thread /// at a certain minimum interval each. Schedules the activity that is the most late at every opportunity, /// but will never execute more than one activity at a time (as they all share the same thread).&lt;/para&gt; /// &lt;/summary&gt; public abstract class PeriodicMultiple : Periodic { /// &lt;summary&gt; /// Used to define the activities to be executed periodically. /// &lt;/summary&gt; protected sealed class Task { /// &lt;summary&gt;The activity to be performed.&lt;/summary&gt; public Action Action; /// &lt;summary&gt;The mimimum interval at which this activity should be repeated. May be delayed arbitrarily though.&lt;/summary&gt; public TimeSpan MinInterval; /// &lt;summary&gt;Stores the last time this activity was executed.&lt;/summary&gt; public DateTime LastExecuted; /// &lt;summary&gt;Calculates by how much this activity has been delayed. Is used internally to pick the next activity to run. Returns negative values for activities that aren't due yet.&lt;/summary&gt; public TimeSpan DelayedBy() { if (LastExecuted == default(DateTime)) return TimeSpan.FromDays(1000) - MinInterval; // to run shortest interval first when none of the tasks have ever executed else return (DateTime.UtcNow - LastExecuted) - MinInterval; } } /// &lt;summary&gt;If desired, override to provide a custom interval at which the scheduler /// should re-check whether any activity is due to start. Defaults to 1 second.&lt;/summary&gt; protected override TimeSpan SubsequentInterval { get { return TimeSpan.FromSeconds(1); } } /// &lt;summary&gt;Initialise this with the list of activities to be executed.&lt;/summary&gt; protected IList&lt;Task&gt; Tasks; /// &lt;summary&gt;For internal use.&lt;/summary&gt; protected sealed override void PeriodicActivity() { TimeSpan maxDelay = TimeSpan.MinValue; Task maxDelayTask = null; foreach (var task in Tasks) { var delayedBy = task.DelayedBy(); if (maxDelay &lt; delayedBy &amp;&amp; delayedBy &gt; TimeSpan.Zero) { maxDelay = delayedBy; maxDelayTask = task; } } if (maxDelayTask != null) { maxDelayTask.LastExecuted = DateTime.UtcNow; maxDelayTask.Action(); } } } </code></pre> <p>The thread spends most of the time sleeping, but it does wake up every 1 second to check if a task is due. This 1 second interval is probably too short for intervals like 15 minutes, so reduce it to something like 30 seconds instead (that would be the <code>SubsequentInterval</code>).</p> <p>Hope it's useful!</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload