Running a task periodically with a cancellation token called from another method - c#

I am trying to run a screenshot task every x amount of minutes, but it seems that when i want to cancel that task using the given cancellation token, it doesn't seem to do anything.
Here is my start method code:
var CancellationTokenSource = new CancellationTokenSource();
CancellationTokenSource?.Cancel();
CancellationTokenSource = new CancellationTokenSource();
var token = CancellationTokenSource.Token;
await RunPeriodically(async () =>
{
var screenCaptured = TakeScreenshot();
if (screenCaptured == null || CancellationTokenSource.Token.IsCancellationRequested)
return;
var correctUserName = Settings.Default.Username.Split('.');
var parsedUsername = correctUserName[0] + " " + correctUserName[1];
await ScreenshotHelper.UploadScreenshotAsync(ProjectName, "screenshotscontainer",
screenCaptured.ToArray(), Environment.MachineName, parsedUsername);
Console.WriteLine("Took Screenshot: " + DateTime.Now.ToString(CultureInfo.InvariantCulture));
}, TimeSpan.FromSeconds(3), token);
and here is the run periodically code:
public async Task RunPeriodically(Action action, TimeSpan interval, CancellationToken token)
{
while (true)
{
action();
await Task.Delay(interval, token);
}
}

I thought I'd knock up a quick example of a class that will run a process, wait a period of time and start the process again without blocking any threads.
class RepeatableProcess
{
private Timer processTimer;
private int delay;
private CancellationTokenSource source;
private CancellationToken token;
private Action processToRun;
private bool canStart = true;
public RepeatableProcess(int delaySeconds,Action process)
{
delay = delaySeconds;
processToRun = process;
}
public void Start()
{
if (canStart)
{
canStart = false;
source = new CancellationTokenSource();
token = source.Token;
processTimer = new Timer(TimedProcess, token, Timeout.Infinite, Timeout.Infinite);
processTimer.Change(0, Timeout.Infinite);
}
}
public void Stop()
{
source.Cancel();
}
public void TimedProcess(object state)
{
CancellationToken ct = (CancellationToken)state;
if (ct.IsCancellationRequested)
{
Console.WriteLine("Timer Stopped");
processTimer.Dispose();
canStart = true;
}
else
{
processToRun.Invoke();
processTimer.Change(delay, Timeout.Infinite);
}
}
}
The Start method creates a timer that never starts and never repeats.
It then starts the process immediately, to run only once.
The TimedProcess method checks for cancellation and runs the specified process . After the process completes the timer is set to start after the specified delay and run only once.
When the timer fires it gets a thread from the thread pool. There can be no overrun issues because the timer is not set to run again until the process is finished.
This class would need more protection but it is just an example.
Hope this helps.

Related

How can I cancel first ongoing click event when user click on it again?

