Executing code on a schedule, and skipping a task - c#

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

Related

How do I add a timer in Xamarin?

So I need to have a timer to count down from 60 seconds. I am new to Xamarin and have no idea what it accepts. It will be used in Android.
Any suggestions on how to start?
Can you use System.Timers.Timer?
You can use the System.Threading.Timer class, which is documented in the Xamarin docs: https://developer.xamarin.com/api/type/System.Threading.Timer/
Alternatively, for Xamarin.Forms, you have access to a cross-platform Timer via the Device class:
Device.StartTimer(TimeSpan.FromSeconds(1), () =>
{
// called every 1 second
// do stuff here
return true; // return true to repeat counting, false to stop timer
});
If you just need Android you can use
System.Threading.Timer
In shared code with Xamarin Forms you can use
Device.StartTimer(...)
Or you can implement one yourselfe with advanced features like:
public sealed class Timer : CancellationTokenSource {
private readonly Action _callback;
private int _millisecondsDueTime;
private readonly int _millisecondsPeriod;
public Timer(Action callback, int millisecondsDueTime) {
_callback = callback;
_millisecondsDueTime = millisecondsDueTime;
_millisecondsPeriod = -1;
Start();
}
public Timer(Action callback, int millisecondsDueTime, int millisecondsPeriod) {
_callback = callback;
_millisecondsDueTime = millisecondsDueTime;
_millisecondsPeriod = millisecondsPeriod;
Start();
}
private void Start() {
Task.Run(() => {
if (_millisecondsDueTime <= 0) {
_millisecondsDueTime = 1;
}
Task.Delay(_millisecondsDueTime, Token).Wait();
while (!IsCancellationRequested) {
//TODO handle Errors - Actually the Callback should handle the Error but if not we could do it for the callback here
_callback();
if(_millisecondsPeriod <= 0) break;
if (_millisecondsPeriod > 0 && !IsCancellationRequested) {
Task.Delay(_millisecondsPeriod, Token).Wait();
}
}
});
}
public void Stop() {
Cancel();
}
protected override void Dispose(bool disposing) {
if (disposing) {
Cancel();
}
base.Dispose(disposing);
}
}
Yes, you can use System.Timers.Timer.
In Android we can use java.util.Timer like this:
private int i;
final Timer timer=new Timer();
TimerTask task=new TimerTask() {
#Override
public void run() {
if (i < 60) {
i++;
} else {
timer.cancel();
}
Log.e("time=",i+"");
}
};
timer.schedule(task, 0,1000);
In Xamarin.Android we can also use java.util.Timer like this:
[Activity(Label = "Tim", MainLauncher = true)]
public class MainActivity : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
Timer timer = new Timer();
timer.Schedule(new MyTask(timer),0,1000);
}
}
class MyTask : TimerTask
{
Timer mTimer;
public MyTask(Timer timer) {
this.mTimer = timer;
}
int i;
public override void Run()
{
if (i < 60)
{
i++;
}
else {
mTimer.Cancel();
}
Android.Util.Log.Error("time",i+"");
}
}
You can always use
Task.Factory.StartNewTaskContinuously(YourMethod, new CancellationToken(), TimeSpan.FromMinutes(10));
Be sure to add the following class to your project
static class Extensions
{
/// <summary>
/// Start a new task that will run continuously for a given amount of time.
/// </summary>
/// <param name="taskFactory">Provides access to factory methods for creating System.Threading.Tasks.Task and System.Threading.Tasks.Task`1 instances.</param>
/// <param name="action">The action delegate to execute asynchronously.</param>
/// <param name="cancellationToken">The System.Threading.Tasks.TaskFactory.CancellationToken that will be assigned to the new task.</param>
/// <param name="timeSpan">The interval between invocations of the callback.</param>
/// <returns>The started System.Threading.Tasks.Task.</returns>
public static Task StartNewTaskContinuously(this TaskFactory taskFactory, Action action, CancellationToken cancellationToken, TimeSpan timeSpan
, string taskName = "")
{
return taskFactory.StartNew(async () =>
{
if (!string.IsNullOrEmpty(taskName))
{
Debug.WriteLine("Started task " + taskName);
}
while (!cancellationToken.IsCancellationRequested)
{
action();
try
{
await Task.Delay(timeSpan, cancellationToken);
}
catch (Exception e)
{
break;
}
}
if (!string.IsNullOrEmpty(taskName))
{
Debug.WriteLine("Finished task " + taskName);
}
});
}
}

