I am wondering about timers in WPF.
what i basically know is how to make a simple count down timer (label) count down like this code:
private void buttonStartOne_Click(object sender, RoutedEventArgs e)
{
counterOne = new DispatcherTimer();
counterOne.Tick += new EventHandler(counterOne_Tick);
counterOne.Interval = new TimeSpan(0, 0, 1);
counterOneTime = 10;
counterOne.Start();
}
private void counterOne_Tick(object sender, EventArgs e)
{
// code goes here
if (counterOneTime > 0)
{
counterOneTime--;
labelCounterOne.Content = counterOneTime + "s";
}
else
counterOne.Stop();
}
In this example code above, the countdown is just 10 seconds.
What i want, and dont know is how i should make it as: HH:mm:ss and than make it count down.
Would you do that with 3 separate counters and labels (one for each time unit)?
Or what should be a better way to tackle this one?
You could convert your Timespan to a string with the ToString(string format) method. Set the resulting string on your label
public class TimeController
{
private static readonly TimeSpan TimeSpan = new TimeSpan(0, 0, 1);
private static int _time;
protected static readonly DispatcherTimer Timer = new DispatcherTimer();
protected static readonly DispatcherTimer BeeperTimer = new DispatcherTimer();
protected static readonly Stopwatch StopWatch = new Stopwatch();
protected static Label TimerLabel;
protected static Button StartButton;
internal static int Time { get { return _time; } set { _time = value; ExtractAndUpdate(); } }
internal static bool Countdown { get; set; }
/// <summary>
/// Static constructor
/// </summary>
static TimeController()
{
BeeperTimer.Interval = TimeSpan;
BeeperTimer.Tick += BeeperTick;
Timer.Interval = TimeSpan;
Timer.Tick += TimerTick;
}
/// <summary>
/// Timer tick event method
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void TimerTick(object sender, EventArgs e)
{
if (Countdown)
if (Time > 0)
{
ExtractAndUpdate();
Time -= 1;
}
else
{
StopRunning();
BeeperTimer.Start();
}
else
ExtractAndUpdate();
}
/// <summary>
/// Start timer and stopwatch
/// </summary>
protected static void StartRunning()
{
Timer.Start();
StopWatch.Start();
StartButton.Content = Labels.Pause;
}
/// <summary>
/// Stop timer and stopwatch
/// </summary>
protected static void StopRunning()
{
Timer.Stop();
StopWatch.Stop();
StartButton.Content = Labels.Start;
}
/// <summary>
/// Beeper event method and label blinking
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void BeeperTick(object sender, EventArgs e)
{
TimerLabel.Visibility = TimerLabel.Visibility.Equals(Visibility.Hidden) ? Visibility.Visible : Visibility.Hidden;
Console.Beep();
}
/// <summary>
/// Extract time and update label
/// </summary>
private static void ExtractAndUpdate()
{
var elapsed = Countdown ? ConvertToTimeSpan() : StopWatch.Elapsed;
UpdateTimeLabel(elapsed);
}
/// <summary>
/// Convert int to TimeSpan
/// </summary>
/// <returns></returns>
internal static TimeSpan ConvertToTimeSpan()
{
var hours = Time / 3600;
var minutes = (Time % 3600) / 60;
var seconds = Time % 60;
return new TimeSpan(hours, minutes, seconds);
}
/// <summary>
/// Update label with data and change color
/// </summary>
/// <param name="elapsed"></param>
protected static void UpdateTimeLabel(TimeSpan elapsed)
{
TimerLabel.Foreground = Brushes.Black;
var time = String.Format(CultureInfo.CurrentCulture, "{0:00h} {1:00m} {2:00s}", elapsed.Hours, elapsed.Minutes, elapsed.Seconds);
if (Countdown && elapsed.TotalMinutes < 1)
TimerLabel.Foreground = Brushes.Red;
TimerLabel.Content = time;
}
}
Related
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've made a timer class based on this discussion but I have a problem with it, the elapsed event occurs only one time.
The smaller issue: It would be a good thing if the _timer not have to be instantiated at every calling of Start().
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Diagnostics.Contracts;
namespace JellystonePark.Model
{
internal delegate void TimerCallback(object state);
internal sealed class Timer : CancellationTokenSource, IDisposable
{
internal Timer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
{
Contract.Assert(period == TimeSpan.FromMilliseconds(-1), "This stub implementation only supports dueTime.");
Task.Delay(dueTime, Token).ContinueWith((t, s) =>
{
var tuple = (Tuple<TimerCallback, object>)s;
tuple.Item1(tuple.Item2);
}, Tuple.Create(callback, state), CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Default);
}
public new void Dispose() { base.Cancel(); }
}
public class PCLTimer
{
private Timer _timer;
private TimeSpan _interval;
/// <summary>
/// Interval between signals in milliseconds.
/// </summary>
public double Interval
{
get { return _interval.TotalMilliseconds; }
set { _interval = TimeSpan.FromMilliseconds(value); Stop(); Start(); }
}
/// <summary>
/// True if PCLTimer is running, false if not.
/// </summary>
public bool Enabled
{
get { return null != _timer; }
set { if (value) Start(); else Stop(); }
}
/// <summary>
/// Occurs when the specified time has elapsed and the PCLTimer is enabled.
/// </summary>
public event EventHandler Elapsed;
/// <summary>
/// Starts the PCLTimer.
/// </summary>
public void Start()
{
if (0 == _interval.TotalMilliseconds)
throw new InvalidOperationException("Set Elapsed property before calling PCLTimer.Start().");
_timer = new Timer(OnElapsed, null, _interval, _interval);
}
/// <summary>
/// Stops the PCLTimer.
/// </summary>
public void Stop()
{
_timer.Dispose();
}
/// <summary>
/// Releases all resources.
/// </summary>
public void Dispose()
{
_timer.Dispose();
}
/// <summary>
/// Invokes Elapsed event.
/// </summary>
/// <param name="state"></param>
private void OnElapsed(object state)
{
if (null != _timer && null != Elapsed)
Elapsed(this, EventArgs.Empty);
}
}
}
If you interested in implementing Timer with Tasks, you can try this code:
public delegate void TimerCallback(object state);
public sealed class Timer : CancellationTokenSource, IDisposable
{
public Timer(TimerCallback callback, object state, int dueTime, int period)
{
Task.Delay(dueTime, Token).ContinueWith(async (t, s) =>
{
var tuple = (Tuple<TimerCallback, object>) s;
while (true)
{
if (IsCancellationRequested)
break;
Task.Run(() => tuple.Item1(tuple.Item2));
await Task.Delay(period);
}
}, Tuple.Create(callback, state), CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.Default);
}
public new void Dispose() { base.Cancel(); }
}
As already mentioned you can introduce dependency on some interface and ask user to give you particular timer implementation.
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'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);