I have a button click event handler in which I need to have 3 sec delay to make some flag true ..so it takes time to completely execute the function now meantime if the user click on the button again then this is making flag true for second click also...so I want to cancel the first click event as soon as I receive another click request.
This is my code :
private async void ClickEventHandler(ClickEvent obj)
{
int indexOfSelectedItem = this.List.IndexOf(this.List.FirstOrDefault(x => Convert.ToDouble(x.Value) == obj.Value));
if (indexOfSelectedItem > -1)
{
for (int i = 0; i < indexOfSelectedItem; i++)
{
var item = this.List.ElementAtOrDefault(0);
this.List.RemoveAt(0);
this.List.Add(item);
}
this.IsScrollEnabled = false;
await Task.Delay(3000);
this.IsScrollEnabled = true;
}
}
Yes I need to cancel the execution of the first method and call the method again ..so that it will wait for 3 sec after clicking it on second time
A simple example with a cancellation token:
private CancellationTokenSource tokenSource = new();
private async void ClickEventHandler(ClickEvent obj)
{
// Since this method is called from the clicker,
// it always starts on the main thread. Therefore,
// there is no need for additional Thread-Safe.
tokenSource.Cancel();
tokenSource = new();
CancellationToken token = tokenSource.Token;
int indexOfSelectedItem = this.List.IndexOf(this.List.FirstOrDefault(x => Convert.ToDouble(x.Value) == obj.Value));
if (indexOfSelectedItem > -1)
{
for (int i = 0; i < indexOfSelectedItem; i++)
{
var item = this.List.ElementAtOrDefault(0);
this.List.RemoveAt(0);
this.List.Add(item);
}
this.IsScrollEnabled = false;
try
{
// If the cancellation is during the Delay, then an exception will be exited.
await Task.Delay(3000, token);
this.IsScrollEnabled = true;
}
catch (Exception)
{
// Here, if necessary, actions in case the method is canceled.
}
}
}
P.S. In the example, the token is checked only in the Delay(...) method. If you need to check somewhere else, then insert a call to the method token.ThrowIfCancellationRequested(); into this place.
you could change a variable when the button gets clicked and change it back after it is done and then add an if statement checking the variable
As like xceing said , you can have a variable to check if already clicked and yet to complete the process.
Here is a sample
//keep a variable to check already clicked and yet to complete the action
bool clickEventInProgress = false;
private async void ClickEventHandler(ClickEvent obj)
{
//check it before start processing click action
if (!clickEventInProgress)
{
clickEventInProgress = true;
int indexOfSelectedItem = this.List.IndexOf(this.List.FirstOrDefault(x => Convert.ToDouble(x.Value) == obj.Value));
if (indexOfSelectedItem > -1)
{
for (int i = 0; i < indexOfSelectedItem; i++)
{
var item = this.List.ElementAtOrDefault(0);
this.List.RemoveAt(0);
this.List.Add(item);
}
this.IsScrollEnabled = false;
await Task.Delay(3000);
this.IsScrollEnabled = true;
}
clickEventInProgress = false;
}
}
you can make the variable false, one the process completed . So that next click operation will work fine.
It's not clear what exactly you are doing. A general solution could execute the cancellable task using Task.Run and then cancel it using a CancellationTokenSource. It's important to pass the associated CancellationToken to the Task API (and any asynchronous API that supports cancellation in general) too in order to enable full cancellation support e.g. cancellation of Task.Delay:
MainWindow.xaml
<Window>
<Button Content="Go!"
Click="OnClick" />
</Window>
MainWindow.xaml.cs
partial class MainWindow : Window
{
private CancellationTokenSource CancellationTokenSource { get; set; }
private SemaphoreSlim Semaphore { get; } = new SemaphoreSlim(1, 1);
public MainWindow() => InitializeComponent();
private async void OnClick(object sender, RoutedEventArgs e)
{
// If there is nothing to cancel, the reference is NULL
this.CancellationTokenSource?.Cancel();
// Wait for the previous operation to be cancelled.
// If there is nothing to cancel the SemaphoreSlim has a free slot
// and the execution continues.
await this.Semaphore.WaitAsync();
try
{
using (this.CancellationTokenSource = new CancellationTokenSource())
{
await RunCancellableOperationAsync(this.CancellationTokenSource.Token);
}
}
catch (OperationCanceledException)
{
// Invalidate disposed object to make it unusable
this.CancellationTokenSource = null;
}
finally // Cancellation completed
{
this.Semaphore.Release();
}
}
private async Task RunCancellableOperationAsync(CancellationToken cancellationToken)
{
// Execute blocking code concurrently to enable cancellation
await Task.Run(() =>
{
for (int index = 0; index < 1000; index++)
{
// Abort the iteration if requested
cancellationToken.ThrowIfCancellationRequested();
// Simulate do something
Thread.Sleep(5000);
}
}, cancellationToken);
// Support cancellation of the delay
await Task.Delay(TimeSpan.FromSeconds(3), cancellationToken);
}
}

How to run 10 long-running functions repeatedly at specific times, avoiding overlapping execution?