How to set start time for Stopwatch in C#? [duplicate]

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; }
}
}

How to dispose System.Threading.EventWaitHandle in Mono?

The System.Threading.EventWaitHandle type does not contain a definition for 'dispose'.
How am I supposed to dispose of the EventWaitHandle when I want to kill the object in which it is contained? I've been having a serious memory leak problem and I've tried setting the EventWaitHandle to null and the memory leak persists.
Here's my code (I'm using Bob Craven's library, I added only the dispose() method):
using System;
using System.Threading;
namespace Rlc.Cron
{
public class CronObject
{
public delegate void CronEvent(CronObject cronObject);
public event CronEvent OnCronTrigger;
public event CronEvent OnStarted;
public event CronEvent OnStopped;
public event CronEvent OnThreadAbort;
private CronObjectDataContext _cronObjectDataContext;
private Guid _id = Guid.NewGuid();
private object _startStopLock = new object();
private EventWaitHandle _wh = new AutoResetEvent(false);
private Thread _thread;
public bool _isStarted;
private bool _isStopRequested;
private DateTime _nextCronTrigger;
public Guid Id { get { return _id; } }
public object Object { get { return _cronObjectDataContext.Object; } }
public DateTime LastTigger { get { return _cronObjectDataContext.LastTrigger; } }
/// <summary>
/// Initializes a new instance of the <see cref="CronObject"/> class.
/// </summary>
/// <param name="cronObjectDataContext">The cron object data context.</param>
public CronObject(CronObjectDataContext cronObjectDataContext)
{
if (cronObjectDataContext == null)
{
throw new ArgumentNullException("cronObjectDataContext");
}
if (cronObjectDataContext.Object == null)
{
throw new ArgumentException("cronObjectDataContext.Object");
}
if (cronObjectDataContext.CronSchedules == null || cronObjectDataContext.CronSchedules.Count == 0)
{
throw new ArgumentException("cronObjectDataContext.CronSchedules");
}
_cronObjectDataContext = cronObjectDataContext;
}
/// <summary>
/// Starts this instance.
/// </summary>
/// <returns></returns>
public bool Start()
{
lock (_startStopLock)
{
// Can't start if already started.
//
if (_isStarted)
{
return false;
}
_isStarted = true;
_isStopRequested = false;
// This is a long running process. Need to run on a thread
// outside the thread pool.
//
_thread = new Thread(ThreadRoutine);
_thread.Start();
}
// Raise the started event.
//
if(OnStarted != null)
{
OnStarted(this);
}
return true;
}
/// <summary>
/// Stops this instance.
/// </summary>
/// <returns></returns>
public bool Stop()
{
lock (_startStopLock)
{
// Can't stop if not started.
//
if (!_isStarted)
{
return false;
}
_isStarted = false;
_isStopRequested = true;
// Signal the thread to wake up early
//
_wh.Set();
// Wait for the thread to join.
//
if(!_thread.Join(5000))
{
_thread.Abort();
// Raise the thread abort event.
//
if(OnThreadAbort != null)
{
OnThreadAbort(this);
}
}
}
// Raise the stopped event.
//
if(OnStopped != null)
{
OnStopped(this);
}
return true;
}
public void dispose(){
this.Stop ();
this.OnCronTrigger = null;
this.OnStarted=null;
this.OnStopped=null;
this.OnThreadAbort=null;
this._cronObjectDataContext=null;
this._startStopLock = null;
this._wh = null;
this._thread=null;
}
/// <summary>
/// Cron object thread routine.
/// </summary>
private void ThreadRoutine()
{
// Continue until stop is requested.
//
while(!_isStopRequested)
{
// Determine the next cron trigger
//
DetermineNextCronTrigger(out _nextCronTrigger);
TimeSpan sleepSpan = _nextCronTrigger - DateTime.Now;
if(sleepSpan.TotalMilliseconds < 0)
{
// Next trigger is in the past. Trigger the right away.
//
sleepSpan = new TimeSpan(0, 0, 0, 0, 50);
}
// Wait here for the timespan or until I am triggered
// to wake up.
//
if(!_wh.WaitOne(sleepSpan))
{
// Timespan is up...raise the trigger event
//
if(OnCronTrigger != null)
{
OnCronTrigger(this);
}
// Update the last trigger time.
//
_cronObjectDataContext.LastTrigger = DateTime.Now;
}
}
}
/// <summary>
/// Determines the next cron trigger.
/// </summary>
/// <param name="nextTrigger">The next trigger.</param>
private void DetermineNextCronTrigger(out DateTime nextTrigger)
{
nextTrigger = DateTime.MaxValue;
foreach (CronSchedule cronSchedule in _cronObjectDataContext.CronSchedules)
{
DateTime thisTrigger;
if(cronSchedule.GetNext(LastTigger, out thisTrigger))
{
if (thisTrigger < nextTrigger)
{
nextTrigger = thisTrigger;
}
}
}
}
~CronObject(){
Console.WriteLine ("===================CRONOBJECT DESTROYED!!===============");
}
}
}

