Created
June 26, 2012 15:40
-
-
Save aliostad/2996519 to your computer and use it in GitHub Desktop.
A class that creates a dedicated thread to poll
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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