I am creating a .NET core worker service that is intended to run 10 different long running functions which parse various CSV files and spreadsheets.
I need each function to run at various times daily based on the specific function. If the very same function is currently running then it must 'await' that same function to finish before a new run.
I am new to async/await. Could anyone suggest an approach that would allow all 10 functions to run in parallel but never the same function at once?
Thanks in advance!
EDIT:
These parsers take anywhere from 5 minutes to 5 hrs to run.
Each function has its own unique needs of when exactly when to run
daily or even hourly.
If a current function is running and the same function is up to run
again, the next function should be removed until the next scheduled time and repeat
if needed
Here is a CronosTimer class similar in shape with the System.Timers.Timer class, that fires the Elapsed event on dates and times specified with a Cron expression. The event is fired in a non-overlapping manner. The CronosTimer has a dependency on the Cronos library by Sergey Odinokov. This library is a TimeSpan calculator, not a scheduler. Caveat: in its current version (0.7.1), the Cronos library is capped to the year 2099.
using Cronos;
/// <summary>
/// Generates non-overlapping events according to a Cron expression.
/// </summary>
public class CronosTimer : IAsyncDisposable
{
private readonly System.Threading.Timer _timer; // Used also as the locker.
private readonly CronExpression _cronExpression;
private readonly CancellationTokenSource _cts;
private Func<CancellationToken, Task> _handler;
private Task _activeTask;
private bool _disposed;
private static readonly TimeSpan _minDelay = TimeSpan.FromMilliseconds(500);
public CronosTimer(string expression, CronFormat format = CronFormat.Standard)
{
_cronExpression = CronExpression.Parse(expression, format);
_cts = new();
_timer = new(async _ =>
{
Task task;
lock (_timer)
{
if (_disposed) return;
if (_activeTask is not null) return;
if (_handler is null) return;
Func<CancellationToken, Task> handler = _handler;
CancellationToken token = _cts.Token;
_activeTask = task = Task.Run(() => handler(token));
}
try { await task.ConfigureAwait(false); }
catch (OperationCanceledException) when (_cts.IsCancellationRequested) { }
finally
{
lock (_timer)
{
Debug.Assert(ReferenceEquals(_activeTask, task));
_activeTask = null;
if (!_disposed && _handler is not null) ScheduleTimer();
}
}
});
}
private void ScheduleTimer()
{
Debug.Assert(Monitor.IsEntered(_timer));
Debug.Assert(!_disposed);
Debug.Assert(_handler is not null);
DateTime utcNow = DateTime.UtcNow;
DateTime? utcNext = _cronExpression.GetNextOccurrence(utcNow + _minDelay);
if (utcNext is null)
throw new InvalidOperationException("Unreachable date.");
TimeSpan delay = utcNext.Value - utcNow;
Debug.Assert(delay > _minDelay);
_timer.Change(delay, Timeout.InfiniteTimeSpan);
}
/// <summary>
/// Occurs when the next occurrence of the Cron expression has been reached,
/// provided that the previous asynchronous operation has completed.
/// The CancellationToken argument is canceled when the timer is disposed.
/// </summary>
public event Func<CancellationToken, Task> Elapsed
{
add
{
if (value is null) return;
lock (_timer)
{
if (_disposed) return;
if (_handler is not null) throw new InvalidOperationException(
"More than one handlers are not supported.");
_handler = value;
if (_activeTask is null) ScheduleTimer();
}
}
remove
{
if (value is null) return;
lock (_timer)
{
if (_disposed) return;
if (!ReferenceEquals(_handler, value)) return;
_handler = null;
_timer.Change(Timeout.Infinite, Timeout.Infinite);
}
}
}
/// <summary>
/// Returns a ValueTask that completes when all work associated with the timer
/// has ceased.
/// </summary>
public async ValueTask DisposeAsync()
{
Task task;
lock (_timer)
{
if (_disposed) return;
_disposed = true;
_handler = null;
task = _activeTask;
}
await _timer.DisposeAsync().ConfigureAwait(false);
_cts.Cancel();
if (task is not null)
try { await task.ConfigureAwait(false); } catch { }
_cts.Dispose();
}
}
Usage example:
CronosTimer timer = new("30 6,14,22 * * MON-FRI");
timer.Elapsed += async _ =>
{
try
{
await LongRunningAsync();
}
catch (Exception ex)
{
_logger.LogError(ex);
}
};
In this example the LongRunningAsync function will run at 6:30, 14:30 and 22:30 of every working day of the week.
You can find detailed documentation about the format of the Cron expressions here.
For simplicity, the Elapsed event supports only one handler at a time. Subscribing twice with += without unsubscribing with -= results in an exception.
How about using a WaitHandle?
// set intial state to signaled so the first worker can enter
AutoResetEvent done = new AutoResetEvent(true);
public async Task DoWork(){
// Wait at most 1 ms to acquire the handle
if(!done.WaitOne(1)) return;
// do some stuff here
// Release handle to other threads
done.Set();
}
This guarantees only one thread will be doing the work at a time.
For more information on AutoResetEvent
While I use "await/async" a lot it does not mean I use them always. In such case I would use timers (with single trigger, non-repeating), and at the end of each function (or wrapper for it) I would set timer again. This guarantees that execution will not overlap.
Curious about thoughts on this approach.
Worker class:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
//If(NOT SCHEDULED TIME RETURN) //QUARTZ JOB SCHEDULING HERE???
_tasksManager.LongProccess1Async(stoppingToken);
_tasksManager.LongProccess2Async(stoppingToken);
await Task.Delay(_intDelay, stoppingToken); //1 * 1000 = 1 second
}
}
Task Manager Class:
private Task<long>? _taskLongProccess1;
//private long _taskParseDelimitedFiles;
public async Task longProccess1Async(CancellationToken stoppingToken)
{
var pc = _parserConfigs.Find(p => p.Name == "longProccess1");
while (!stoppingToken.IsCancellationRequested)
{
if (_taskLongProccess1 == null)
_taskLongProccess1 = Task.Run(() => _delimitedParser.LongProccess1(pc.FilePath, pc.Delimiter, pc.ConnectionString, pc.Schema, pc.BulkSize));
if (_taskLongProccess1.Status == TaskStatus.Running)
{
await Task.Delay(pc.Delay, stoppingToken);
}
else if (_taskLongProccess1.Status == TaskStatus.RanToCompletion)
{
//ONCE DONE LOG AND NULL TASK
LoggingFunctions.addToLog(_logger, $"Total execution time for task:LongProccess1 = {_taskLongProccess1}", LoggingHelpers.InformationCode);
_taskLongProccess1 = null;
}
}
}
private Task<long>? _taskLongProccess2;
//private long _taskParseDelimitedFiles;
public async Task longProccess2Async(CancellationToken stoppingToken)
{
var pc = _parserConfigs.Find(p => p.Name == "longProccess2");
while (!stoppingToken.IsCancellationRequested)
{
if (_taskLongProccess2 == null)
_taskLongProccess2 = Task.Run(() => _delimitedParser.LongProccess2(pc.FilePath, pc.Delimiter, pc.ConnectionString, pc.Schema, pc.BulkSize));
if (_taskLongProccess2.Status == TaskStatus.Running)
{
await Task.Delay(pc.Delay, stoppingToken);
}
else if (_taskLongProccess2.Status == TaskStatus.RanToCompletion)
{
//ONCE DONE LOG AND NULL TASK
LoggingFunctions.addToLog(_logger, $"Total execution time for task:LongProccess1 = {_taskLongProccess2}", LoggingHelpers.InformationCode);
_taskLongProccess2 = null;
}
}
}

