Im trying to start a stopwatch from a given time (decimal value pulled from a database). However, because the Stopwatch.Elapsed.Add returns a new Timespan rather than modify the Stopwatch, I can't work out the best way forward.
var offsetTimeStamp = new System.TimeSpan(0,0,0).Add(TimeSpan.FromSeconds((double)jd.ActualTime));
Stopwatch.Elapsed.Add(offsetTimeStamp);
Stopwatch.Start();
Any ideas how I can do this? Cheers
The normal StopWatch does not support initialization with an offset timespan and TimeSpan is a struct, therefore Elapsed is immutable. You could write a wrapper around StopWatch:
public class StopWatchWithOffset
{
private Stopwatch _stopwatch = null;
TimeSpan _offsetTimeSpan;
public StopWatchWithOffset(TimeSpan offsetElapsedTimeSpan)
{
_offsetTimeSpan = offsetElapsedTimeSpan;
_stopwatch = new Stopwatch();
}
public void Start()
{
_stopwatch.Start();
}
public void Stop()
{
_stopwatch.Stop();
}
public TimeSpan ElapsedTimeSpan
{
get
{
return _stopwatch.Elapsed + _offsetTimeSpan;
}
set
{
_offsetTimeSpan = value;
}
}
}
Now you can add a start-timespan:
var offsetTimeStamp = TimeSpan.FromHours(1);
var watch = new StopWatchWithOffset(offsetTimeStamp);
watch.Start();
System.Threading.Thread.Sleep(300);
Console.WriteLine(watch.ElapsedTimeSpan);// 01:00:00.2995983
The Elapsed property of StopWatch is read only, which makes sense. A stopwatch simply measures the amount of time that passed between start and stop.
If you want to add a timespan to the value - get the Elapsed value in a variable and add a timespan to it, after you have measured it (i.e. after stopping).
I think you want to start your Stopwatch after a certain mount of time specified by a TimeSpan. I wonder why you don't want to start your Stopwatch at a time specified by a DateTime instead?
public class MyStopwatch : Stopwatch
{
public void Start(long afterMiliseconds)
{
Timer t = new Timer() { Interval = 1 };
int i = 0;
t.Tick += (s, e) =>
{
if (i++ == afterMiliseconds)
{
Start();
t.Stop();
}
};
t.Start();
}
}
//use it
var offsetTimeStamp = new System.TimeSpan(0,0,0).Add(TimeSpan.FromSeconds((double)jd.ActualTime));
myStopwatch.Start((long)offsetTimeStamp.TotalMiliseconds);
This isn't a great fit for the OPs scenario (which I'm guessing they solved 8 years ago), but if you just need to create stopwatches for unit tests or other non-production scenarios then you can use reflection to modify the elapsed time.
This isn't going to give you the best performance, and can break if the underlying implementation of Stopwatch changes, so I'd be very circumspect using this approach.
However, for unit tests where you need to pass around a Stopwatch and can't change to use an alternate implementation, I find this approach to work well and the risk to be acceptable.
/// <summary>
/// Some static mechanisms for creating Stopwatch instances that start from a specific time.
/// </summary>
public static class TestStopwatch
{
/// <summary>
/// Creates a <see cref="Stopwatch"/> instance with a specified amount of time already elapsed
/// </summary>
/// <param name="start">The <see cref="TimeSpan"/> indicated the elapsed time to start from.</param>
public static Stopwatch WithElapsed(TimeSpan start)
{
var sw = new Stopwatch();
var elapsedProperty = typeof(Stopwatch).GetField("_elapsed", BindingFlags.NonPublic | BindingFlags.Instance);
long rawElapsedTicks = start.Ticks;
if (Stopwatch.IsHighResolution)
{
rawElapsedTicks = (long)((double)rawElapsedTicks / (10000000 / (double)Stopwatch.Frequency));
}
elapsedProperty.SetValue(sw, rawElapsedTicks);
return sw;
}
/// <summary>
/// Initializes a new <see cref="Stopwatch"/> instance, sets the elapsed time property to the specified value,
/// and starts measuring elapsed time.
/// </summary>
/// <param name="start">The <see cref="TimeSpan"/> indicated the elapsed time to start from.</param>
public static Stopwatch StartNew(TimeSpan start)
{
var sw = TestStopwatch.WithElapsed(start);
sw.Start();
return sw;
}
}
If you add this file to your project, there is nothing you need to change in your project. This class inherits from the original Stopwatch class and has the same name and the same methods/properties, but with additional features:
SetOffset() method
Initialization with offset
.
using System;
public class Stopwatch : System.Diagnostics.Stopwatch
{
TimeSpan _offset = new TimeSpan();
public Stopwatch()
{
}
public Stopwatch(TimeSpan offset)
{
_offset = offset;
}
public void SetOffset(TimeSpan offsetElapsedTimeSpan)
{
_offset = offsetElapsedTimeSpan;
}
public TimeSpan Elapsed
{
get{ return base.Elapsed + _offset; }
set{ _offset = value; }
}
public long ElapsedMilliseconds
{
get { return base.ElapsedMilliseconds + _offset.Milliseconds; }
}
public long ElapsedTicks
{
get { return base.ElapsedTicks + _offset.Ticks; }
}
}
Related
Definition
It is necessary to run the code for example in 12:00, 15:00, 19:00, 20:00 every day.
The task that is being performed can be any, for example, copying a folder.
My implementation
There is a code for running tasks on a schedule:
public class TaskScheduler
{
private static TaskScheduler _instance;
private List<Timer> _timers = new List<Timer>();
private TaskScheduler() { }
public static TaskScheduler Instance => _instance ?? (_instance = new TaskScheduler());
/// <summary>
/// Create new Task
/// </summary>
/// <code>
/// TaskScheduler.Instance.ScheduleTask(
/// ()=>
/// {
///
/// }, new TimeSpan(0, 20, 0));
///
/// TaskScheduler.Instance.ScheduleTask(Action, TimeSpan);
///
/// </code>
/// <param name="task">Action</param>
/// <param name="time"></param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="OverflowException"></exception>
public void ScheduleTask(Action task, TimeSpan time)
{
ScheduleTask(task, time.Hours, time.Minutes, time.Seconds);
}
/// <summary>
/// Create new Task
/// </summary>
/// <code>
/// TaskScheduler.Instance.ScheduleTask(
/// ()=>
/// {
///
/// }, 1, 20, 0);
///
/// TaskScheduler.Instance.ScheduleTask(Action, hour, minute, second);
///
/// </code>
/// <param name="task">Action</param>
/// <param name="hour">Hour</param>
/// <param name="min">Minute</param>
/// <param name="second">Second</param>
/// <param name="intervalInHour">Interval in hours</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentOutOfRangeException"></exception>
/// <exception cref="OverflowException"></exception>
public void ScheduleTask(Action task, int hour = 0, int min = 0, int second = 1, double intervalInHour = 24d)
{
var now = DateTime.Now;
var firstRun = new DateTime(now.Year, now.Month, now.Day, hour, min, second);
if (now > firstRun) firstRun = firstRun.AddDays(1);
var timeToGo = firstRun - now;
if (timeToGo <= TimeSpan.Zero) timeToGo = TimeSpan.Zero;
var timer = new Timer(x =>
{
task.Invoke();
}, null, timeToGo, TimeSpan.FromHours(intervalInHour));
_timers.Add(timer);
}
}
It works great, tasks are running, working.
There is a certain nuance, there is a code that copies a directory to a directory.
public static class FolderCopper
{
public static void CopyAll(DirectoryInfo source, DirectoryInfo target, CancellationToken token)
{
if (!source.Exists) return;
if (!target.Exists) target.Create();
var po = new ParallelOptions
{
CancellationToken = token
};
Parallel.ForEach(source.GetDirectories(), po, (sourceChildDirectory) =>
{
if (po.CancellationToken.IsCancellationRequested)
{
po.CancellationToken.ThrowIfCancellationRequested();
}
CopyAll(sourceChildDirectory, new DirectoryInfo(Path.Combine(target.FullName, sourceChildDirectory.Name)), token);
});
Parallel.ForEach(source.GetFiles(), po, sourceFile =>
{
if (po.CancellationToken.IsCancellationRequested)
{
po.CancellationToken.ThrowIfCancellationRequested();
}
var file = new FileInfo(Path.Combine(target.FullName, sourceFile.Name));
switch (file.Exists)
{
case false:
sourceFile.CopyTo(file.FullName);
break;
case true when file.LastWriteTimeUtc < sourceFile.LastWriteTimeUtc:
sourceFile.CopyTo(file.FullName, true);
break;
}
});
}
}
It must be run for example once every 20 or 10 minutes.
I call in this way:
Utils.Task Scheduler.Instance.Schedule Task(() => {}, 0, 20, 0, 0.33); //Task, hours, minutes, seconds, the interval in hours of 20 minutes is 0.33.
```c#
Or:
```c#
Utils.Task Scheduler.Instance.Schedule Task(() => {//coppy}, 1, 20, 30, 24); //Task, hours, minutes, seconds, the interval in hours.
One and the same task is always started.
Consider the situation, running the code at 12:00 and 13: 00 to copy the directory. The directory weighs 1670 GB. Accordingly, he does not have time to copy copy, and starts another 1 task.
Question
How can I rewrite the code so that it does not run another 1 instance if the previous one did not work, and just skip the task?
I did it through the blocking mechanism.
Described the class for the work of the task itself:
public class TimedTask
{
private Timer _timer;
private readonly Action _actionToInvoke;
private TimeSpan _intervalTime;
private TimeSpan _startTime;
private const int WAITING_TIME_IN_MINUTES = 1;
private object _locker = new object();
public TimedTask(Action toInvoke, TimeSpan startTime, TimeSpan interval)
{
this._actionToInvoke = toInvoke;
this._startTime = startTime;
this._intervalTime = interval;
}
public void Start()
{
this.Stop();
TimeSpan timeToGo = CalculateTheFirstStartTime();
_timer = new Timer(Callback, null, timeToGo, _intervalTime);
}
public void Stop()
{
var local = _timer;
_timer = null;
local?.Dispose();
}
private TimeSpan CalculateTheFirstStartTime()
{
var now = DateTime.Now;
var firstRun = new DateTime(now.Year, now.Month, now.Day, _startTime.Hours, _startTime.Minutes, _startTime.Seconds);
if (now > firstRun) firstRun = firstRun.AddDays(1);
var timeToGo = firstRun - now;
if (timeToGo <= TimeSpan.Zero) timeToGo = TimeSpan.Zero;
return timeToGo;
}
private void Callback(object state)
{
var timeout = TimeSpan.FromMinutes(WAITING_TIME_IN_MINUTES);
Console.WriteLine($"{DateTime.Now.ToLongTimeString()} Попытка запуска");
if (Monitor.TryEnter(_locker, timeout))
{
try
{
Console.WriteLine($"{DateTime.Now.ToLongTimeString()} Попытка успешна");
Console.WriteLine($"{DateTime.Now.ToLongTimeString()} Запускаю задачу");
this._actionToInvoke();
Console.WriteLine($"{DateTime.Now.ToLongTimeString()} Задача выполнена");
}
finally
{
Monitor.Exit(_locker);
}
}
else
{
Console.WriteLine($"{DateTime.Now.ToLongTimeString()} Запуск не осуществлён");
}
}
}
And a class that is a collection of tasks:
public class Scheduler
{
private List<TimedTask> _tasks = new List<TimedTask>();
public void ScheduleJob(Action action, TimeSpan firstRun)
{
ScheduleJob(action, firstRun, new TimeSpan(24, 0, 0));
}
public void ScheduleJob(Action action, TimeSpan firstRun, TimeSpan interval)
{
var task = new TimedTask(action, firstRun, interval);
_tasks.Add(task);
task.Start();
}
public void Stop()
{
_tasks.ForEach(t => t.Stop());
_tasks.Clear();
}
}
You can use a variable (like a static bool) to save the state. Ensure only locked access is possible and query that. If state is still running, then return, else do your work.
class Foo {
private bool Running = false;
private static object lockObject = new object();
public void Run() {
lock (lockObject) {
if (Running) return;
Running = true;
}
try {
// do your stuff
} finally {
lock (lockObject) {
Running = false;
}
}
}
}
Above is simplified code to demonstrate the idea
Do WPF have Touch-and-Hold gesture? I cannot find event for that, so I tried to implement one for myself. I know that there is Stylus class but in WPF it does not help me. If there aren't one there is my code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
namespace WebControlTouch
{
/// <summary>
/// Due to lack of Touch-and-Hold gesture, here is implementation of it. Stupid M$.
/// </summary>
public static class Touch_and_Hold
{
#region Constructor + methods
/// <summary>
/// Static constructor which creates timer object with 1000ms interval, also sets parameters of Timer.
/// </summary>
static Touch_and_Hold()
{
gestureTimer = new Timer(1000);
gestureTimer.AutoReset = false;
gestureTimer.Elapsed += gestureTimer_Elapsed;
}
/// <summary>
/// On elasped (time ofc)
/// </summary>
/// <seealso cref="gestureTimer"/>
static void gestureTimer_Elapsed(object sender, ElapsedEventArgs e)
{
occured = true;
}
/// <summary>
/// Call it on OnTouchDown event.
/// It will start timer and will count time of touch
/// </summary>
/// <returns>Returns that gesture occured</returns>
public static void onTouch()
{
gestureTimer.Start();
}
/// <summary>
/// Call it on touch up mainwindow event (or somewhere else)
/// It stops gesture timer
/// </summary>
public static void onTouchUp()
{
occured = false;
}
#endregion
#region Members + properties
/// <summary>
/// Timer for measuring touchTime
/// </summary>
private static Timer gestureTimer;
/// <summary>
/// Do tap-and-hold occured
/// </summary>
private static bool occured = false;
/// <summary>
/// Property for getting occured flag
/// </summary>
public static bool occuredGesture
{
get { return occured; }
}
#endregion
}
}
If yes, please tell me name of the event. If not - try to steer me to solution.
Any help will be very appreciated.
It is possible to do that in an awaitable fashion. Create a timer with specific interval. Start it when user tapped and return the method when timer elapsed. If user release the hand, return the method with false flag.
public static Task<bool> TouchHold(this FrameworkElement element, TimeSpan duration)
{
DispatcherTimer timer = new DispatcherTimer();
TaskCompletionSource<bool> task = new TaskCompletionSource<bool>();
timer.Interval = duration;
MouseButtonEventHandler touchUpHandler = delegate
{
timer.Stop();
if (task.Task.Status == TaskStatus.Running)
{
task.SetResult(false);
}
};
element.PreviewMouseUp += touchUpHandler;
timer.Tick += delegate
{
element.PreviewMouseUp -= touchUpHandler;
timer.Stop();
task.SetResult(true);
};
timer.Start();
return task.Task;
}
For more information, read this post.
I've previously achieved this by create a custom control that extends button to delay the trigger of a button command after a delay on press-and-hold.
public class DelayedActionCommandButton : Button
First dependency properties:
public static readonly DependencyProperty DelayElapsedProperty =
DependencyProperty.Register("DelayElapsed", typeof(double), typeof(DelayedActionCommandButton), new PropertyMetadata(0d));
public static readonly DependencyProperty DelayMillisecondsProperty =
DependencyProperty.Register("DelayMilliseconds", typeof(int), typeof(DelayedActionCommandButton), new PropertyMetadata(1000));
public double DelayElapsed
{
get { return (double)this.GetValue(DelayElapsedProperty); }
set { this.SetValue(DelayElapsedProperty, value); }
}
public int DelayMilliseconds
{
get { return (int)this.GetValue(DelayMillisecondsProperty); }
set { this.SetValue(DelayMillisecondsProperty, value); }
}
These give us a control on how the delay should be and an output of how long is left.
Next I create an animation, to control the elapsed amount which when complete fires the command. There is also a cancel delay method:
private void BeginDelay()
{
this._animation = new DoubleAnimationUsingKeyFrames() { FillBehavior = FillBehavior.Stop };
this._animation.KeyFrames.Add(new EasingDoubleKeyFrame(0, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(0)), new CubicEase() { EasingMode = EasingMode.EaseIn }));
this._animation.KeyFrames.Add(new EasingDoubleKeyFrame(1, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(this.DelayMilliseconds)), new CubicEase() { EasingMode = EasingMode.EaseIn }));
this._animation.Completed += (o, e) =>
{
this.DelayElapsed = 0d;
this.Command.Execute(this.CommandParameter); // Replace with whatever action you want to perform
};
this.BeginAnimation(DelayElapsedProperty, this._animation);
}
private void CancelDelay()
{
// Cancel animation
this.BeginAnimation(DelayElapsedProperty, null);
}
Finally, we wire up the event handlers:
private void DelayedActionCommandButton_TouchDown(object sender, System.Windows.Input.TouchEventArgs e)
{
this.BeginDelay();
}
private void DelayedActionCommandButton_TouchUp(object sender, System.Windows.Input.TouchEventArgs e)
{
this.CancelDelay();
}
When used in XAML, you can optionally create a template that can animate based on the value of DelayElapsed to provide a countdown, or visual cue such as an expanding border, whatever takes your fancy.
Im trying to start a stopwatch from a given time (decimal value pulled from a database). However, because the Stopwatch.Elapsed.Add returns a new Timespan rather than modify the Stopwatch, I can't work out the best way forward.
var offsetTimeStamp = new System.TimeSpan(0,0,0).Add(TimeSpan.FromSeconds((double)jd.ActualTime));
Stopwatch.Elapsed.Add(offsetTimeStamp);
Stopwatch.Start();
Any ideas how I can do this? Cheers
The normal StopWatch does not support initialization with an offset timespan and TimeSpan is a struct, therefore Elapsed is immutable. You could write a wrapper around StopWatch:
public class StopWatchWithOffset
{
private Stopwatch _stopwatch = null;
TimeSpan _offsetTimeSpan;
public StopWatchWithOffset(TimeSpan offsetElapsedTimeSpan)
{
_offsetTimeSpan = offsetElapsedTimeSpan;
_stopwatch = new Stopwatch();
}
public void Start()
{
_stopwatch.Start();
}
public void Stop()
{
_stopwatch.Stop();
}
public TimeSpan ElapsedTimeSpan
{
get
{
return _stopwatch.Elapsed + _offsetTimeSpan;
}
set
{
_offsetTimeSpan = value;
}
}
}
Now you can add a start-timespan:
var offsetTimeStamp = TimeSpan.FromHours(1);
var watch = new StopWatchWithOffset(offsetTimeStamp);
watch.Start();
System.Threading.Thread.Sleep(300);
Console.WriteLine(watch.ElapsedTimeSpan);// 01:00:00.2995983
The Elapsed property of StopWatch is read only, which makes sense. A stopwatch simply measures the amount of time that passed between start and stop.
If you want to add a timespan to the value - get the Elapsed value in a variable and add a timespan to it, after you have measured it (i.e. after stopping).
I think you want to start your Stopwatch after a certain mount of time specified by a TimeSpan. I wonder why you don't want to start your Stopwatch at a time specified by a DateTime instead?
public class MyStopwatch : Stopwatch
{
public void Start(long afterMiliseconds)
{
Timer t = new Timer() { Interval = 1 };
int i = 0;
t.Tick += (s, e) =>
{
if (i++ == afterMiliseconds)
{
Start();
t.Stop();
}
};
t.Start();
}
}
//use it
var offsetTimeStamp = new System.TimeSpan(0,0,0).Add(TimeSpan.FromSeconds((double)jd.ActualTime));
myStopwatch.Start((long)offsetTimeStamp.TotalMiliseconds);
This isn't a great fit for the OPs scenario (which I'm guessing they solved 8 years ago), but if you just need to create stopwatches for unit tests or other non-production scenarios then you can use reflection to modify the elapsed time.
This isn't going to give you the best performance, and can break if the underlying implementation of Stopwatch changes, so I'd be very circumspect using this approach.
However, for unit tests where you need to pass around a Stopwatch and can't change to use an alternate implementation, I find this approach to work well and the risk to be acceptable.
/// <summary>
/// Some static mechanisms for creating Stopwatch instances that start from a specific time.
/// </summary>
public static class TestStopwatch
{
/// <summary>
/// Creates a <see cref="Stopwatch"/> instance with a specified amount of time already elapsed
/// </summary>
/// <param name="start">The <see cref="TimeSpan"/> indicated the elapsed time to start from.</param>
public static Stopwatch WithElapsed(TimeSpan start)
{
var sw = new Stopwatch();
var elapsedProperty = typeof(Stopwatch).GetField("_elapsed", BindingFlags.NonPublic | BindingFlags.Instance);
long rawElapsedTicks = start.Ticks;
if (Stopwatch.IsHighResolution)
{
rawElapsedTicks = (long)((double)rawElapsedTicks / (10000000 / (double)Stopwatch.Frequency));
}
elapsedProperty.SetValue(sw, rawElapsedTicks);
return sw;
}
/// <summary>
/// Initializes a new <see cref="Stopwatch"/> instance, sets the elapsed time property to the specified value,
/// and starts measuring elapsed time.
/// </summary>
/// <param name="start">The <see cref="TimeSpan"/> indicated the elapsed time to start from.</param>
public static Stopwatch StartNew(TimeSpan start)
{
var sw = TestStopwatch.WithElapsed(start);
sw.Start();
return sw;
}
}
If you add this file to your project, there is nothing you need to change in your project. This class inherits from the original Stopwatch class and has the same name and the same methods/properties, but with additional features:
SetOffset() method
Initialization with offset
.
using System;
public class Stopwatch : System.Diagnostics.Stopwatch
{
TimeSpan _offset = new TimeSpan();
public Stopwatch()
{
}
public Stopwatch(TimeSpan offset)
{
_offset = offset;
}
public void SetOffset(TimeSpan offsetElapsedTimeSpan)
{
_offset = offsetElapsedTimeSpan;
}
public TimeSpan Elapsed
{
get{ return base.Elapsed + _offset; }
set{ _offset = value; }
}
public long ElapsedMilliseconds
{
get { return base.ElapsedMilliseconds + _offset.Milliseconds; }
}
public long ElapsedTicks
{
get { return base.ElapsedTicks + _offset.Ticks; }
}
}
I'm trying to build a display for a die roll. What I want to do is flicker images of random faces on the die, then end with the face that shows the number rolled. After this happens, I want the function to continue and return the number of the die roll. Here's what I have
public int RollDie()
{
RollNum = dieRoll.Next(1, 7);
DispCount = 0;
Timer Time = new Timer();
Time.Interval = TimerInterval;
Time.Tick += DisplayRollHandler;
Time.Start();
System.Threading.Thread DispThread = new System.Threading.Thread(Time.Start);
DispThread.Start();
DispThread.Join();
return RollNum;
}
private void DisplayRollHandler(object sender, EventArgs evt)
{
if (DispCount < TargetDispCount)
{
Random Nums = new Random();
Display.BackgroundImage = Faces[Nums.Next(0, 6)];
DispCount++;
}
else
{
((Timer)sender).Stop();
Display.BackgroundImage = Faces[RollNum - 1];
}
}
where dieRoll is a random object and Display is a Panel. The image flicker works, and it does return the number of the roll consistently. Unfortunately, it doesn't wait for the display flicker to finish before continuing, which is a problem when I have automatic messages that pop up after the die is rolled.
I'm a fairly inexperienced programmer, so I'm probably missing a really basic concept. I know that if I could abstract this into a method call, I can wait for a method call to finish, but I can't figure out how to do that without using a Thread.Sleep call and freezing the program.
Any suggestions?
There is a fundamental error with this solution in that the randomizer should never be instantiated within the dice object itself. A simple test will show why. Simply add another dice object to the form and roll both at the same time. Notice something funny? They are always the same!
This is because, as a default, the randomizer used the current time to seed the generator. Creating two (or more) objects in the same part of code will result in all of the dice objects having the same seed, and thus the same result when rolled every time.
A better solution would involve creating a static singleton class that would handle all the rolling (randomizing) all the dice for you.
Here is a quick example (using a Dice class that is a bit more generic):
public static class DiceRoller
{
private static Random _roller;
public static void RollDice(Dice dice)
{
if (dice.Faces.Count < 1)
throw new InvalidOperationException("A dice must contain at least 1 side to be rolled.");
if (_roller == null)
_roller = new Random();
int index = _roller.Next(dice.Faces.Count);
dice.SetFacingIndex(index);
}
}
Just wrote a little Dice class which will provide the desired values to you:
public class Dice
{
private Random _Random;
private BackgroundWorker _Worker;
/// <summary>
/// Initializes a new instance of the <see cref="Dice"/> class.
/// </summary>
public Dice()
{
_Random = new Random();
InitializeDefaultValues();
InitializeBackgroundWorker();
}
/// <summary>
/// Occurs when the dice finished rolling.
/// </summary>
public event EventHandler Rolled;
/// <summary>
/// Occurs while the dice is rolling and the value has changed.
/// </summary>
public event EventHandler RollingChanged;
/// <summary>
/// Gets or sets the including maximum value that the dice can return.
/// </summary>
/// <value>
/// The maximum value.
/// </value>
[DefaultValue(6)]
public int Maximum { get; set; }
/// <summary>
/// Gets or sets the including minimum value that the dice can return.
/// </summary>
/// <value>
/// The minimum.
/// </value>
[DefaultValue(1)]
public int Minimum { get; set; }
/// <summary>
/// Gets the result that this dice currently has.
/// </summary>
public int Result { get; private set; }
/// <summary>
/// Gets or sets the duration of the rolling.
/// </summary>
/// <value>
/// The duration of the rolling.
/// </value>
[DefaultValue(typeof(TimeSpan), "00:00:03")]
public TimeSpan RollingDuration { get; set; }
/// <summary>
/// Starts rolling the dice.
/// </summary>
public void Roll()
{
if (!_Worker.IsBusy)
{
CheckParameters();
_Worker.RunWorkerAsync();
}
}
private void CheckParameters()
{
if (Minimum >= Maximum)
{
throw new InvalidOperationException("Minimum value must be less than the Maximum value.");
}
if (RollingDuration <= TimeSpan.Zero)
{
throw new InvalidOperationException("The RollingDuration must be greater zero.");
}
}
private void InitializeBackgroundWorker()
{
_Worker = new BackgroundWorker();
_Worker.WorkerReportsProgress = true;
_Worker.DoWork += OnWorkerDoWork;
_Worker.ProgressChanged += OnWorkerProgressChanged;
_Worker.RunWorkerCompleted += OnWorkerRunWorkerCompleted;
}
private void InitializeDefaultValues()
{
Minimum = 1;
Maximum = 6;
Result = Minimum;
RollingDuration = TimeSpan.FromSeconds(3);
}
private void OnWorkerDoWork(object sender, DoWorkEventArgs e)
{
var finishTime = DateTime.UtcNow + RollingDuration;
while (finishTime > DateTime.UtcNow)
{
Result = _Random.Next(Minimum, Maximum + 1);
_Worker.ReportProgress(0);
// ToDo: Improve sleep times for more realistic rolling.
Thread.Sleep(50);
}
}
private void OnWorkerProgressChanged(object sender, ProgressChangedEventArgs e)
{
RaiseEvent(RollingChanged);
}
private void OnWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
RaiseEvent(Rolled);
}
private void RaiseEvent(EventHandler handler)
{
var temp = handler;
if (temp != null)
{
temp(this, EventArgs.Empty);
}
}
}
In a first and simple example i simply added a button to a form (buttonRoll) and a label (labelDiceResult) and added the follwing code (don't forget to add the initialize method into the form constructor):
private void InitializeDice()
{
_Dice = new Dice();
_Dice.RollingChanged += OnDiceRollingChanged;
_Dice.Rolled += OnDiceRolled;
}
void OnDiceRolled(object sender, EventArgs e)
{
buttonRoll.Enabled = true;
}
void OnDiceRollingChanged(object sender, EventArgs e)
{
// ToDo: Select desired picture from image list depending on _Dice.Result
labelDiceResult.Text = _Dice.Result.ToString();
}
private void OnButtonRollClick(object sender, EventArgs e)
{
buttonRoll.Enabled = false;
_Dice.Roll();
}
As a last step i would maybe tweak the Thread.Sleep(50) call to use different values over time by using a calculated list depending on the rising part of a sinus and the desired duration to let the dice slow down over time. But i let this part open for the reader (or a next question).
I have a function that I want to invoke every x seconds, but I want it to be thread-safe.
Can I set up this behavior when I am creating the timer? (I don't mind which .NET timer I use, I just want it to be thread-safe).
I know I can implement locks inside my callback function, but I think it would be more elegant if it were in the timer level.
My callback function, and environment are not related to a UI.
[Edit 1]
I just don't want there to be more than one thread inside my callback function.
[Edit 2]
I want to keep the locking inside the timer level, because the timer is responsible for when to call my callback, and here there is a particular situation when I don't want to call my callback function. So I think when to call is the responsibility of the timer.
I'm guessing, as your question is not entirely clear, that you want to ensure that your timer cannot re-enter your callback whilst you are processing a callback, and you want to do this without locking. You can achieve this using a System.Timers.Timer and ensuring that the AutoReset property is set to false. This will ensure that you have to trigger the timer on each interval manually, thus preventing any reentrancy:
public class NoLockTimer : IDisposable
{
private readonly Timer _timer;
public NoLockTimer()
{
_timer = new Timer { AutoReset = false, Interval = 1000 };
_timer.Elapsed += delegate
{
//Do some stuff
_timer.Start(); // <- Manual restart.
};
_timer.Start();
}
public void Dispose()
{
if (_timer != null)
{
_timer.Dispose();
}
}
}
Complementing Tim Lloyd's solution for System.Timers.Timer, here's a solution to prevent reentrancy for cases where you want to use System.Threading.Timer instead.
TimeSpan DISABLED_TIME_SPAN = TimeSpan.FromMilliseconds(-1);
TimeSpan interval = TimeSpan.FromSeconds(1);
Timer timer = null; // assign null so we can access it inside the lambda
timer = new Timer(callback: state =>
{
doSomeWork();
try
{
timer.Change(interval, DISABLED_TIME_SPAN);
}
catch (ObjectDisposedException timerHasBeenDisposed)
{
}
}, state: null, dueTime: interval, period: DISABLED_TIME_SPAN);
I believe you don't want interval to be accessed inside of the callback, but that is be easy to fix, if you want to: Put the above into a NonReentrantTimer class that wraps the BCL's Timer class. You would then pass the doSomeWork callback in as a parameter. An example of such a class:
public class NonReentrantTimer : IDisposable
{
private readonly TimerCallback _callback;
private readonly TimeSpan _period;
private readonly Timer _timer;
public NonReentrantTimer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period)
{
_callback = callback;
_period = period;
_timer = new Timer(Callback, state, dueTime, DISABLED_TIME_SPAN);
}
private void Callback(object state)
{
_callback(state);
try
{
_timer.Change(_period, DISABLED_TIME_SPAN);
}
catch (ObjectDisposedException timerHasBeenDisposed)
{
}
}
public void Dispose()
{
_timer.Dispose();
}
}
I know I can implement locks inside my callback function, but I think it will be more elegant if it will be in the timer level
If locking is necessary then how could a timer arrange that? You're looking for a magical freebie.
Re Edit1:
Your choices are System.Timers.Timer and System.Threading.Timer, both need precautions against re-entrance. See this page and look for the Dealing with Timer Event Reentrance section.
using System;
using System.Diagnostics;
/// <summary>
/// Updated the code.
/// </summary>
public class NicerFormTimer : IDisposable {
public void Dispose() {
using ( this.Timer ) { }
GC.SuppressFinalize( this );
}
private System.Windows.Forms.Timer Timer { get; }
/// <summary>
/// Perform an <paramref name="action" /> after the given interval (in <paramref name="milliseconds" />).
/// </summary>
/// <param name="action"></param>
/// <param name="repeat">Perform the <paramref name="action" /> again. (Restarts the <see cref="Timer" />.)</param>
/// <param name="milliseconds"></param>
public NicerFormTimer( Action action, Boolean repeat, Int32? milliseconds = null ) {
if ( action == null ) {
return;
}
this.Timer = new System.Windows.Forms.Timer {
Interval = milliseconds.GetValueOrDefault( 1000 )
};
this.Timer.Tick += ( sender, args ) => {
try {
this.Timer.Stop();
action();
}
catch ( Exception exception ) {
Debug.WriteLine( exception );
}
finally {
if ( repeat ) {
this.Timer.Start();
}
}
};
this.Timer.Start();
}
}
/// <summary>
/// Updated the code.
/// </summary>
public class NicerSystemTimer : IDisposable {
public void Dispose() {
using ( this.Timer ) { }
GC.SuppressFinalize( this );
}
private System.Timers.Timer Timer { get; }
/// <summary>
/// Perform an <paramref name="action" /> after the given interval (in <paramref name="milliseconds" />).
/// </summary>
/// <param name="action"></param>
/// <param name="repeat">Perform the <paramref name="action" /> again. (Restarts the <see cref="Timer" />.)</param>
/// <param name="milliseconds"></param>
public NicerSystemTimer( Action action, Boolean repeat, Double? milliseconds = null ) {
if ( action == null ) {
return;
}
this.Timer = new System.Timers.Timer {
AutoReset = false,
Interval = milliseconds.GetValueOrDefault( 1000 )
};
this.Timer.Elapsed += ( sender, args ) => {
try {
this.Timer.Stop();
action();
}
catch ( Exception exception ) {
Debug.WriteLine( exception );
}
finally {
if ( repeat ) {
this.Timer.Start();
}
}
};
this.Timer.Start();
}
}
How timer could know about your shared data?
Timer callback is executed on some ThreadPool thread. So you will have at least 2 threads:
Your main thread where timer is created and launched;
Thread from ThreadPool for launching callback.
And it is your responsibility to provide correct work with your shared data.
Re edits: chibacity provided the perfect example.