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);
Related
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.
This question already has answers here:
How do I update the GUI from another thread?
(47 answers)
Closed 8 years ago.
I have a weird problem that i can't solve, i have a form that i open within another form, as soon as i open that form and since there is no event to fire after page finish loading, at the form load event i set a Timer that will start after 5sec. The timer will trigger a Task that will download files, downloading files is updated in a progressbar. The problem is that anything i try to change while Tasks are running doesn't update the GUI and will only change GUI after all Tasks finishes, note that the progressbar gets updated fine. Here is my code:
private void frm_HosterDownloader_Load(object sender, EventArgs e)
{
StartDownloadTimer = new Timer();
StartDownloadTimer.Tick += StartDownloadTimer_Tick;
StartDownloadTimer.Interval = 5000;
StartDownloadTimer.Start();
}
void StartDownloadTimer_Tick(object sender, EventArgs e)
{
StartDownload();
StartDownloadTimer.Stop();
}
private void StartDownload()
{
int counter = 0;
Dictionary<string, string> hosters = Hosters.GetAllHostersUrls();
progressBar_Download.Maximum = hosters.Count * 100;
progressBar_Download.Minimum = 0;
progressBar_Download.Value = 0;
foreach (KeyValuePair<string, string> host in hosters)
{
//Updating these tow lables never works, only when everything finishes
lbl_FileName.Text = host.Key;
lbl_NumberOfDownloads.Text = (++counter).ToString() + "/" + hosters.Count().ToString();
Task downloadTask = new Task(() =>
{
Downloader downloader = new Downloader(host.Value, string.Format(HostersImagesFolder + #"\{0}.png", IllegalChars(host.Key)));
downloader.HosterName = host.Key;
downloader.DownloadFinished += downloader_DownloadFinished;
downloader.Execute();
});
downloadTask.Start();
downloadTask.Wait();
}
}
void downloader_DownloadFinished(object sender, ProgressEventArgs e)
{
progressBar_Download.Value = progressBar_Download.Value + (int)e.ProgressPercentage;
}
I tired putting the tow label statments within the Task and even tried to pass them as an argument to be updated in the DownloadFinish event but no luck.
Edit:
Here is the Downloader Class:
public class Downloader : DownloaderBase
{
public string HosterName { set; get; }
/// <summary>
/// Initializes a new instance of the <see cref="Downloader"/> class.
/// </summary>
/// <param name="hoster">The hoster to download.</param>
/// <param name="savePath">The path to save the video.</param>
/// <param name="bytesToDownload">An optional value to limit the number of bytes to download.</param>
/// <exception cref="ArgumentNullException"><paramref name="video"/> or <paramref name="savePath"/> is <c>null</c>.</exception>
public Downloader(string hosterUrl, string savePath, int? bytesToDownload = null)
: base(hosterUrl, savePath, bytesToDownload)
{ }
/// <summary>
/// Occurs when the downlaod progress of the file file has changed.
/// </summary>
public event EventHandler<ProgressEventArgs> DownloadProgressChanged;
/// <summary>
/// Starts download.
/// </summary>
/// <exception cref="IOException">The video file could not be saved.</exception>
/// <exception cref="WebException">An error occured while downloading the video.</exception>
public override void Execute()
{
this.OnDownloadStarted(new ProgressEventArgs(0, HosterName));
var request = (HttpWebRequest)WebRequest.Create(this.HosterUrl);
if (this.BytesToDownload.HasValue)
{
request.AddRange(0, this.BytesToDownload.Value - 1);
}
try
{
// the following code is alternative, you may implement the function after your needs
request.Timeout = 100000;
request.ReadWriteTimeout = 100000;
request.ContinueTimeout = 100000;
using (WebResponse response = request.GetResponse())
{
using (Stream source = response.GetResponseStream())
{
using (FileStream target = File.Open(this.SavePath, FileMode.Create, FileAccess.Write))
{
var buffer = new byte[1024];
bool cancel = false;
int bytes;
int copiedBytes = 0;
while (!cancel && (bytes = source.Read(buffer, 0, buffer.Length)) > 0)
{
target.Write(buffer, 0, bytes);
copiedBytes += bytes;
var eventArgs = new ProgressEventArgs((copiedBytes * 1.0 / response.ContentLength) * 100, HosterName);
if (this.DownloadProgressChanged != null)
{
this.DownloadProgressChanged(this, eventArgs);
if (eventArgs.Cancel)
{
cancel = true;
}
}
}
}
}
}
}
catch (WebException ex)
{
if (ex.Status == WebExceptionStatus.Timeout)
Execute();
}
this.OnDownloadFinished(new ProgressEventArgs(100, HosterName));
}
}
public abstract class DownloaderBase
{
/// <summary>
/// Initializes a new instance of the <see cref="DownloaderBase"/> class.
/// </summary>
/// <param name="hosterUrl">The video to download/convert.</param>
/// <param name="savePath">The path to save the video/audio.</param>
/// /// <param name="bytesToDownload">An optional value to limit the number of bytes to download.</param>
/// <exception cref="ArgumentNullException"><paramref name="hosterUrl"/> or <paramref name="savePath"/> is <c>null</c>.</exception>
protected DownloaderBase(string hosterUrl, string savePath, int? bytesToDownload = null)
{
if (hosterUrl == null)
throw new ArgumentNullException("video");
if (savePath == null)
throw new ArgumentNullException("savePath");
this.HosterUrl = hosterUrl;
this.SavePath = savePath;
this.BytesToDownload = bytesToDownload;
}
/// <summary>
/// Occurs when the download finished.
/// </summary>
public event EventHandler<ProgressEventArgs> DownloadFinished;
/// <summary>
/// Occurs when the download is starts.
/// </summary>
public event EventHandler<ProgressEventArgs> DownloadStarted;
/// <summary>
/// Gets the number of bytes to download. <c>null</c>, if everything is downloaded.
/// </summary>
public string HosterUrl { get; set; }
/// <summary>
/// Gets the number of bytes to download. <c>null</c>, if everything is downloaded.
/// </summary>
public int? BytesToDownload { get; private set; }
/// <summary>
/// Gets the path to save the video/audio.
/// </summary>
public string SavePath { get; private set; }
/// <summary>
/// Starts the work of the <see cref="DownloaderBase"/>.
/// </summary>
public abstract void Execute();
protected void OnDownloadFinished(ProgressEventArgs e)
{
if (this.DownloadFinished != null)
{
this.DownloadFinished(this, e);
}
}
protected void OnDownloadStarted(ProgressEventArgs e)
{
if (this.DownloadStarted != null)
{
this.DownloadStarted(this, e);
}
}
}
It is not useful to use a Task this way:
downloadTask.Start();
downloadTask.Wait();
The Wait() will block the calling code and that is handling an event. Your downloads are effectively executing on the main GUI thread, blocking it.
The solution is
//downloadTask.Wait();
You don't seem to need it.
There is rarely a good reason to use threads (either new ones you create or threadpool) to do IO bound work. Here is an async alternative to your synchronous Execute method:
public async Task ExecuteAsync()
{
this.OnDownloadStarted(new ProgressEventArgs(0, HosterName));
var httpClient = new HttpClient();
var request = (HttpWebRequest)WebRequest.Create(this.HosterUrl);
if (this.BytesToDownload.HasValue)
{
request.AddRange(0, this.BytesToDownload.Value - 1);
}
try
{
request.Timeout = 100000;
request.ReadWriteTimeout = 100000;
request.ContinueTimeout = 100000;
var response = await httpClient.SendAsync(request);
var responseStream = await response.Content.ReadAsStreamAsync();
using (FileStream target = File.Open(this.SavePath, FileMode.Create, FileAccess.Write))
{
var buffer = new byte[1024];
bool cancel = false;
int bytes;
int copiedBytes = 0;
while (!cancel && (bytes = await responseStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await target.WriteAsync(buffer, 0, bytes);
copiedBytes += bytes;
var eventArgs = new ProgressEventArgs((copiedBytes * 1.0 / response.ContentLength) * 100, HosterName);
if (this.DownloadProgressChanged != null)
{
this.DownloadProgressChanged(this, eventArgs);
if (eventArgs.Cancel)
{
cancel = true;
}
}
}
}
}
catch (WebException ex)
{
if (ex.Status == WebExceptionStatus.Timeout)
}
this.OnDownloadFinished(new ProgressEventArgs(100, HosterName));
}
Now, there is no need to use Task.Wait or create a new Task. IO bound work is asynchronous by nature. In a combination with the new async-await keywords in C# 5, you can keep your UI responsive through the whole time, as each await yields control back to the calling method, and frees your winforms message pump to process more messasges in the meanwhile.
private async void frm_HosterDownloader_Load(object sender, EventArgs e)
{
await Task.Delay(5000);
await StartDownloadAsync();
}
private async Task StartDownloadAsync()
{
int counter = 0;
Dictionary<string, string> hosters = Hosters.GetAllHostersUrls();
progressBar_Download.Maximum = hosters.Count * 100;
progressBar_Download.Minimum = 0;
progressBar_Download.Value = 0;
var downloadTasks = hosters.Select(hoster =>
{
lbl_FileName.Text = hoster.Key;
lbl_NumberOfDownloads.Text = (++counter).ToString() + "/" + hosters.Count().ToString();
Downloader downloader = new Downloader(host.Value, string.Format(HostersImagesFolder + #"\{0}.png", IllegalChars(host.Key)));
downloader.HosterName = host.Key;
downloader.DownloadFinished += downloader_DownloadFinished;
return downloader.ExecuteAsync();
});
return Task.WhenAll(downloadTasks);
}
Note i changed your timer to a Task.Delay, since it internally uses a timer and you only need it to execute once.
If you want more on the use of async-await, you can start here.
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();
}
}
It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 10 years ago.
My apologies I was not accurate in my question and effort. I am developing Console application which has different components. Now I have decoupled them and want them to interact them using asynchronous Publisher/Subscriber way; similar to WPF. So in this case I will have one Master thread which will always be there and depending on request it will invoke event e.g. DataRequested which would be fired on background thread. Once Background thread completes process it will fire event again e.g. DataCompleted which should come back to the calling thread i.e. Master thread. I hope I am clear in my explanation.
So far I have coded below; where I have EventBroker.
public class EventBroker
{
public static event EventHandler SubscriptionAdded;
public static event EventHandler SubscriptionRemoved;
private static volatile EventBroker instance;
private static object syncRoot = new Object();
private static Dictionary<string, List<Delegate>> subscriptions;
/// <summary>
/// Initializes a new instance of the <see cref="T:EventBroker"/> class.
/// </summary>
private EventBroker()
{
}
/// <summary>
/// Gets the instance.
/// </summary>
/// <value>The instance.</value>
public static EventBroker Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
{
instance = new EventBroker();
subscriptions = new Dictionary<string, List<Delegate>>();
}
}
}
return instance;
}
}
/// <summary>
/// Gets or sets the internal subscriptions dictionary.
/// </summary>
/// <value>The subscriptions.</value>
private static Dictionary<string, List<Delegate>> Subscriptions
{
get { return EventBroker.subscriptions; }
set
{
lock (syncRoot)
{
EventBroker.subscriptions = value;
}
}
}
/// <summary>
/// Raises the subscription added event.
/// </summary>
/// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
private static void OnSubscriptionAdded(EventArgs e)
{
if (SubscriptionAdded != null)
SubscriptionAdded(instance, e);
}
/// <summary>
/// Raises the subscription removed event.
/// </summary>
/// <param name="e">The <see cref="T:System.EventArgs"/> instance containing the event data.</param>
private static void OnSubscriptionRemoved(EventArgs e)
{
if (SubscriptionRemoved != null)
SubscriptionRemoved(instance, e);
}
/// <summary>
/// Subscribe method to the specified event.
/// </summary>
/// <param name="id">The id.</param>
/// <param name="method">The method Delegate to be invoked when Event fires.</param>
public static void Subscribe(string id, Delegate method)
{
//Check if there is a existing event
List<Delegate> delegates = null;
if (Subscriptions == null)
Subscriptions = new Dictionary<string, List<Delegate>>();
if (Subscriptions.ContainsKey(id))
{
delegates = subscriptions[id];
}
else
{
delegates = new List<Delegate>();
Subscriptions.Add(id, delegates);
}
delegates.Add(method);
OnSubscriptionAdded(new EventArgs());
}
/// <summary>
/// Unsubscribe method from event notifications
/// </summary>
/// <param name="id">The id.</param>
/// <param name="method">The method.</param>
public static void Unsubscribe(string id, Delegate method)
{
if (Subscriptions.ContainsKey(id))
{
if (Subscriptions[id].Contains(method))
{
Subscriptions[id].Remove(method);
OnSubscriptionRemoved(new EventArgs());
}
if (Subscriptions[id].Count == 0)
Subscriptions.Remove(id);
}
}
/// <summary>
/// Fire the specified event by and pass parameters.
/// </summary>
/// <param name="id">The id.</param>
/// <param name="args">The args.</param>
public static void Execute(string id, object sender, EventArgs e)
{
if (Subscriptions.ContainsKey(id))
{
for (int i = 0; i < Subscriptions[id].Count; i++)
{
Delegate x = Subscriptions[id][i];
DynamicInvoke(id, x, sender, e);
if (!Subscriptions.ContainsKey(id))
break;
}
}
}
/// <summary>
/// Checks to see if target of invocation is still a valid
/// (non-disposed objects). Then it dinamicly invokes Delegate.
/// </summary>
/// <param name="id">Event ID</param>
/// <param name="x">Delegate to invoke</param>
/// <param name="args">Object array of arguments</param>
private static void DynamicInvoke(string id, Delegate x, object sender, EventArgs e)
{
if (x.Method != null)
{
if (x.Target is Control)
{
Control ctl = (Control)x.Target;
if (ctl.IsDisposed)
{
Unsubscribe(id, x);
return;
}
}
if (x.Target == null)
{
Unsubscribe(id, x);
return;
}
x.DynamicInvoke(sender, e); ***//this becomes blocking call untill EventHandle is completed and hangs Master Thread***
}
}
}
I use this EventBroker to keep track of my Subscribers and once Publisher comes I invoke certain delegate. But it gets invoked only on Master thread and it gets hanged. I want to invoke EventHandler on separate thread.
public class MasterClass
{
public MasterClass()
{
EventBroker.Subscribe("Topic2", new EventHandler<EventArgs<string>>(CallBackfromWorker));
}
public void InvokeTest()
{
EventArgs<string> EventArgs = new EventArgs<string>("Test");
EventBroker.Execute("Topic1", null, EventArgs); //I want both of this to be asynchronous.
}
public void CallBackfromWorker(object sender, EventArgs<string> e)
{
Debug.Pring("Get Called Asynchronously from Worker thread through Event");
}
}
**//Worker Class**
public class WorkerClass
{
public WorkerClass()
{
EventBroker.Subscribe("Topic1", new EventHandler<EventArgs<string>>(HandleRapRequest1));
}
public void HandleRapRequest1(string RAPRequest)
//public void HandleRapRequest1(object sender, EventArgs<string> e)
{
Logger.LogToDisplay("WorkerClass Request" + RAPRequest);
Logger.LogToDisplay("AsyncClient : " + System.Threading.Thread.CurrentThread.IsBackground);
Logger.LogToDisplay("AsyncClient : " + System.Threading.Thread.CurrentThread.ManagedThreadId);
Logger.LogToDisplay("Going to Sleep");
System.Threading.Thread.Sleep(10000); ***//Hangs my Master Thread***
EventBroker.Execute("Topic2", null, EventArgs); //I want both of this to be asynchronous.
}
}
So bottom line is I am looking for Asynchronous Eventbased Publisher/Subscriber in Console application...similar to CAB event sin SCSF and in WPF...
Thanks
This is simple enough to do:
public class Foo
{
public event Action MyEvent;
public void FireEvent()
{
Action myevent = MyEvent;
if (myevent != null)
{
Task.Factory.StartNew(() => myevent())
.ContinueWith(t =>
{
//TODO code to run in UI thread after event runs goes here
}, CancellationToken.None
, TaskContinuationOptions.None
, TaskScheduler.FromCurrentSynchronizationContext());
}
}
}
If you're using C# 5.0 you can use await which simplifies this code:
public class Foo
{
public event Action MyEvent;
public async Task FireEvent()
{
Action myevent = MyEvent;
if (MyEvent != null)
{
await Task.Run(() => myevent());
//TODO code to run in UI thread after event runs goes here
}
}
}
If you don't need the code running in the UI thread to start after the event handlers are all completed, and it can instead keep going on the UI thread at the same time, you can also simplify the code to just:
public class Foo
{
public event Action MyEvent;
public void FireEvent()
{
Action myevent = MyEvent;
if (MyEvent != null)
{
Task.Factory.StartNew(() => myevent());
//TODO code to run in UI thread while event handlers run goes here
}
}
}
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();
}
}