C#: Task cancellation not working (CancellationTokenSource)

I have some long running code that I would like to run as a Task and cancel when needed using CancellationTokenSource but cancellation doesn't seem to work as my task keeps running when tokenSource.Cancel() is called (no exception thrown).
Probably missing something obvious?
Cut down example below:
bool init = false;
private void Button1_Click(object sender, EventArgs e)
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
Task task = new Task(() =>
{
while (true)
{
token.ThrowIfCancellationRequested();
if (token.IsCancellationRequested)
{
Console.WriteLine("Operation is going to be cancelled");
throw new Exception("Task cancelled");
}
else
{
// do some work
}
}
}, token);
if (init)
{
tokenSource.Cancel();
button1.Text = "Start again";
init = false;
} else
{
try
{
task.Start();
} catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
button1.Text = "Cancel";
init = true;
}
}
Main issue in your code is that you don't store a tokenSource. Second Button1_Click invocation cancels different token than you pass to task during first call.
Second issue is that you create over and over again new task, but your logic suggest that you want one task which should be created on first click and terminated during second click.

C# Task.Delay blocks second task

I want to run two tasks.
StartAccessTokenTimer() runs every 60 seconds and refreshes accesstoken variable.
StartItemsTimer() will start after StartAccessTokenTimer() and work every 3 seconds if access token get.
private accessToken = "";
private async Task StartAccessTokenTimer()
{
CancellationTokenSource source = new CancellationTokenSource();
while (true)
{
accesstoken = await GetAccessToken();
await Task.Delay(TimeSpan.FromSeconds(3), source.Token);
}
}
private async Task StartItemsTimer()
{
CancellationTokenSource source = new CancellationTokenSource();
while (true)
{
var items = await GetItems(accessToken, "1");
await Task.Delay(TimeSpan.FromSeconds(60), source.Token);
}
}
public async Task StartOperations(){
await StartAccessTokenTimer();
await StartItemsTimer();
}
But it does not filre GetItems() methot. Because StartAccessTokenTimer() never start.. It fires GetAccessToken() continiously.
To trigger them to fire at the same time you can do the following:
public async Task StartOperations()
{
await Task.WhenAll(new Task[]
{
StartAccessTokenTimer(),
StartItemsTimer()
});
}

