Skip to content

Instantly share code, notes, and snippets.

@mikaelo
Forked from aliostad/PollingWorker.cs
Created January 26, 2014 20:57
Show Gist options
  • Save mikaelo/8639379 to your computer and use it in GitHub Desktop.
Save mikaelo/8639379 to your computer and use it in GitHub Desktop.
using System.Collections.Generic;
namespace System.Threading
{
public class PollingWorker : IPollingWorker,IDisposable
{
private class TickAction
{
public string Name { get; set; }
public Action Work { get; set; }
public int Every { get; set; }
}
private class IntervalAction
{
public IntervalAction()
{
Last = DateTime.Now;
}
public string Name { get; set; }
public Action Work { get; set; }
public TimeSpan Every { get; set; }
public DateTime Last { get; set; }
}
private const int DefaultIntervalInMillis = 1000; // 1 second
private const int WaitIntervalInMillis = 20; // MS
private Thread _labourer = null;
private int _pollingIntervalInMillis = DefaultIntervalInMillis;
private Action<object> _work = null;
private bool _isWorking = false;
private object _state = new object();
private AutoResetEvent _resetTimer = new AutoResetEvent(false);
private bool _itsTimeToQuite = false;
private bool _poked = false;
private bool _isCurrentlyWorking = false;
private Dictionary<string, TickAction> _tickActions = new Dictionary<string, TickAction>();
private Dictionary<string, IntervalAction> _intervalActions = new Dictionary<string, IntervalAction>();
private long _tickCount = 0;
public string Name
{
get { return _labourer.Name; }
set { _labourer.Name = value; }
}
public PollingWorker(int intervalInMillis, Action work) :
this(intervalInMillis, (object o) => { work(); }, null, Guid.NewGuid().ToString())
{
}
public PollingWorker(int intervalInMillis, Action<object> work, object state) :
this(intervalInMillis, work, state, Guid.NewGuid().ToString())
{
}
public PollingWorker(int intervalInMillis, Action<object> work, object state, string name)
{
_pollingIntervalInMillis = intervalInMillis;
_labourer = new Thread(new ThreadStart(TryDoingSomeWork));
_labourer.Name = name;
_work = work;
}
public int PollingIntervalInMillis
{
get { return _pollingIntervalInMillis; }
set
{
_pollingIntervalInMillis = value;
}
}
public void StartWork()
{
StartWork(true);
}
public void StartWork(bool initialWait)
{
_isWorking = true;
_poked = !initialWait;
_labourer.Start();
}
public void PauseWork()
{
_isWorking = false;
}
public void ContinueWork()
{
_isWorking = true;
}
public void Quit()
{
Quit(int.MaxValue);
}
public int ConvertSecondToTick(int every)
{
return (every * 1000 / PollingIntervalInMillis);
}
public void Quit(int maximumWaitToFinishCurrentWorkMS)
{
int totalWait = 0;
_itsTimeToQuite = true;
_isWorking = false;
_resetTimer.Set();
while (_isCurrentlyWorking && Thread.CurrentThread.Name != _labourer.Name) // in case Quit is called from Work
{
Thread.Sleep(WaitIntervalInMillis);
totalWait += WaitIntervalInMillis;
if (totalWait > maximumWaitToFinishCurrentWorkMS)
break;
}
Dispose();
}
// poke to wake up !
public void Poke()
{
try
{
// if you call set on the same thread while it is not on waitOne
// it does not work
if (Thread.CurrentThread.Name == this.Name)
//_resetTimer.Set();
_poked = true;
else
_resetTimer.Set();
}
catch
{
// ignore any error with poking
}
}
private void TryDoingSomeWork()
{
while (!_itsTimeToQuite)
{
//Debug.WriteLine(string.Format("{0:ss fff}\t{1}\t{2}\t{3}", DateTime.Now, this.Name, string.Empty, string.Empty));
if (!_poked)
_resetTimer.WaitOne(_pollingIntervalInMillis, false);
_poked = false;
// timed-out which means timer's pulse, so do some work
if (_isWorking)
{
_isCurrentlyWorking = true;
_work(_state);
_tickCount++;
ProcessActions();
_isCurrentlyWorking = false;
}
}
}
public void RegisterIntervalAction(TimeSpan every, Action action)
{
RegisterIntervalAction(Guid.NewGuid().ToString(), every, action);
}
public void RegisterIntervalAction(string name, TimeSpan every, Action action)
{
_intervalActions.Add(name, new IntervalAction()
{
Every = every,
Name = name,
Work = action
});
}
public void RegisterTickAction(int every, Action action)
{
RegisterTickAction(Guid.NewGuid().ToString(), every, action);
}
public void RegisterTickAction(string name, int every, Action action)
{
lock (_tickActions)
{
_tickActions.Add(name, new TickAction() { Every = every, Work = action, Name = name });
}
}
public void UpdateTick(string name, int every)
{
lock (_tickActions)
{
_tickActions[name].Every = every;
}
}
public void ClearActions()
{
lock (_tickActions)
{
_tickActions.Clear();
}
lock (_intervalActions)
{
_intervalActions.Clear();
}
}
private void ProcessActions()
{
// ticks
TickAction[] copy;
lock (_tickActions)
{
copy = new TickAction[_tickActions.Count];
_tickActions.Values.CopyTo(copy, 0);
}
foreach (var tickAction in copy)
{
if (_tickCount % tickAction.Every == 0)
tickAction.Work();
}
// intervals
lock (_intervalActions)
{
foreach (var intervalAction in _intervalActions.Values)
{
if(DateTime.Now.Subtract(intervalAction.Last)>intervalAction.Every)
{
intervalAction.Work();
intervalAction.Last = DateTime.Now;
}
}
}
}
public object State
{
get { return _state; }
set
{
lock (_state)
{
_state = value;
}
}
}
public bool IsWorking
{
get { return _isWorking; }
}
#region IDisposable Members
public void Dispose()
{
try
{
_resetTimer.Close();
_labourer.Abort();
}
catch
{
// dont want to raise errors now
// so ignore especially threadabortexception!!
}
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment