I have tried to adapt the async collection from Link to original for UWP. But if I try to build the file, I receive the following error:
Severity Code Description Project File Line Suppression State
Error Cannot determine the item type of collection type 'Logic.Model.AsyncObservableCollection`1[DataAccess.Core.ch.Phex.API.Customer]' because it has more than one Add method or ICollection implementation. To make this collection type usable in XAML, add a public Add(object) method, implement System.Collections.IList or a single System.Collections.Generic.ICollection. Ui.Windows
Can somebody give me a hint?
Best regards
Kaffi
Source Code
public delegate void OnMtCollectionChangedHandler(object sender, NotifyCollectionChangedEventArgs args);
[DataContract]
public class AsyncObservableCollection<T> : ICollection<T>, IReadOnlyList<T>
{
// ******************************************************************
private List<T> _recordedNew = new List<T>();
private List<T> _recordedRemoved = new List<T>();
private bool _isRecording = false;
private readonly object _syncRoot = new object();
protected List<T> List = new List<T>();
private readonly ObservableCollection<T> _obsColl = new ObservableCollection<T>();
private readonly ConcurrentQueue<NotifyCollectionChangedEventArgs> _uiItemQueue = new ConcurrentQueue<NotifyCollectionChangedEventArgs>();
public event OnMtCollectionChangedHandler OnMtCollectionChanged;
public CoreDispatcher Dispatcher { get; set; }
// ******************************************************************
/// <summary>
/// You should never add any item directly in the collection.
/// It should only serve as a readonly collection for the UI.
/// If you ever decide to do so, it would be preferable to use directly the ObsCollection
/// without ever using this class (kind of detach)
/// </summary>
public ObservableCollection<T> ObsColl
{
get { return _obsColl; }
}
// ******************************************************************
public AsyncObservableCollection()
{
//Dispatcher = Application.Current;
Dispatcher = Window.Current.Dispatcher;
}
public AsyncObservableCollection(Collection<T> collection)
{
//Dispatcher = Application.Current;
Dispatcher = Window.Current.Dispatcher;
this.Add(collection.ToList<T>());
}
// ******************************************************************
public bool IsRecording
{
get { return _isRecording; }
set { _isRecording = value; }
}
// ******************************************************************
/// <summary>
/// Return tuple of new and removed items
/// </summary>
/// <returns></returns>
public Tuple<List<T>, List<T>> ResetRecordedItems()
{
Tuple<List<T>, List<T>> changes;
lock (_syncRoot)
{
changes = new Tuple<List<T>, List<T>>(_recordedNew, _recordedRemoved);
_recordedNew = new List<T>();
_recordedRemoved = new List<T>();
}
return changes;
}
// ******************************************************************
public T[] GetCopyOfRecordedItemsNew()
{
T[] changes;
lock (_syncRoot)
{
changes = _recordedNew.ToArray();
}
return changes;
}
// ******************************************************************
public T[] GetCopyOfRecordedItemsRemoved()
{
T[] changes;
lock (_syncRoot)
{
changes = _recordedRemoved.ToArray();
}
return changes;
}
// ******************************************************************
private async void AddTask(NotifyCollectionChangedEventArgs args)
{
_uiItemQueue.Enqueue(args);
// Dispatcher.BeginInvoke(new Action(this.ProcessQueue));
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
this.ProcessQueue();
});
}
// ******************************************************************
private void ProcessQueue()
{
// This Method should always be invoked only by the UI thread only.
if (!this.Dispatcher.HasThreadAccess)
{
throw new Exception("Can't be called from any thread than the dispatcher one");
}
NotifyCollectionChangedEventArgs args;
while (this._uiItemQueue.TryDequeue(out args))
{
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
int offset = 0;
foreach (T item in args.NewItems)
{
ObsColl.Insert(args.NewStartingIndex + offset, item);
offset++;
}
break;
case NotifyCollectionChangedAction.Remove:
if (args.NewStartingIndex >= 0)
{
ObsColl.RemoveAt(args.NewStartingIndex);
}
else
{
foreach (T item in args.OldItems)
{
ObsColl.Remove(item);
}
}
break;
case NotifyCollectionChangedAction.Replace:
// Replace is used for the [] operator. 'Insert' raise an 'Add' event.
if (args.NewStartingIndex >= 0 && args.OldStartingIndex < 0)
{
throw new ArgumentException(String.Format("Replace action expect NewStartingIndex and OldStartingIndex as: 0 <= {0} <= {1}, {2} <= 0.", args.NewStartingIndex, ObsColl.Count, args.OldStartingIndex));
}
IList listOld = args.OldItems as IList;
IList listNew = args.NewItems as IList;
if (listOld == null || listNew == null)
{
throw new ArgumentException("Both argument Old and New item should be IList in a replace action.");
}
ObsColl[args.NewStartingIndex] = (T)listNew[0];
break;
case NotifyCollectionChangedAction.Reset:
ObsColl.Clear();
break;
case NotifyCollectionChangedAction.Move:
ObsColl.Move(args.OldStartingIndex, args.NewStartingIndex);
break;
default:
throw new Exception("Unsupported NotifyCollectionChangedEventArgs.Action");
}
}
}
// ******************************************************************
public List<T> GetSnapshot()
{
List<T> listCopy = null;
lock (_syncRoot)
{
listCopy = new List<T>(List);
}
return listCopy;
}
// ******************************************************************
public void GetSnapshot(IList list)
{
lock (_syncRoot)
{
List.ApplyForEachItem((path) => list.Add(path));
}
}
// ******************************************************************
public virtual IEnumerator<T> GetEnumerator()
{
return GetSnapshot().GetEnumerator();
}
// ******************************************************************
public virtual IEnumerator<T> GetBlockingEnumerator()
{
return new BlockingIterator<T>(List.GetEnumerator(), _syncRoot);
}
// ******************************************************************
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetSnapshot().GetEnumerator();
}
// ******************************************************************
public void InsertAsFirst(T item)
{
NotifyCollectionChangedEventArgs args;
lock (_syncRoot)
{
List.Insert(0, item);
args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, 0);
AddTask(args);
}
RaiseEventCollectionChanged(args);
}
// ******************************************************************
public void Add(T item)
{
NotifyCollectionChangedEventArgs args;
lock (_syncRoot)
{
List.Add(item);
if (_isRecording)
{
_recordedNew.Add(item);
}
args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, List.Count - 1);
AddTask(args);
}
RaiseEventCollectionChanged(args);
}
public void Add(IList<T> items)
{
NotifyCollectionChangedEventArgs args;
lock (_syncRoot)
{
int insertIndex = List.Count;
List.AddRange(items);
if (_isRecording)
{
_recordedNew.AddRange(items);
}
args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, items as IList, insertIndex);
AddTask(args);
}
RaiseEventCollectionChanged(args);
}
// ******************************************************************
public bool Remove(T item)
{
bool isRemoved = false;
NotifyCollectionChangedEventArgs args;
lock (_syncRoot)
{
isRemoved = List.Remove(item);
if (_isRecording)
{
_recordedNew.Add(item);
}
args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item);
AddTask(args);
}
RaiseEventCollectionChanged(args);
return isRemoved;
}
// ******************************************************************
public void Replace(T itemOld, T itemNew)
{
NotifyCollectionChangedEventArgs args = null;
lock (_syncRoot)
{
int index = List.IndexOf(itemOld);
if (index < 0 || index >= List.Count)
{
throw new ArgumentException("Invalid old value");
}
if (_isRecording)
{
_recordedNew.Add(itemNew);
_recordedRemoved.Add(itemOld);
}
List[index] = itemNew;
args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, itemNew, itemOld, index);
AddTask(args);
}
RaiseEventCollectionChanged(args);
}
// ******************************************************************
private void RaiseEventCollectionChanged(NotifyCollectionChangedEventArgs args)
{
if (OnMtCollectionChanged != null && args != null)
{
OnMtCollectionChanged(this, args);
}
}
// ******************************************************************
/// <summary>
/// To use this function and all 'Unsafe' ones in a MT context,
/// you should have a lock on the collection prior to call it.
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public T UnsafeGetAt(int index)
{
return List[index];
}
// ******************************************************************
/// <summary>
/// To use this function and all 'Unsafe' ones in a MT context,
/// you should have a lock on the collection prior to call it.
/// </summary>
/// <param name="index"></param>
/// <param name="item"></param>
/// <returns></returns>
public T UnsafeSetAt(int index, T itemNew)
{
T itemOld = List[index];
if (_isRecording)
{
_recordedNew.Add(itemNew);
_recordedRemoved.Add(itemOld);
}
List[index] = itemNew;
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, itemNew, itemOld, index);
AddTask(args);
RaiseEventCollectionChanged(args);
return itemOld;
}
// ******************************************************************
public void UnsafeInsertAt(int index, T itemNew)
{
if (_isRecording)
{
_recordedNew.Add(itemNew);
}
List.Insert(index, itemNew);
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, itemNew, index);
AddTask(args);
RaiseEventCollectionChanged(args);
}
// ******************************************************************
/// <summary>
/// To use this function and all 'Unsafe' ones in a MT context,
/// you should have a lock on the collection prior to call it.
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public T UnsafeRemoveAt(int index)
{
T itemOld = List[index];
if (_isRecording)
{
_recordedRemoved.Add(itemOld);
}
List.RemoveAt(index);
NotifyCollectionChangedEventArgs args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, itemOld, index);
AddTask(args);
RaiseEventCollectionChanged(args);
return itemOld;
}
// ******************************************************************
public virtual void Clear()
{
NotifyCollectionChangedEventArgs args = null;
lock (_syncRoot)
{
if (_isRecording)
{
_recordedRemoved.AddRange(List);
}
List.Clear();
args = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
AddTask(args);
}
RaiseEventCollectionChanged(args);
}
// ******************************************************************
public bool Contains(T item)
{
bool result;
lock (_syncRoot)
{
result = List.Contains(item);
}
return result;
}
// ******************************************************************
public void CopyTo(T[] array, int arrayIndex)
{
lock (_syncRoot)
{
List.CopyTo(array, arrayIndex);
}
}
// ******************************************************************
public int Count
{
get
{
lock (_syncRoot)
{
return List.Count;
}
}
}
// ******************************************************************
public void Remove(object item)
{
Remove((T)item);
}
// ******************************************************************
public int IndexOf(object value)
{
return IndexOf((T)value);
}
// ******************************************************************
public object SyncRoot
{
get { return _syncRoot; }
}
// ******************************************************************
public bool IsEqual(IEnumerable<T> iEnumerable)
{
if (this.Count != iEnumerable.Count())
{
return false;
}
lock (_syncRoot)
{
var thisEnumerator = this.GetEnumerator();
thisEnumerator.Reset();
foreach (var t in iEnumerable)
{
thisEnumerator.MoveNext();
if (thisEnumerator.Current.Equals(t))
{
return false;
}
}
Disposal.Dispose(thisEnumerator);
}
return true;
}
// ******************************************************************
private void IsEqualToObsColl()
{
if (!IsEqual(this.ObsColl))
{
Dump();
}
}
// ******************************************************************
/// <summary>
/// This function dumps to the ouput window formated lines of the content of both collections...
/// The list which is thread safe and the obs coll that is used as a readonly list.
/// Its main purpose is to debug to validate that both list contains the same values in the same order.
/// </summary>
private void Dump()
{
Debug.WriteLine("=============== Start");
lock (_syncRoot)
{
IEnumerator enum1 = List.GetEnumerator();
IEnumerator enum2 = ObsColl.GetEnumerator();
enum1.Reset();
enum2.Reset();
bool ok1 = enum1.MoveNext();
bool ok2 = enum2.MoveNext();
while (ok1 || ok2)
{
Debug.WriteLine(String.Format("{0,20} - {0,-20}", ok1 == true ? enum1.Current : "-", ok2 == true ? enum2.Current : "-"));
if (ok1)
ok1 = enum1.MoveNext();
if (ok2)
ok2 = enum2.MoveNext();
}
Disposal.Dispose(enum1);
Disposal.Dispose(enum2);
}
Debug.WriteLine("=============== End");
}
// ******************************************************************
[OnSerializing]
void OnSerializing(StreamingContext ctx)
{
Monitor.Enter(this._syncRoot);
}
// ******************************************************************
[OnSerialized]
void OnSerialized(StreamingContext ctx)
{
Monitor.Exit(this._syncRoot);
}
// ******************************************************************
[OnDeserializing]
void OnDeserializing(StreamingContext ctx)
{
}
// ******************************************************************
[OnDeserialized]
void OnDeserialized(StreamingContext ctx)
{
}
// ******************************************************************
/// <summary>
/// ATTENTION : This method is not MT safe
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public T this[int index]
{
get { return this.List[index]; }
}
// ******************************************************************
/// <summary>
/// Add stack functionnality to use the list as a queue
/// </summary>
/// <param name="item"></param>
public void Push(T item)
{
Add(item);
}
// ******************************************************************
/// <summary>
/// Add stack functionnality to use the list as a queue
/// </summary>
/// <returns></returns>
public bool TryPop(out T item)
{
lock (_syncRoot)
{
int count = List.Count;
if (count > 0)
{
item = UnsafeRemoveAt(count - 1);
return true;
}
}
item = default(T);
return false;
}
// ******************************************************************
/// <summary>
/// Add queue functionnality to use the list as a queue. Item are added at the end of the list
/// </summary>
/// <param name="item"></param>
public void Enqueue(T item)
{
Add(item);
}
// ******************************************************************
/// <summary>
/// Add queue functionnality to use the list as a queue. Item are removed at position 0 (cost a lot due to move all array item left from one position)
/// </summary>
/// <returns></returns>
public bool TryDequeue(out T item)
{
lock (_syncRoot)
{
int count = List.Count;
if (count > 0)
{
item = UnsafeRemoveAt(0);
return true;
}
}
item = default(T);
return false;
}
// ******************************************************************
public bool IsReadOnly
{
get { return false; }
}
// ******************************************************************
bool ICollection<T>.Remove(T item)
{
return Remove(item);
}
// ******************************************************************
public void CopyTo(Array array, int index)
{
lock (_syncRoot)
{
foreach (var t in List)
{
array.SetValue(t, index++);
}
}
}
// ******************************************************************
public bool IsSynchronized
{
get { return Dispatcher.HasThreadAccess; }
}
// ******************************************************************
}
Well you're missing a few things from the code sample so I couldn't compile it (Disposal and ApplyForEachItem), but I can see that you have an public void Add(T item) and a public void Add(IList<T> items) method as well. The ICollection<T> interface implementation only requires the first one - for the second one the usual naming convention is AddRange.
Related
I have a method in a class that receives and returns multiple parameters from/to Form1.
I need to use a timed event to execute some code using those parameters.
I have arranged this simplified code to show the dynamic:
class Motor
{
public static System.Timers.Timer _timer;
int valReg = 30;
public void PID(decimal _actualSpeed, Decimal _speedRequest, out Decimal _pwmAuto, out decimal _preValReg)
{
_timer = new System.Timers.Timer();
_timer.Interval = (3000);
_timer.Elapsed += new System.Timers.ElapsedEventHandler(_timerAutoset);
_timer.Enabled = true;
// {....}
_pwmAuto = valReg;
_preValReg = valReg - 1;
}
static void _timerAutoset(object sender, System.Timers.ElapsedEventArgs e)
{
/* here I need to work with:
_actualSpeed
_speedRequest
_pwmAuto
_preValReg
and send back the last two variables
*/
}
}
This is how I pass and receive the variables from Form1 button :
private void button4_Click(object sender, EventArgs e)
{
// some code ................
Motor mtr = new Motor();
mtr.PID(speedRequest, actualSpeed, out pwmAuto, out xxx);
//..more code
How can I pass/get back those parameters to/from _timerAutoset event?
I tend to solve this problem using anonymous delegates.
public void PID(decimal _actualSpeed, Decimal _speedRequest, out Decimal _pwmAuto, out decimal _preValReg)
{
_pwmAuto = valReg;
_preValReg = valReg - 1;
// Because we cannot use [out] variables inside the anonymous degegates,
// we make a value copy
Decimal pwmAutoLocal = _pwmAuto;
Decimal preValRegLocal = _preValReg;
_timer = new System.Timers.Timer();
_timer.Interval = (3000);
_timer.Elapsed += (sender, e) => { HandleTimerElapsed(_actualSpeed, _speedRequst, pwmAutoLocal, preValRegLocal); };
_timer.Enabled = true;
// {....}
}
static void HandleTimerElapsed(Decimal actualSpeed, Decimal speedRequst, Decimal pwmAuto, Decimal preValReg)
{
// (...)
}
(You have to be mindful when the delegate accesses local variables from the enclosing block. Double-check the code to ensure the values stored in those variables will not change between the assignment of the event handler and the invocation of this handler).
It seems these parameters are coming from somewhere else. One approach could be to pass a callback via delegate and use it to get the updated values from.
Another approach will be to make a class and pass it to Motor's constructor and use its reference in the _timerAutoset to get the updated values.
Using Delegates:
class Motor
{
public static System.Timers.Timer _timer;
int valReg = 30;
public delegate TimerParam ParameterizedTimerDelegate();
public static ParameterizedTimerDelegate TimerCallback { get; set; }
public void PID()
{
_timer = new System.Timers.Timer();
_timer.Interval = (3000);
_timer.Elapsed += new System.Timers.ElapsedEventHandler(_timerAutoset);
_timer.Enabled = true;
// {....}
//Param.PwmAuto = valReg;
//Param.PreValReg = valReg - 1;
}
static void _timerAutoset(object sender, System.Timers.ElapsedEventArgs e)
{
TimerParam param = TimerCallback();
/* here you can use:
Param.ActualSpeed
Param.SpeedRequest
Param.PwmAuto
Param.PreValReg
*/
}
}
Using a shared instance:
class TimerParam
{
public decimal ActualSpeed { get; set; }
public decimal SpeedRequest { get; set; }
public Decimal PwmAuto { get; set; }
public decimal PreValReg { get; set; }
}
class Motor
{
public static System.Timers.Timer _timer;
int valReg = 30;
public TimerParam Param { get; set; }
public void PID(TimerParam param)
{
Param = param;
_timer = new System.Timers.Timer();
_timer.Interval = (3000);
_timer.Elapsed += new System.Timers.ElapsedEventHandler(_timerAutoset);
_timer.Enabled = true;
// {....}
Param.PwmAuto = valReg;
Param.PreValReg = valReg - 1;
}
static void _timerAutoset(object sender, System.Timers.ElapsedEventArgs e)
{
/* here you can use:
Param.ActualSpeed
Param.SpeedRequest
Param.PwmAuto
Param.PreValReg
*/
}
}
You can then update the instance of TimerParam that you passed to the Motor class and timer will always get the updated values.
you could try using lambda expression for inserting additional arguement..
_timer.Elapsed += (sender, e) => _timerAutoset(sender, e, _actualSpeed,_speedRequest);
your method be like
static void _timerAutoset(object sender, System.Timers.ElapsedEventArgs e,decimal speed,decimal speedRequest)
You just could initialize them in your class, so all methods could access them...
private void StartTimerForDeleteMessage(UC_ChatReceiveMessageControl ucChatReceiveMessageControl)
{
try
{
System.Timers.Timer aTimer = new System.Timers.Timer();
aTimer.Elapsed += (sender, e) => MyElapsedMethod(sender, e, ucChatReceiveMessageControl);
aTimer.Interval = 1000;
aTimer.Enabled = true;
}
catch (Exception ex)
{
Helper.WriteToLogFile("SetMessageBodyContentAfterAcknoledged ex::" + ex.Message, LoggingLevel.Errors);
}
}
static void MyElapsedMethod(object sender, ElapsedEventArgs e, UC_ChatReceiveMessageControl ucChatReceiveMessageControl)
{
try
{
}
catch (Exception ex)
{
Helper.WriteToLogFile("SetMessageBodyContentAfterAcknoledged ex::" + ex.Message, LoggingLevel.Errors);
}
}
I'm using a Backgroundworker styled class called "ScheduledWorker" which executes a recurring operation on a separate thread and returns to the main thread after each execution of this background operation.
For data exchange an object variable can be passed to the background operation when starting the ScheduledWorker and can also be changed while the ScheduledWorker is running. Inside the background procedure this object can be called via DoScheduledWorkEventArgs.Argument. The time when the DoWork event was raised can be called via DoScheduledWorkEventArgs.SignalTime property. The way ScheduledWorker reports result and progress of the background operation to the main thread is the same as the BackgroundWorker class.
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Threading;
namespace ScheduledWorker
{
/// <summary>
/// Executes a recurring operation on a separate thread.
/// </summary>
[DefaultEvent("DoWork")]
[HostProtection(SharedState = true)]
public partial class ScheduledWorker : Component, ISupportInitialize
{
private bool enabled;
private bool delayedEnable;
private bool initializing;
private bool disposed;
private readonly ManualResetEvent doNotDisposeWaitHandle = new ManualResetEvent(false);
private int disposeWaitMSec;
private bool cancellationPending;
private bool isRunning;
private bool isOccupied;
private bool isWorking;
private object argument;
private readonly object statusChangeLockObject = new object();
private readonly object doWorkKey = new object();
private readonly object runWorkerCompletedKey = new object();
private readonly object progressChangedKey = new object();
private readonly EventHandler<DoScheduledWorkEventArgs> workHandler;
private readonly SendOrPostCallback completedCallback;
private readonly SendOrPostCallback progressCallback;
private AsyncOperation mainThreadOperation;
private Timer timer;
private double interval;
/// <summary>
/// Initializes a new instance of the ScheduledWorker class and sets the <see cref="ScheduledWorker.Interval"/> property to 100 milliseconds.
/// </summary>
public ScheduledWorker() : this(100, -1) { }
/// <summary>
/// Initializes a new instance of the ScheduledWorker class, and sets the <see cref="ScheduledWorker.Interval"/> property to the specified number of milliseconds.
/// </summary>
/// <param name="interval">The time, in milliseconds, between events. The value must be greater than zero and less than or equal to <see cref="int.MaxValue"/>."/></param>
public ScheduledWorker(double interval, int disposeWaitMSec) : base()
{
this.interval = interval;
this.disposeWaitMSec = disposeWaitMSec;
completedCallback = new SendOrPostCallback(AsynOperationCompleted);
progressCallback = new SendOrPostCallback(ProgressReporter);
initializing = false;
delayedEnable = false;
workHandler = new EventHandler<DoScheduledWorkEventArgs>(WorkerThreadStart);
}
/// <summary>
/// Occurs when <see cref="ScheduledWorker.RunWorkerAsync"/> or <see cref="ScheduledWorker.RunWorkerAsync(object)"/> are called.
/// </summary>
public event EventHandler<DoScheduledWorkEventArgs> DoWork
{
add
{
Events.AddHandler(doWorkKey, value);
}
remove
{
Events.RemoveHandler(doWorkKey, value);
}
}
/// <summary>
/// Occurs when the background operation has completed, has been canceled, or has raised an exception.
/// </summary>
public event EventHandler<RunWorkerCompletedEventArgs> RunWorkerCompleted
{
add
{
Events.AddHandler(runWorkerCompletedKey, value);
}
remove
{
Events.RemoveHandler(runWorkerCompletedKey, value);
}
}
/// <summary>
/// Occurs when <see cref="ScheduledWorker.ReportProgress(int)"/> or <see cref="ScheduledWorker.ReportProgress(int, object)"/> are called.
/// </summary>
public event EventHandler<ProgressChangedEventArgs> ProgressChanged
{
add
{
Events.AddHandler(progressChangedKey, value);
}
remove
{
Events.RemoveHandler(progressChangedKey, value);
}
}
/// <summary>
/// Starts raising the <see cref="ScheduledWorker.DoWork"/> event by setting Enabled to true.
/// </summary>
public void RunWorkerAsync()
{
RunWorkerAsync(null);
}
/// <summary>
/// Starts raising the <see cref="ScheduledWorker.DoWork"/> event by setting Enabled to true.
/// </summary>
/// <param name="argument">A parameter for use by the background operation to be executed in the <see cref="ScheduledWorker.DoWork"/> event handler.</param>
public void RunWorkerAsync(object argument)
{
Argument = argument;
Enabled = true;
}
/// <summary>
/// Stops raising the <see cref="ScheduledWorker.DoWork"/> event by setting Enabled to false.
/// </summary>
public void Stop()
{
Enabled = false;
}
/// <summary>
/// Gets or sets a value indicating whether the <see cref="ScheduledWorker.DoWork"/> event should be raised.
/// </summary>
[Category("Behavior")]
public bool Enabled
{
get
{
lock (statusChangeLockObject)
{
return enabled;
}
}
set
{
if (DesignMode)
{
delayedEnable = value;
enabled = value;
}
else if (initializing)
{
delayedEnable = value;
}
else if (enabled != value)
{
lock (statusChangeLockObject)
{
if (!value)
{
if (timer != null)
{
timer.Dispose();
timer = null;
}
enabled = false;
if (!isWorking)
{
if (!isOccupied)
{
isRunning = false;
}
SetMainThreadOperationCompleted();
}
}
else
{
enabled = true;
if (timer == null && !isRunning)
{
if (disposed)
{
throw new ObjectDisposedException(GetType().Name);
}
else
{
int roundedInterval = Convert.ToInt32(Math.Ceiling(interval));
isRunning = true;
isOccupied = false;
isWorking = false;
cancellationPending = false;
SetMainThreadOperationCompleted();
mainThreadOperation = AsyncOperationManager.CreateOperation(null);
timer = new Timer(MyTimerCallback, null, roundedInterval, roundedInterval);
}
}
else if (isRunning)
{
throw new InvalidOperationException("ScheduledWorker is busy.");
}
else
{
UpdateTimer();
}
}
}
}
}
}
/// <summary>
/// Gets or sets the interval, expressed in milliseconds, at which to raise the <see cref="ScheduledWorker.DoWork"/> event.
/// It can be changed while the ScheduledWorker is running.
/// </summary>
[Category("Behavior"), DefaultValue(100d), SettingsBindable(true)]
public double Interval
{
get
{
return interval;
}
set
{
if (value <= 0)
{
throw new ArgumentException("Minimum interval is 1.");
}
else
{
interval = value;
if (timer != null)
{
UpdateTimer();
}
}
}
}
/// <summary>
/// Gets or sets a value indicating whether the ScheuledWorker can report progress updates.
/// </summary>
[DefaultValue(false)]
public bool WorkerReportsProgress { get; set; }
/// <summary>
/// Raises the ProgressChanged event.
/// </summary>
/// <param name="percentProgress">The percentage, from 0 to 100, of the background operation that is complete.</param>
public void ReportProgress(int percentProgress)
{
ReportProgress(percentProgress, null);
}
/// <summary>
/// Raises the ProgressChanged event.
/// </summary>
/// <param name="percentProgress">The percentage, from 0 to 100, of the background operation that is complete.</param>
/// <param name="userState">The state object passed to <see cref="ScheduledWorker.RunWorkerAsync(object)"/>.</param>
public void ReportProgress(int percentProgress, object userState)
{
if (!WorkerReportsProgress)
{
throw new InvalidOperationException("This ScheduledWorker does not support reporting progress.");
}
else
{
mainThreadOperation.Post(progressCallback, new ProgressChangedEventArgs(percentProgress, userState));
}
}
/// <summary>
/// Gets or sets a value indicating whether the ScheduledWorker supports asynchronous cancellation.
/// </summary>
[DefaultValue(false)]
public bool WorkerSupportsCancellation { get; set; }
/// <summary>
/// Gets a value indicating whether the application has requested cancellation of a background operation.
/// </summary>
[Browsable(false)]
public bool CancellationPending
{
get
{
lock (statusChangeLockObject)
{
return cancellationPending;
}
}
}
/// <summary>
/// Requests cancellation of a pending background operation.
/// </summary>
public void CancelAsync()
{
if (!WorkerSupportsCancellation)
{
throw new InvalidOperationException("This ScheduledWorker does not support cancellation.");
}
else
{
lock (statusChangeLockObject)
{
cancellationPending = true;
Stop();
}
}
}
/// <summary>
/// Gets a value indicating whether the ScheduledWorker is running an asynchronous operation. This is the case until the SchedeuledWorker has been stopped (<see cref="ScheduledWorker.Enabled"/> = false)
/// and the last <see cref="ScheduledWorker.DoWork"/> event has completed.
/// </summary>
[Browsable(false)]
public bool IsBusy
{
get
{
lock (statusChangeLockObject)
{
return isRunning;
}
}
}
/// <summary>
/// A parameter for use by the background operation to be executed in the <see cref="ScheduledWorker.DoWork"/> event handler.
/// It can be changed while the ScheduledWorker is running.
/// </summary>
[Browsable(false)]
public object Argument
{
get
{
return Interlocked.Exchange(ref argument, argument);
}
set
{
Interlocked.Exchange(ref argument, value);
}
}
/// <summary>
/// Begins the run-time initialization of a ScheduledWorker that is used on a form or by another component.
/// </summary>
public void BeginInit()
{
Close();
initializing = true;
}
/// <summary>
/// Ends the run-time initialization of a ScheduledWorker that is used on a form or by another component.
/// </summary>
public void EndInit()
{
initializing = false;
enabled = delayedEnable;
}
private void MyTimerCallback(object state)
{
lock (statusChangeLockObject)
{
try
{
if (enabled && !isOccupied)
{
doNotDisposeWaitHandle.Reset();
isOccupied = true;
isWorking = true;
FILE_TIME fileTime = new FILE_TIME();
SafeNativeMethods.GetSystemTimeAsFileTime(ref fileTime);
workHandler.BeginInvoke(this,
new DoScheduledWorkEventArgs(Argument,
DateTime.FromFileTime((long)((((ulong)fileTime.ftTimeHigh) << 32) | (((ulong)fileTime.ftTimeLow) & 0xffffffff)))),
null,
null);
}
}
catch { }
}
}
private void WorkerThreadStart(object sender, DoScheduledWorkEventArgs args)
{
Exception Error = null;
try
{
if (CancellationPending)
{
args.Cancel = true;
}
else
{
OnDoWork(args);
}
if (args.Cancel)
{
args.Result = null;
cancellationPending = true;
}
}
catch (Exception ex)
{
Error = ex;
args.Result = null;
}
finally
{
mainThreadOperation.Post(completedCallback, new RunWorkerCompletedEventArgs(args.Result, Error, args.Cancel));
doNotDisposeWaitHandle.Set();
}
}
protected void OnDoWork(DoScheduledWorkEventArgs args)
{
((EventHandler<DoScheduledWorkEventArgs>)Events[doWorkKey])?.Invoke(this, args);
}
private void AsynOperationCompleted(object args)
{
lock (statusChangeLockObject)
{
isWorking = false;
if (!enabled)
{
isRunning = false;
SetMainThreadOperationCompleted();
}
}
OnRunWorkerCompleted((RunWorkerCompletedEventArgs)args);
lock (statusChangeLockObject)
{
isOccupied = false;
if (!enabled)
{
isRunning = false;
SetMainThreadOperationCompleted();
}
}
}
protected void OnRunWorkerCompleted(RunWorkerCompletedEventArgs args)
{
((EventHandler<RunWorkerCompletedEventArgs>)Events[runWorkerCompletedKey])?.Invoke(this, args);
}
private void SetMainThreadOperationCompleted()
{
if (mainThreadOperation != null)
{
mainThreadOperation.OperationCompleted();
mainThreadOperation = null;
}
}
private void ProgressReporter(object arg)
{
OnProgressChanged((ProgressChangedEventArgs)arg);
}
protected void OnProgressChanged(ProgressChangedEventArgs args)
{
((EventHandler<ProgressChangedEventArgs>)Events[progressChangedKey])?.Invoke(this, args);
}
private void UpdateTimer()
{
int roundedInterval = Convert.ToInt32(Math.Ceiling(interval));
timer.Change(roundedInterval, roundedInterval);
}
protected override void Dispose(bool disposing)
{
disposed = true;
Close();
base.Dispose(disposing);
}
public void Close()
{
if (timer != null)
{
timer.Change(Timeout.Infinite, Timeout.Infinite);
using (ManualResetEvent disposeWaitHandle = new ManualResetEvent(false))
{
if (timer.Dispose(disposeWaitHandle))
{
disposeWaitHandle.WaitOne(disposeWaitMSec, false);
}
timer = null;
}
}
initializing = false;
delayedEnable = false;
enabled = false;
doNotDisposeWaitHandle.WaitOne(disposeWaitMSec, false);
doNotDisposeWaitHandle.Close();
SetMainThreadOperationCompleted();
}
[StructLayout(LayoutKind.Sequential)]
internal struct FILE_TIME
{
internal int ftTimeLow;
internal int ftTimeHigh;
}
private sealed class SafeNativeMethods
{
[ResourceExposure(ResourceScope.None)]
[DllImport("Kernel32"), SuppressUnmanagedCodeSecurityAttribute()]
internal static extern void GetSystemTimeAsFileTime(ref FILE_TIME lpSystemTimeAsFileTime);
}
}
/// <summary>
/// Provides data for the <see cref="ScheduledWorker.DoWork"/> event.
/// </summary>
public sealed class DoScheduledWorkEventArgs : DoWorkEventArgs
{
internal DoScheduledWorkEventArgs(object arg, DateTime signalTime) : base(arg)
{
SignalTime = signalTime;
}
/// <summary>
/// Gets the date/time when the <see cref="ScheduledWorker.DoWork"/> event was raised.
/// </summary>
public DateTime SignalTime { get; }
}
}
I just coded this class. I wish it helpful to others.
private class CustomTimer : IDisposable
{
private int duration = 1000;
private Action<object> tick;
private object obj;
private Thread thread;
private bool start = false;
public CustomTimer(int duration, Action<object> tick)
{
this.duration = duration;
this.tick = tick;
}
public void Start(object obj)
{
this.obj = obj;
start = true;
if (thread == null)
{
keepRunning = true;
thread = new Thread(ThreadMethod);
thread.Start();
}
else
{
if (thread.ThreadState == ThreadState.WaitSleepJoin)
thread.Interrupt();
}
}
public void Stop()
{
if (!start)
return;
start = false;
if (thread.ThreadState == ThreadState.WaitSleepJoin)
thread.Interrupt();
}
public bool IsStopped
{
get { return !start; }
}
private bool keepRunning = false;
private void ThreadMethod()
{
while (keepRunning)
{
if (start)
{
try { Thread.Sleep(duration); } catch { }
if (start && keepRunning)
tick(this.obj);
}
else if(keepRunning)
{
try { Thread.Sleep(int.MaxValue); } catch { }
}
}
}
public void Dispose()
{
this.keepRunning = false;
this.start = false;
if (thread.ThreadState == ThreadState.WaitSleepJoin)
thread.Interrupt();
if (thread.ThreadState == ThreadState.WaitSleepJoin)
thread.Interrupt();
}
}
I have legacy code which performs some very long operations on the UI thread.
What I want to do is to show a progress bar with message and run the work on another thread. Unfortunately , for now I don't have access to VS2012 so I can't use the async keyword.
I've written some code which works fine with operations of 0-1 parameters and no return value using Action.
But when I tried adjusting it to support Func I encountered some issues with
invoking the tasks and returning TResult.
Attached is my original code, would appreciate any suggestions. Thanks, Omer
public partial class FreeProgressBarFrm : System.Windows.Forms.Form
{
#region Members
/// <summary>
/// timer for the progress bar.
/// </summary>
private Timer m_Timer = new Timer();
/// <summary>
/// Delegate for the background operation to perform.
/// </summary>
private Action m_backgroundOperation;
/// <summary>
/// Standard operation to show the user while the operation is in progress.
/// </summary>
private static readonly string m_performingUpdatesMessage = IO_Global.GetResourceString("Performing updates, please wait", "Performing updates, please wait", null);
#endregion
#region Constructor
/// <summary>
/// Constructor
/// </summary>
/// <param name="backgroundDelegate"> Delegate for the background operation to perform</param>
/// <param name="operationName">meessage to show the user while the operation is in progress.</param>
public FreeProgressBarFrm(Action backgroundDelegate, string operationName)
{
InitializeComponent();
m_backgroundOperation = backgroundDelegate;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
this.lblOperation.Text = operationName;
m_Timer.Interval = 1000;
m_Timer.Tick += new EventHandler(m_Timer_Tick);
}
/// <summary>
/// Constructor , for progressbar with defalt user message (performing updates, please wait).
/// </summary>
/// <param name="backgroundDelegate"> Delegate for the background operation to perform</param>
/// <param name="operationName">operation display name</param>
public FreeProgressBarFrm(Action backgroundDelegate): this(backgroundDelegate, m_performingUpdatesMessage)
{
}
#endregion
#region Methods
/// <summary>
/// Call this method to begin backgorund operation while
/// showing the progress bar to the client.
/// </summary>
public void Wait()
{
ShowDialog(ControlsHelper.MainFrm);
}
/// <summary>
/// Advance the progress bar
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void m_Timer_Tick(object sender, EventArgs e)
{
PerformStep();
}
/// <summary>
/// Advance the progress bar
/// </summary>
private void PerformStep()
{
this.progressBar1.PerformStep();
this.lblOperation.Refresh();
if (this.progressBar1.Value == this.progressBar1.Maximum)
{
this.progressBar1.Value = this.progressBar1.Minimum;
}
}
/// <summary>
/// Load the form , start the progress bar and backroud task.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ProgressBarFrm_Load(object sender, EventArgs e)
{
m_Timer.Start();
this.lblOperation.Refresh();
Task task = new Task(m_backgroundOperation);
Task UITask = task.ContinueWith(delegate { OnWorkCompleted(); },
TaskScheduler.FromCurrentSynchronizationContext());
try
{
task.Start();
}
catch (Exception)
{
Close();
throw;
}
}
/// <summary>
/// Called when the work has been completed.
/// </summary>
private void OnWorkCompleted()
{
Close();
}
/// <summary>
/// Close the timer.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ProgressBarFrm_FormClosing(object sender, FormClosingEventArgs e)
{
if (m_Timer != null)
{
m_Timer.Dispose();
m_Timer = null;
}
}
#endregion
}
Here is the way i'm executing some async work,
public static class TaskExecuter
{
private static readonly ThreadLocal<List<BackgroundTask>> TasksToExecute =
new ThreadLocal<List<BackgroundTask>>(() => new List<BackgroundTask>());
public static Action<Exception> ExceptionHandler { get; set; }
public static void ExecuteLater(BackgroundTask task)
{
TasksToExecute.Value.Add(task);
}
public static void Discard()
{
TasksToExecute.Value.Clear();
}
public static void StartExecuting()
{
var value = TasksToExecute.Value;
var copy = value.ToArray();
value.Clear();
if (copy.Length > 0)
{
Task.Factory.StartNew(() =>
{
foreach (var backgroundTask in copy)
ExecuteTask(backgroundTask);
}, TaskCreationOptions.LongRunning)
.ContinueWith(task =>
{
if (ExceptionHandler != null)
ExceptionHandler(task.Exception);
}, TaskContinuationOptions.OnlyOnFaulted);
}
}
public static void ExecuteTask(BackgroundTask task)
{
task.Run();
}
}
Here is the base class
public abstract class BackgroundTask
{
protected readonly Logger Logger = LogManager.GetCurrentClassLogger();
protected virtual void Initialize()
{
}
protected virtual void OnError(Exception e)
{
//do some work
}
public bool? Run()
{
Logger.Info("Started task: {0}", GetType().Name);
Initialize();
try
{
Execute();
TaskExecuter.StartExecuting();
return true;
}
catch (Exception e)
{
Logger.ErrorException("Could not execute task " + GetType().Name, e);
OnError(e);
return false;
}
finally
{
TaskExecuter.Discard();
Logger.Info("Finished task: {0}", GetType().Name);
}
}
public abstract void Execute();
}
Here is the example of using
public class SendEmailTask : BackgroundTask
{
private const string MailServerIp = "yourip";
public string[] To { get; set; }
public string From { get; set; }
public string Template { get; set; }
public object ViewContext { get; set; }
public string[] Attachments { get; set; }
public string Subject { get; set; }
public override void Execute()
{
MailMessage message = new MailMessage();
try
{
MailAddress mailAddress = new MailAddress(From);
message.From = mailAddress;
foreach (string to in To) message.To.Add(to);
message.Subject = Subject;
if (Attachments.ReturnSuccess())
{
foreach (string attachment in Attachments)
message.Attachments.Add(new Attachment(attachment));
}
message.Priority = MailPriority.High;
message.Body = Template;
message.AlternateViews
.Add(AlternateView
.CreateAlternateViewFromString(ViewContext.ToString(), new ContentType("text/html")));
message.IsBodyHtml = true;
new SmtpClient(MailServerIp)
{
Port = 25,
UseDefaultCredentials = true
}.Send(message);
}
catch (Exception e)
{
Logger.FatalException("Error sending email:", e);
}
finally
{
message.Dispose();
}
}
public override string ToString()
{
return string.Format("To: {0}, From: {1}, Template: {2}, ViewContext: {3}, Attachments: {4}, Subject: {5}", To, From, Template, ViewContext, Attachments, Subject);
}
}
Here i've added a changed version for your needs
public static class AsyncExecuter
{
private static readonly ThreadLocal<List<Action>> TasksToExecute =
new ThreadLocal<List<Action>>(() => new List<BackgroundTask>());
public static Action<Exception> ExceptionHandler { get; set; }
public static void ExecuteLater(BackgroundTask task)
{
TasksToExecute.Value.Add(task);
}
public static void Discard()
{
TasksToExecute.Value.Clear();
}
public static void StartExecuting()
{
var value = TasksToExecute.Value;
var copy = value.ToArray();
value.Clear();
if (copy.Length > 0)
{
Task.Factory.StartNew(() =>
{
foreach (var backgroundTask in copy)
ExecuteTask(backgroundTask);
}, TaskCreationOptions.LongRunning)
.ContinueWith(task =>
{
if (ExceptionHandler != null)
ExceptionHandler(task.Exception);
}, TaskContinuationOptions.OnlyOnFaulted);
}
}
public static void ExecuteTask(Action task)
{
task.Invoke();
}
}
I have a list as below:
private List<DateTime> _result = new List<DateTime();
and I add values to it like
_result.Add(DateTime.Now);
The requirement is that each item which is added should be removed from the list within 5 minutes deadline.
I was thinking I could create a Timer which checks my list every e.g. 1 minute and find old items and remove them but I hoped there could be an easier way?
How to implement it?
Thanks
Here's my take on this:
public class DateWrapper
{
private ConcurrentBag<DateWrapper> list;
private DateTime time;
public DateTime Time
{
get { return time; }
}
private Timer timer;
public DateWrapper(ConcurrentBag<DateWrapper> _list, DateTime _time)
{
list = _list;
time = _time;
list.Add(this);
timer = new Timer();
timer.Interval = 300000; // 5 Minutes
timer.Tick += new EventHandler(Tick);
timer.Start();
}
private void Tick(object sender, EventArgs e)
{
list.Remove(this);
}
}
The above work for small list of item. With a too big list, you get too many timer... and performance would get hurt.
So, if you have to handle lot of items, here's a generic way to do it:
public class ExpirableList<T> : IList<T>
{
private volatile List<Tuple<DateTime, T>> collection = new List<Tuple<DateTime,T>>();
private Timer timer;
public int Interval
{
get { return timer.Interval; }
set { timer.Interval = value; }
}
private TimeSpan expiration;
public TimeSpan Expiration
{
get { return expiration; }
set { expiration = value; }
}
/// <summary>
/// Define a list that automaticly remove expired objects.
/// </summary>
/// <param name="_interval"></param>
/// The interval at which the list test for old objects.
/// <param name="_expiration"></param>
/// The TimeSpan an object stay valid inside the list.
public ExpirableList(int _interval, TimeSpan _expiration)
{
timer = new Timer();
timer.Interval = _interval;
timer.Tick += new EventHandler(Tick);
timer.Start();
expiration = _expiration;
}
private void Tick(object sender, EventArgs e)
{
for (int i = collection.Count - 1; i >= 0; i--)
{
if ((DateTime.Now - collection[i].Item1) >= expiration)
{
collection.RemoveAt(i);
}
}
}
#region IList Implementation
public T this[int index]
{
get { return collection[index].Item2; }
set { collection[index] = new Tuple<DateTime, T>(DateTime.Now, value); }
}
public IEnumerator<T> GetEnumerator()
{
return collection.Select(x => x.Item2).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return collection.Select(x => x.Item2).GetEnumerator();
}
public void Add(T item)
{
collection.Add(new Tuple<DateTime, T>(DateTime.Now, item));
}
public int Count
{
get { return collection.Count; }
}
public bool IsSynchronized
{
get { return false; }
}
public bool IsReadOnly
{
get { return false; }
}
public void CopyTo(T[] array, int index)
{
for (int i = 0; i < collection.Count; i++)
array[i + index] = collection[i].Item2;
}
public bool Remove(T item)
{
bool contained = Contains(item);
for (int i = collection.Count - 1; i >= 0; i--)
{
if ((object)collection[i].Item2 == (object)item)
collection.RemoveAt(i);
}
return contained;
}
public void RemoveAt(int i)
{
collection.RemoveAt(i);
}
public bool Contains(T item)
{
for (int i = 0; i < collection.Count; i++)
{
if ((object)collection[i].Item2 == (object)item)
return true;
}
return false;
}
public void Insert(int index, T item)
{
collection.Insert(index, new Tuple<DateTime, T>(DateTime.Now, item));
}
public int IndexOf(T item)
{
for (int i = 0; i < collection.Count; i++)
{
if ((object)collection[i].Item2 == (object)item)
return i;
}
return -1;
}
public void Clear()
{
collection.Clear();
}
#endregion
}
You can use background thread, which will be iterate through the list and remove unneeded elements.
public void RemoveDates()
{
var checkDatesTask= new Task(
() =>
{
while (!_cancelationTokenSource.IsCancellationRequested)
{
//TODO: check and delete elements here
_cancelationTokenSource.Token.WaitHandle.WaitOne(
TimeSpan.FromSeconds(
5));
}
},
_cancelationTokenSource.Token,
TaskCreationOptions.LongRunning);
checkDatesTask.Start();
}
p.s. I suggest you read more about async. operations.
Is there any good implementation of processing queue items asynchronously?
If you're using .NET 4, a lot of this comes for free out of the box.
If you've already got all the items, you can use Parallel.ForEach. If you need a producer/consumer queue, you can use BlockingCollection<T> to wrap one of the concurrent collections (such as ConcurrentQueue<T> or ConcurrentStack<T>). How you use that is up to you; there's a blog post here going into a detailed example, and there are probably other similar posts around too. (You might want to look at the Parallel Team Blog for a lot more material.)
You could take a look at a Producer/Consumer pattern if you are unfortunate enough not to be using .net 4.
Here is my code I have disassembled, my apologies for the mess but you should be able to use this by adding to a project and recompiling, then creating your process using the resulting dll.
Enum for ChannelState:
public enum ChannelState
{
WaitingForSend,
WaitingForReceive,
Open
}
Interfaces:
public interface IChannel<TMessage>
{
// Methods
TMessage Receive();
void Send(TMessage message);
// Properties
bool CanReceive { get; }
bool CanSend { get; }
ChannelState State { get; }
}
using System;
public interface IReceiver<TMessage>
{
// Events
event EventHandler<MessageReceivedEventArgs<TMessage>> MessageReceived;
// Methods
void Activate();
void Deactivate();
// Properties
bool IsActive { get; }
}
Concrete classes:
using System.Collections.Generic;
using System.Threading;
using System;
public class BufferedChannel<TMessage> : IChannel<TMessage>
{
// Fields
private int _blockedReceivers;
private int _blockedSenders;
private Queue<TMessage> _buffer;
private int _capacity;
private EventWaitHandle _capacityAvailableEvent;
private EventWaitHandle _messagesAvailableEvent;
// Methods
public BufferedChannel()
{
this._buffer = new Queue<TMessage>();
this._messagesAvailableEvent = new EventWaitHandle(false, EventResetMode.AutoReset);
this._capacityAvailableEvent = new EventWaitHandle(true, EventResetMode.AutoReset);
this._capacity = 50;
}
public BufferedChannel(int bufferSize)
{
this._buffer = new Queue<TMessage>();
this._messagesAvailableEvent = new EventWaitHandle(false, EventResetMode.AutoReset);
this._capacityAvailableEvent = new EventWaitHandle(true, EventResetMode.AutoReset);
this._capacity = 50;
if (bufferSize <= 0)
{
throw new ArgumentOutOfRangeException("bufferSize", bufferSize, ExceptionMessages.ChannelsBufferSizeMustBeGreaterThanZero);
}
this._capacity = bufferSize;
}
public TMessage Receive()
{
Interlocked.Increment(ref this._blockedReceivers);
try
{
this._messagesAvailableEvent.WaitOne();
}
catch
{
lock (this._buffer)
{
Interlocked.Decrement(ref this._blockedReceivers);
}
throw;
}
lock (this._buffer)
{
Interlocked.Decrement(ref this._blockedReceivers);
this._capacityAvailableEvent.Set();
if ((this._buffer.Count - 1) > this._blockedReceivers)
{
this._messagesAvailableEvent.Set();
}
return this._buffer.Dequeue();
}
}
public void Send(TMessage message)
{
Interlocked.Increment(ref this._blockedSenders);
try
{
this._capacityAvailableEvent.WaitOne();
}
catch
{
lock (this._buffer)
{
Interlocked.Decrement(ref this._blockedSenders);
}
throw;
}
lock (this._buffer)
{
Interlocked.Decrement(ref this._blockedSenders);
this._buffer.Enqueue(message);
if (this._buffer.Count < this.BufferSize)
{
this._capacityAvailableEvent.Set();
}
this._messagesAvailableEvent.Set();
}
}
// Properties
public int BufferCount
{
get
{
lock (this._buffer)
{
return this._buffer.Count;
}
}
}
public int BufferSize
{
get
{
lock (this._buffer)
{
return this._capacity;
}
}
set
{
lock (this._buffer)
{
if (value <= 0)
{
throw new ArgumentOutOfRangeException("BufferSize", value, ExceptionMessages.ChannelsBufferSizeMustBeGreaterThanZero);
}
this._capacity = value;
if ((this._blockedSenders > 0) && (this._capacity > this._buffer.Count))
{
this._capacityAvailableEvent.Set();
}
}
}
}
public bool CanReceive
{
get
{
return true;
}
}
public bool CanSend
{
get
{
return true;
}
}
public ChannelState State
{
get
{
if (this._blockedSenders > 0)
{
return ChannelState.WaitingForReceive;
}
if (this._blockedReceivers > 0)
{
return ChannelState.WaitingForSend;
}
return ChannelState.Open;
}
}
}
using System;
using System.Collections.Generic;
using System.Threading;
using System.ComponentModel;
using System.Runtime.CompilerServices;
public sealed class Receiver<TMessage> : Component, IReceiver<TMessage>
{
// Fields
private volatile bool _continue;
private object _controlLock;
private volatile bool _disposed;
private Thread _receiverThread;
private bool _receiving;
private object _receivingLock;
private object _threadLock;
[CompilerGenerated]
private IChannel<TMessage> channel;
// Events
public event EventHandler<MessageReceivedEventArgs<TMessage>> MessageReceived;
// Methods
public Receiver(IChannel<TMessage> channel)
{
this._controlLock = new object();
this._threadLock = new object();
this._receivingLock = new object();
if (channel == null)
{
throw new ArgumentNullException("channel");
}
this.Channel = channel;
}
public void Activate()
{
this.CheckDisposed();
lock (this._controlLock)
{
if (this._receiverThread != null)
{
throw new InvalidOperationException();
}
this._continue = true;
this._receiverThread = new Thread(new ThreadStart(this.RunAsync));
this._receiverThread.IsBackground = true;
this._receiverThread.Start();
}
}
private void CheckDisposed()
{
if (this._disposed)
{
throw new ObjectDisposedException(base.GetType().Name);
}
}
public void Deactivate()
{
lock (this._controlLock)
{
if (this._continue)
{
this._continue = false;
lock (this._threadLock)
{
if (this._receiverThread != null)
{
this.SafeInterrupt();
this._receiverThread.Join();
this._receiverThread = null;
}
}
}
}
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
this.Deactivate();
this._disposed = true;
}
}
private void OnMessageReceived(TMessage message)
{
EventHandler<MessageReceivedEventArgs<TMessage>> messageReceived = this.MessageReceived;
if (messageReceived != null)
{
messageReceived(this, new MessageReceivedEventArgs<TMessage>(message));
}
}
private void RunAsync()
{
while (this._continue)
{
TMessage message = default(TMessage);
bool flag = false;
try
{
lock (this._receivingLock)
{
this._receiving = true;
}
message = this.Channel.Receive();
flag = true;
lock (this._receivingLock)
{
this._receiving = false;
}
Thread.Sleep(0);
}
catch (ThreadInterruptedException)
{
}
if (!this._continue)
{
if (flag)
{
this.Channel.Send(message);
return;
}
break;
}
this.OnMessageReceived(message);
}
}
private void SafeInterrupt()
{
lock (this._receivingLock)
{
lock (this._threadLock)
{
if (this._receiving && (this._receiverThread != null))
{
this._receiverThread.Interrupt();
}
}
}
}
// Properties
protected override bool CanRaiseEvents
{
get
{
return true;
}
}
public IChannel<TMessage> Channel
{
[CompilerGenerated]
get
{
return this.channel;
}
[CompilerGenerated]
private set
{
this.channel = value;
}
}
public bool IsActive
{
get
{
lock (this._controlLock)
{
return (this._receiverThread != null);
}
}
}
}
using System;
using System.Runtime.CompilerServices;
public class MessageReceivedEventArgs<TMessage> : EventArgs
{
// Fields
[CompilerGenerated]
private TMessage message;
// Methods
public MessageReceivedEventArgs(TMessage message)
{
this.Message = message;
}
// Properties
public TMessage Message
{
[CompilerGenerated]
get
{
return this.message;
}
[CompilerGenerated]
private set
{
this.message = value;
}
}
}
using System.Threading;
public class BlockingChannel<TMessage> : IChannel<TMessage>
{
// Fields
private TMessage _message;
private EventWaitHandle _messageReceiveEvent;
private EventWaitHandle _messageReceiveyEvent;
private object _sendLock;
private ChannelState _state;
private object _stateLock;
// Methods
public BlockingChannel()
{
this._state = ChannelState.Open;
this._stateLock = new object();
this._messageReceiveyEvent = new EventWaitHandle(false, EventResetMode.AutoReset);
this._messageReceiveEvent = new EventWaitHandle(false, EventResetMode.AutoReset);
this._sendLock = new object();
}
public TMessage Receive()
{
this.State = ChannelState.WaitingForSend;
this._messageReceiveyEvent.WaitOne();
this._messageReceiveEvent.Set();
this.State = ChannelState.Open;
return this._message;
}
public void Send(TMessage message)
{
lock (this._sendLock)
{
this._message = message;
this.State = ChannelState.WaitingForReceive;
this._messageReceiveyEvent.Set();
this._messageReceiveEvent.WaitOne();
}
}
// Properties
public bool CanReceive
{
get
{
return true;
}
}
public bool CanSend
{
get
{
return true;
}
}
public ChannelState State
{
get
{
lock (this._stateLock)
{
return this._state;
}
}
private set
{
lock (this._stateLock)
{
this._state = value;
}
}
}
}
Pretty old but this is the good one that I know off http://www.codeproject.com/KB/cs/inprocessasynservicesincs.aspx
Use .NET 4 tasks.
var t = Task<int>.Factory.StartNew(() => ProcessItem());
Use the ConcurrencyOptions to set the maximum degree of parallelism on that processing.
If you want to roll it yourself, use BlockingCollection<T> which provides blocking and bounding capabilities for thread-safe collections and implement a separate thread (or threads) for the consumer.
I'm developing a thread safe class that I'll use as cache, it should work in .NET and Mono.
The items have a time to live, and every time that a object is retrieved, its time to live is refreshed. Each time that I add a item, the timestamp is added to another collection that holds the same keys. A timer raises the method that looks for out dated items and remove them.
When I try to get and item, I have to provide also a delegate indicating how to obtain it if it wouldn't exist in the cache.
I've testing, and although the items removal should happen every 30 seconds in the test, it's happening very often, almost every second, and I'don't know why.
This is the class:
public class GenericCache<TId, TItem>:IDisposable where TItem : class
{
SortedDictionary<TId, TItem> _cache;
SortedDictionary<TId, DateTime> _timeouts;
Timer _timer;
Int32 _cacheTimeout;
System.Threading.ReaderWriterLockSlim _locker;
public GenericCache(Int32 minutesTTL)
{
_locker = new System.Threading.ReaderWriterLockSlim();
_cacheTimeout = minutesTTL;
_cache = new SortedDictionary<TId, TItem>();
_timeouts = new SortedDictionary<TId, DateTime>();
_timer = new Timer((minutesTTL * 60) / 2);
_timer.Elapsed += new ElapsedEventHandler(_timer_Elapsed);
_timer.AutoReset = true;
_timer.Enabled = true;
_timer.Start();
}
/// <summary>
/// Get an item, if it doesn't exist, create it using the delegate
/// </summary>
/// <param name="id">Id for the item</param>
/// <param name="create">A delegate that generates the item</param>
/// <returns>The item</returns>
public TItem Get(TId id, Func<TItem> create)
{
_locker.EnterUpgradeableReadLock();
try
{
TItem item = _cache.Where(ci => ci.Key.Equals(id)).Select(ci => ci.Value).SingleOrDefault();
if (item == null)
{
_locker.EnterWriteLock();
// check again, maybe another thread is waiting in EnterWriteLock cos the same item is null
item = _cache.Where(ci => ci.Key.Equals(id)).Select(ci => ci.Value).SingleOrDefault();
if (item == null)
{
Debug.Write("_");
item = create.Invoke();
if (item != null)
{
_cache.Add(id, item);
_timeouts.Add(id, DateTime.Now);
}
}
}
else
_timeouts[id] = DateTime.Now;
return item;
}
finally
{
if(_locker.IsWriteLockHeld)
_locker.ExitWriteLock();
_locker.ExitUpgradeableReadLock();
}
}
/// <summary>
/// Execute a delegate in the items, for example clear nested collections.
/// </summary>
/// <param name="action">The delegate</param>
public void ExecuteOnItems(Action<TItem> action)
{
_locker.EnterWriteLock();
try
{
foreach (var i in _cache.Values)
action.Invoke(i);
}
finally
{
_locker.ExitWriteLock();
}
}
/// <summary>
/// Clear this cache
/// </summary>
public void Clear()
{
_locker.EnterWriteLock();
try
{
_cache.Clear();
_timeouts.Clear();
}
finally
{
_locker.ExitWriteLock();
}
}
/// <summary>
/// Remove outdated items
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void _timer_Elapsed(object sender, ElapsedEventArgs e)
{
_locker.EnterUpgradeableReadLock();
try
{
var delete = _timeouts.Where(to => DateTime.Now.Subtract(to.Value).TotalMinutes > _cacheTimeout).ToArray();
if(delete.Any())
{
_locker.EnterWriteLock();
foreach (var timeitem in delete)
{
Debug.Write("-");
_cache.Remove(timeitem.Key);
_timeouts.Remove(timeitem.Key);
}
}
}
finally
{
if(_locker.IsWriteLockHeld)
_locker.ExitWriteLock();
_locker.ExitUpgradeableReadLock();
}
}
#region IDisposable Members
private volatile Boolean disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
try
{
this.Clear();
}
finally
{
_locker.Dispose();
}
disposed = true;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~GenericCache()
{
Dispose(false);
}
#endregion
}
As you can see, in debug mode, when a item is added a "_" symbol is printed, and when and item is removed a "-" symbol is printed. In the tests, after the second minute can see how items are removed and added in the same second, when the items should be removed only every 30 seconds, and I don't know why:
This is how I tests:
static void Main(string[] args)
{
GenericCache<Int32, String> cache = new GenericCache<Int32, String>(1);
Debug.Listeners.Add(new ConsoleTraceListener());
Action a = delegate()
{
Random r = new Random(DateTime.Now.Millisecond);
while (true)
{
Int32 number = r.Next(0, 9999);
if (String.IsNullOrEmpty(cache.Get(number, () => number.ToString())))
Debug.Write("E");
Thread.Sleep(number);
}
};
for (int i = 0; i < 150; i++)
{
new Thread(new ThreadStart(a)).Start();
Thread.Sleep(5);
}
Console.ReadKey();
}
Do you see any problem in the GenericCache class?
Thanks in advance, kind regards.
First issue i see (assuming you are using System.Timers.Timer accepts milliseconds and you are passing seconds).
_timer = new Timer((minutesTTL * 60000) / 2);