Using Task to run continuous Thread

I want run a thread continuously. This thread would poll and check for card status. Here is a sample implementation:
static void Main(string[] args)
{
var _cancelationTokenSource = new CancellationTokenSource();
new Task(() => chkRequestTask(_cancelationTokenSource), _cancelationTokenSource.Token, TaskCreationOptions.LongRunning).Start();
while (true)
{
}
}
static bool chkRequestTask(CancellationTokenSource _cancellationTokenSource)
{
bool noRequest = false;
while (!_cancellationTokenSource.Token.IsCancellationRequested)
{
var RequestTask = Task.Factory.StartNew(() => noRequest = chkRequestTask(_cancellationTokenSource), _cancellationTokenSource.Token);
if (noRequest)
{
_cancellationTokenSource.Token.WaitHandle.WaitOne(15000);
Console.WriteLine("Waiting for 15Seconds");
}
else
{
Console.WriteLine("Checking the card");
}
}
return noRequest;
}
What I want to achieve here is chkRequestTask should be run on a separate thread. This would continously poll the status of the card. For this sample I'm simply doing : Console.WriteLine("Checking the card");.
Once it checks the status of the card it should sleep for 15secs for this sample only (in general it should check every 50ms, but for testing purposes I have kept 15secs).
But in the above sample it's not sleeping it's simply giving me Checking the card continuously. It's not sleeping at all for 15secs. What is wrong with this code ?
You're calling chkRequestTask recursively using Task.Factory.StartNew which you don't need at all.
It's not clear why you need to poll the status, better idea is to check any event or callback or WaitHandle provided by the card API you're talking about. That should keep you away from the pain.
If at all you believe polling is the only option you've left with, you can do it as follows.
static async Task ChkRequestTask(CancellationToken token)
{
while (true)
{
token.ThrowIfCancellationRequested();
Console.WriteLine("Checking the card");
bool status = PollTheCardForStatus();
if(!status)
break;
await Task.Delay(15 * 1000, token);//Adjust the delay as you wish
}
}
Else where in code, if possible await the call, If not then attach a continuation or use blocking task.Wait.
await ChkRequestTask(token);
This method doesn't need to return bool as you're returning from the method only when it is false, it is safe to assume the status is false when the Task returned from ChkRequestTask completes, which means poll returned false or the CancellationToken is cancelled, in which case you'll get TaskCanceledException
This is how I have done this. It seems to be working properly. As it's a background thread it would exit when the application exits. Could someone advise If this is the right way to do it.
private void Form1_Load(object sender, EventArgs e)
{
m_dev = DASK.Register_Card(DASK.PCI_7250, 0);
if (m_dev < 0)
{
MessageBox.Show("Register_Card error!");
}
FunctionToCall();
}
private void FunctionToCall()
{
short ret;
uint int_value;
var thread = new Thread(() =>
{
while (true)
{
ret = DASK.DI_ReadPort((ushort)m_dev, 0, out int_value);
if (ret < 0)
{
MessageBox.Show("D2K_DI_ReadPort error!");
return;
}
if (int_value > 0)
{
textBox2.Invoke(new UpdateText(DisplayText), Convert.ToInt32(int_value));
}
Thread.Sleep(500);
}
});
thread.Start();
thread.IsBackground = true;
}
private void DisplayText(int i)
{
textBox2.Text = i.ToString();
}

Categories

Resources