SemaphoreSlim with dynamic maxCount

I'm facing a problem where I need to limit the number of calls to another web server. It will vary because the server is shared and maybe it could have more or less capacity.
I was thinking about using SemaphoreSlim class, but there's no public property to change the max count.
Should I wrap my SemaphoreSlim class in another class that will handle the max count? Is there any better approach?
EDIT:
Here's what I'm trying:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Semaphore
{
class Program
{
static SemaphoreSlim _sem = new SemaphoreSlim(10,10000);
static void Main(string[] args)
{
int max = 15;
for (int i = 1; i <= 50; i++)
{
new Thread(Enter).Start(new int[] { i, max});
}
Console.ReadLine();
max = 11;
for (int i = 1; i <= 50; i++)
{
new Thread(Enter).Start(new int[] { i, max });
}
}
static void Enter(object param)
{
int[] arr = (int[])param;
int id = arr[0];
int max = arr[1];
try
{
Console.WriteLine(_sem.CurrentCount);
if (_sem.CurrentCount <= max)
_sem.Release(1);
else
{
_sem.Wait(1000);
Console.WriteLine(id + " wants to enter");
Thread.Sleep((1000 * id) / 2); // can be here at
Console.WriteLine(id + " is in!"); // Only three threads
}
}
catch(Exception ex)
{
Console.WriteLine("opps ", id);
Console.WriteLine(ex.Message);
}
finally
{
_sem.Release();
}
}
}
}
Questions:
1-_sem.Wait(1000) should cancel the execution of threads that will execute for more than 1000ms, wasn't it?
2-Did I got the idea of using Release / Wait?
You can't change the max count, but you can create a SemaphoreSlim that has a very high maximum count, and reserve some of them. See this constructor.
So let's say that the absolute maximum number of concurrent calls is 100, but initially you want it to be 25. You initialize your semaphore:
SemaphoreSlim sem = new SemaphoreSlim(25, 100);
So 25 is the number of requests that can be serviced concurrently. You have reserved the other 75.
If you then want to increase the number allowed, just call Release(num). If you called Release(10), then the number would go to 35.
Now, if you want to reduce the number of available requests, you have to call WaitOne multiple times. For example, if you want to remove 10 from the available count:
for (var i = 0; i < 10; ++i)
{
sem.WaitOne();
}
This has the potential of blocking until other clients release the semaphore. That is, if you allow 35 concurrent requests and you want to reduce it to 25, but there are already 35 clients with active requests, that WaitOne will block until a client calls Release, and the loop won't terminate until 10 clients release.
Get a semaphore.
Set the capacity to something quite a bit higher than you need it to be.
Set the initial capacity to what you want your actual maximum capacity to be.
Give out the semaphore to others to use.
At this point you can then wait on the semaphore however much you want (without a corresponding release call) to lower the capacity. You can release the semaphore a number of times (without a corresponding wait call) to increase the effective capacity.
If this is something you're doing enough of, you can potentially create your own semaphore class that composes a SemaphoreSlim and encapsulates this logic. This composition will also be essential if you have code that already releases a semaphore without first waiting on it; with your own class you could ensure that such releases are no-ops. (That said, you should avoid putting yourself in that position to begin with, really.)
Here is how I solved this situation: I created a custom semaphore slim class that allows me to increase and decrease the number of slots. This class also allows me to set a maximum number of slots so I never exceed a "reasonable" number and also to set a minimum number of slots so I don't go below a "reasonable" threshold.
using Picton.Messaging.Logging;
using System;
using System.Threading;
namespace Picton.Messaging.Utils
{
/// <summary>
/// An improvement over System.Threading.SemaphoreSlim that allows you to dynamically increase and
/// decrease the number of threads that can access a resource or pool of resources concurrently.
/// </summary>
/// <seealso cref="System.Threading.SemaphoreSlim" />
public class SemaphoreSlimDynamic : SemaphoreSlim
{
#region FIELDS
private static readonly ILog _logger = LogProvider.GetLogger(typeof(SemaphoreSlimDynamic));
private readonly ReaderWriterLockSlim _lock;
#endregion
#region PROPERTIES
/// <summary>
/// Gets the minimum number of slots.
/// </summary>
/// <value>
/// The minimum slots count.
/// </value>
public int MinimumSlotsCount { get; private set; }
/// <summary>
/// Gets the number of slots currently available.
/// </summary>
/// <value>
/// The available slots count.
/// </value>
public int AvailableSlotsCount { get; private set; }
/// <summary>
/// Gets the maximum number of slots.
/// </summary>
/// <value>
/// The maximum slots count.
/// </value>
public int MaximumSlotsCount { get; private set; }
#endregion
#region CONSTRUCTOR
/// <summary>
/// Initializes a new instance of the <see cref="SemaphoreSlimDynamic"/> class.
/// </summary>
/// <param name="minCount">The minimum number of slots.</param>
/// <param name="initialCount">The initial number of slots.</param>
/// <param name="maxCount">The maximum number of slots.</param>
public SemaphoreSlimDynamic(int minCount, int initialCount, int maxCount)
: base(initialCount, maxCount)
{
_lock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);
this.MinimumSlotsCount = minCount;
this.AvailableSlotsCount = initialCount;
this.MaximumSlotsCount = maxCount;
}
#endregion
#region PUBLIC METHODS
/// <summary>
/// Attempts to increase the number of slots
/// </summary>
/// <param name="millisecondsTimeout">The timeout in milliseconds.</param>
/// <param name="increaseCount">The number of slots to add</param>
/// <returns>true if the attempt was successfully; otherwise, false.</returns>
public bool TryIncrease(int millisecondsTimeout = 500, int increaseCount = 1)
{
return TryIncrease(TimeSpan.FromMilliseconds(millisecondsTimeout), increaseCount);
}
/// <summary>
/// Attempts to increase the number of slots
/// </summary>
/// <param name="timeout">The timeout.</param>
/// <param name="increaseCount">The number of slots to add</param>
/// <returns>true if the attempt was successfully; otherwise, false.</returns>
public bool TryIncrease(TimeSpan timeout, int increaseCount = 1)
{
if (increaseCount < 0) throw new ArgumentOutOfRangeException(nameof(increaseCount));
else if (increaseCount == 0) return false;
var increased = false;
try
{
if (this.AvailableSlotsCount < this.MaximumSlotsCount)
{
var lockAcquired = _lock.TryEnterWriteLock(timeout);
if (lockAcquired)
{
for (int i = 0; i < increaseCount; i++)
{
if (this.AvailableSlotsCount < this.MaximumSlotsCount)
{
Release();
this.AvailableSlotsCount++;
increased = true;
}
}
if (increased) _logger.Trace($"Semaphore slots increased: {this.AvailableSlotsCount}");
_lock.ExitWriteLock();
}
}
}
catch (SemaphoreFullException)
{
// An exception is thrown if we attempt to exceed the max number of concurrent tasks
// It's safe to ignore this exception
}
return increased;
}
/// <summary>
/// Attempts to decrease the number of slots
/// </summary>
/// <param name="millisecondsTimeout">The timeout in milliseconds.</param>
/// <param name="decreaseCount">The number of slots to add</param>
/// <returns>true if the attempt was successfully; otherwise, false.</returns>
public bool TryDecrease(int millisecondsTimeout = 500, int decreaseCount = 1)
{
return TryDecrease(TimeSpan.FromMilliseconds(millisecondsTimeout), decreaseCount);
}
/// <summary>
/// Attempts to decrease the number of slots
/// </summary>
/// <param name="timeout">The timeout.</param>
/// <param name="decreaseCount">The number of slots to add</param>
/// <returns>true if the attempt was successfully; otherwise, false.</returns>
public bool TryDecrease(TimeSpan timeout, int decreaseCount = 1)
{
if (decreaseCount < 0) throw new ArgumentOutOfRangeException(nameof(decreaseCount));
else if (decreaseCount == 0) return false;
var decreased = false;
if (this.AvailableSlotsCount > this.MinimumSlotsCount)
{
var lockAcquired = _lock.TryEnterWriteLock(timeout);
if (lockAcquired)
{
for (int i = 0; i < decreaseCount; i++)
{
if (this.AvailableSlotsCount > this.MinimumSlotsCount)
{
if (Wait(timeout))
{
this.AvailableSlotsCount--;
decreased = true;
}
}
}
if (decreased) _logger.Trace($"Semaphore slots decreased: {this.AvailableSlotsCount}");
_lock.ExitWriteLock();
}
}
return decreased;
}
#endregion
}
}
Ok, I could solve my problem lookin on mono project.
// SemaphoreSlim.cs
//
// Copyright (c) 2008 Jérémie "Garuma" Laval
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
//
using System;
using System.Diagnostics;
using System.Threading.Tasks;
namespace System.Threading
{
public class SemaphoreSlimCustom : IDisposable
{
const int spinCount = 10;
const int deepSleepTime = 20;
private object _sync = new object();
int maxCount;
int currCount;
bool isDisposed;
public int MaxCount
{
get { lock (_sync) { return maxCount; } }
set
{
lock (_sync)
{
maxCount = value;
}
}
}
EventWaitHandle handle;
public SemaphoreSlimCustom (int initialCount) : this (initialCount, int.MaxValue)
{
}
public SemaphoreSlimCustom (int initialCount, int maxCount)
{
if (initialCount < 0 || initialCount > maxCount || maxCount < 0)
throw new ArgumentOutOfRangeException ("The initialCount argument is negative, initialCount is greater than maxCount, or maxCount is not positive.");
this.maxCount = maxCount;
this.currCount = initialCount;
this.handle = new ManualResetEvent (initialCount > 0);
}
public void Dispose ()
{
Dispose(true);
}
protected virtual void Dispose (bool disposing)
{
isDisposed = true;
}
void CheckState ()
{
if (isDisposed)
throw new ObjectDisposedException ("The SemaphoreSlim has been disposed.");
}
public int CurrentCount {
get {
return currCount;
}
}
public int Release ()
{
return Release(1);
}
public int Release (int releaseCount)
{
CheckState ();
if (releaseCount < 1)
throw new ArgumentOutOfRangeException ("releaseCount", "releaseCount is less than 1");
// As we have to take care of the max limit we resort to CAS
int oldValue, newValue;
do {
oldValue = currCount;
newValue = (currCount + releaseCount);
newValue = newValue > maxCount ? maxCount : newValue;
} while (Interlocked.CompareExchange (ref currCount, newValue, oldValue) != oldValue);
handle.Set ();
return oldValue;
}
public void Wait ()
{
Wait (CancellationToken.None);
}
public bool Wait (TimeSpan timeout)
{
return Wait ((int)timeout.TotalMilliseconds, CancellationToken.None);
}
public bool Wait (int millisecondsTimeout)
{
return Wait (millisecondsTimeout, CancellationToken.None);
}
public void Wait (CancellationToken cancellationToken)
{
Wait (-1, cancellationToken);
}
public bool Wait (TimeSpan timeout, CancellationToken cancellationToken)
{
CheckState();
return Wait ((int)timeout.TotalMilliseconds, cancellationToken);
}
public bool Wait (int millisecondsTimeout, CancellationToken cancellationToken)
{
CheckState ();
if (millisecondsTimeout < -1)
throw new ArgumentOutOfRangeException ("millisecondsTimeout",
"millisecondsTimeout is a negative number other than -1");
Stopwatch sw = Stopwatch.StartNew();
Func<bool> stopCondition = () => millisecondsTimeout >= 0 && sw.ElapsedMilliseconds > millisecondsTimeout;
do {
bool shouldWait;
int result;
do {
cancellationToken.ThrowIfCancellationRequested ();
if (stopCondition ())
return false;
shouldWait = true;
result = currCount;
if (result > 0)
shouldWait = false;
else
break;
} while (Interlocked.CompareExchange (ref currCount, result - 1, result) != result);
if (!shouldWait) {
if (result == 1)
handle.Reset ();
break;
}
SpinWait wait = new SpinWait ();
while (Thread.VolatileRead (ref currCount) <= 0) {
cancellationToken.ThrowIfCancellationRequested ();
if (stopCondition ())
return false;
if (wait.Count > spinCount) {
int diff = millisecondsTimeout - (int)sw.ElapsedMilliseconds;
int timeout = millisecondsTimeout < 0 ? deepSleepTime :
Math.Min (Math.Max (diff, 1), deepSleepTime);
handle.WaitOne (timeout);
} else
wait.SpinOnce ();
}
} while (true);
return true;
}
public WaitHandle AvailableWaitHandle {
get {
return handle;
}
}
public Task WaitAsync ()
{
return Task.Factory.StartNew (() => Wait ());
}
public Task WaitAsync (CancellationToken cancellationToken)
{
return Task.Factory.StartNew (() => Wait (cancellationToken), cancellationToken);
}
public Task<bool> WaitAsync (int millisecondsTimeout)
{
return Task.Factory.StartNew (() => Wait (millisecondsTimeout));
}
public Task<bool> WaitAsync (TimeSpan timeout)
{
return Task.Factory.StartNew (() => Wait (timeout));
}
public Task<bool> WaitAsync (int millisecondsTimeout, CancellationToken cancellationToken)
{
return Task.Factory.StartNew (() => Wait (millisecondsTimeout, cancellationToken), cancellationToken);
}
public Task<bool> WaitAsync (TimeSpan timeout, CancellationToken cancellationToken)
{
return Task.Factory.StartNew (() => Wait (timeout, cancellationToken), cancellationToken);
}
}
}
Updated .Net Core 5 answer:
Let's say I want a lock with a maximum of 10 requests, but most of the time I only want 1.
private readonly static SemaphoreSlim semLock = new(1, 10);
Now when I want to release some resources I can do:
semLock.Release(Math.Min(9, requiredAmount));
note that 9 is one less than 10 as we already have one release initially.
Once I want to restrict the available resources again I can call:
while(semLock.CurrentCount > 1)
{
await semLock.WaitAsync();
}
which will await bringing it back down to 1

Start a stopwatch from specified time

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; }
}
}

Categories

Resources