Is what I'm doing below the correct/best way to accomplish this?
I have a window with a timer. Each time the timer ticks, I call the RunTask method shown below. Within RunTask, I call DoTheThing. DoTheThing may take a while to run, and may fail (it's a database update). I want to make sure that at any point in time, I only have one DoTheThing outstanding. I also want to make sure that I don't have a bunch of RunTask instances all queued and waiting for a lock to be released by the RunTask instance that is running DoTheThing.
public void RunTask()
{
bool canRunTask = true;
// Check if another instance of this method is currently executing. If so, do not execute the rest of this method
lock (this.runTaskLock)
{
if (this.isTaskRunning)
{
canRunTask = false;
}
else
{
this.isTaskRunning = true;
}
}
// Call DoTheThing if another instance is not currently outstanding
if (canRunTask)
{
try
{
Task task = new Task(() => DoTheThing());
task.Start();
}
catch (Exception ex)
{
// Handle the exception
}
finally
{
lock (this.runTaskLock)
{
this.isTaskRunning = false;
}
}
}
}
Because of the architecture of the program, I would rather put all of my thread synchronization within this method instead of enabling and disabling the timer.
By thinking about the problem slightly differently, it becomes a lot easier. Instead of firing a timer every x seconds, why not wait x seconds between invocations?
Now you can just run an async loop to do the scheduled work and save yourself a bunch of painful synchronization work.
async Task RunActionPeriodicallyAsync(Action action,
TimeSpan ts,
CancellationToken token = default(CancellationToken))
{
while(!token.IsCancellationRequested)
{
action();
await Task.Delay(ts, token);
//or alternatively (see comment below)
//var delayTask = Task.Delay(ts, token);
//action();
//await delayTask;
}
}
Now, just call RunActionPeriodicallyAsync once, and calls to its action will never overlap.
RunActionPeriodicallyAsync(() => DoSomething(), TimeSpan.FromSeconds(10))
You could overload this to take an async "action"... actually a Func<Task>...
async Task RunActionPeriodicallyAsync(Func<CancellationToken, Task> actionAsync,
TimeSpan ts,
CancellationToken token = default(CancellationToken))
{
while(!token.IsCancellationRequested)
{
await actionAsync(token);
await Task.Delay(ts, token);
//or alternatively (see comment below)
//await Task.WhenAll(actionAsync(token), Task.Delay(ts, token))
}
}
and use it:
RunActionPeriodicallyAsync(async cancTok => await DoSomethingAsync(cancTok),
TimeSpan.FromSeconds(10))
If you are worried about too much locking, you can do the following. You might miss a run if one task completes while the other is just at the check (marked), but you got rid of some locking and you will only need to lock when you set isTaskRunnung = true.
In Addition you need to mark your method as async so you can await the task.
public async Task RunTask()
{
bool canRunTask = true;
// Check if another instance of this method is currently executing. If so, do not execute the rest of this method
if (this.isTaskRunning)
{ // <-- ___MARK___
canRunTask = false;
}
else
{
lock (this.runTaskLock)
{
if (this.isTaskRunning)
{
canRunTask = false;
}
else
{
this.isTaskRunning = true;
}
}
}
// Call DoTheThing if another instance is not currently outstanding
if (canRunTask)
{
try
{
await Task.Run(() => DoTheThing());
}
catch (Exception ex)
{
// Handle the exception
}
finally
{
this.isTaskRunning = false;
}
}
}
Related
I have a question about the syncronisation between loading resources async and keeping the selected element to the correct loaded resource. To be pricise I have a listview with users and one panel with his profile. If I choose that user, the user is loaded from an webservice and after that his data are shown in that profile-panel. Loading a user can be a very expensive operation (time) so I tried so make that loading async to prevent to block the whole UI-thread. I wrote in the ItemChange-Event something like this->
ItemChangeEvent(){
Task.Factory.StartNew(()=>{
.. load profile from Server
this.Dispatcher.Invoke(.. some UI changes);
});
}
Now it sometimes happens, that the user I selected in that listview, is not the user which is shown on the profile. My guess is, that any of the task is delayed and pushed his content after the "correct" user-profile task is finished. So how can I achieve that the loading is async but syncronisation with the current-selected-item?
You could add a CancellationTokenSource in the outer scope, and store the CancellationToken in a local variable inside the event handler. Ideally this token should be passed and used by the method that fetches the profile from the remote server, to avoid having ongoing tasks fetching data that are no longer needed.
Also instead of using the awkward Dispatcher.Invoke for switching back to the UI thread, you could take advantage of the modern and neat async-await approach. The code after await continues automatically in the UI thread, without having to do anything special beyond adding the keyword async in the event handler:
private CancellationTokenSource _itemChangeTokenSource;
private async void ListView1_ItemChange(object sender, EventArgs e)
{
_itemChangeTokenSource?.Cancel();
_itemChangeTokenSource = new CancellationTokenSource();
CancellationToken token = _itemChangeTokenSource.Token;
var id = GetSelectedId(ListView1);
Profile profile;
try
{
profile = await Task.Run(() =>
{
return GetProfile(id, token); // Expensive operation
}, token);
token.ThrowIfCancellationRequested();
}
catch (OperationCanceledException)
{
return; // Nothing to do, this event was canceled
}
UpdatePanel(profile);
}
It would be even more ideal if the expensive operation could become asynchronous. This way you would avoid blocking a ThreadPool thread every time the user clicked on the ListView control.
profile = await Task.Run(async () =>
{
return await GetProfileAsync(id, token); // Expensive asynchronous operation
}, token);
Update: I made an attempt to encapsulate the cancellation-related logic inside a class, so that the same functionality can be achieved with fewer lines of code. It may be tempting to reduce this code in case it is repeated multiple times in the same window, or in multiple windows. The class is named CancelableExecution, and has a single method Run which accepts the cancelable operation in the form of a Func<CancellationToken, T> parameter.
Here is a usage example of this class:
private CancelableExecution _updatePanelCancelableExecution = new CancelableExecution();
private async void ListView1_ItemChange(object sender, EventArgs e)
{
var id = GetSelectedId(ListView1);
if (await _updatePanelCancelableExecution.Run(cancellationToken =>
{
return GetProfile(id, cancellationToken); // Expensive operation
}, out var profile))
{
UpdatePanel(await profile);
}
}
The Run method returns a Task<bool>, that has the value true if the operation was completed successfully (not canceled). The result of a successful operation is available via an out Task<T> parameter. This API makes for less code, but also for less readable code, so use this class with caution!
public class CancelableExecution
{
private CancellationTokenSource _activeTokenSource;
public Task<bool> RunAsync<T>(Func<CancellationToken, Task<T>> function,
out Task<T> result)
{
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
var resultTcs = new TaskCompletionSource<T>(
TaskCreationOptions.RunContinuationsAsynchronously);
result = resultTcs.Task;
return ((Func<Task<bool>>)(async () =>
{
try
{
var oldTokenSource = Interlocked.Exchange(ref _activeTokenSource,
tokenSource);
if (oldTokenSource != null)
{
await Task.Run(() =>
{
oldTokenSource.Cancel(); // Potentially expensive
}).ConfigureAwait(false);
token.ThrowIfCancellationRequested();
}
var task = function(token);
var result = await task.ConfigureAwait(false);
token.ThrowIfCancellationRequested();
resultTcs.SetResult(result);
return true;
}
catch (OperationCanceledException ex) when (ex.CancellationToken == token)
{
resultTcs.SetCanceled();
return false;
}
catch (Exception ex)
{
resultTcs.SetException(ex);
throw;
}
finally
{
if (Interlocked.CompareExchange(
ref _activeTokenSource, null, tokenSource) == tokenSource)
{
tokenSource.Dispose();
}
}
}))();
}
public Task<bool> RunAsync<T>(Func<Task<T>> function, out Task<T> result)
{
return RunAsync(ct => function(), out result);
}
public Task<bool> Run<T>(Func<CancellationToken, T> function, out Task<T> result)
{
return RunAsync(ct => Task.Run(() => function(ct), ct), out result);
}
public Task<bool> Run<T>(Func<T> function, out Task<T> result)
{
return RunAsync(ct => Task.Run(() => function(), ct), out result);
}
}
I'd suggest you use CancellationToken to cancel previous load task once other user is selected. This can be achieved in few steps:
Create instance field CancellationTokenSource _tokenSource
change your handler:
ItemChangeEvent(){
// first, try to cancel previous event
_tokenSource?.Cancel();
// then, update token source; previous object will be collected eventually
_tokenSource = new CancellationTokenSource();
// finally, add cancellation token from token source to task creation
Task.Factory.StartNew(()=>{
.. load profile from Server
this.Dispatcher.Invoke(.. some UI changes);
}, _tokenSource.Token);
}
I have the following method:
public async Task ScrapeObjects(int page = 1)
{
try
{
while (!isObjectSearchCompleted)
{
..do calls..
}
}
catch (HttpRequestException ex)
{
Thread.Sleep(TimeSpan.FromSeconds(60));
ScrapeObjects(page);
Log.Fatal(ex, ex.Message);
}
}
I call this long running method async and I don't wait for it to finish. Thing is that an exception my occur and in that case I want to handle it. But then I want to start from where I left and with the same thread. At the current state a new thread gets used when I recursively call the method after handling the exception. I would like to keep using the same thread. Is there a way to do so? Thank you!
You probably need to move the try/catch block inside the while loop, and add a counter with the errors occurred, to bail out in case of continuous faulted attempts.
public async Task ScrapeObjects()
{
int failedCount = 0;
int page = 1;
while (!isObjectSearchCompleted)
{
try
{
//..do calls..
}
catch (HttpRequestException ex)
{
failedCount++;
if (failedCount < 3)
{
Log.Info(ex, ex.Message);
await Task.Delay(TimeSpan.FromSeconds(60));
}
else
{
Log.Fatal(ex, ex.Message);
throw; // or return;
}
}
}
}
As a side note it is generally better to await Task.Delay instead of Thread.Sleep inside asynchronous methods, to avoid blocking a thread without a reason.
One simple question before you read the long answer below:
Why you need the same thread? Are you accessing thread static / contextual data?
If yes, there will be ways to solve that easily than limiting your tasks to run on the same thread.
How to limit tasks to run on a single thread
As long as you use async calls on the default synchronization context, and as soon as the code is resumed from an await, it is possible that the thread can change after an await. This is because the default context schedules tasks to the next available thread in the thread pool. Like in the below case, before can be different from after:
public async Task ScrapeObjects(int page = 1)
{
var before = Thread.CurrentThread.ManagedThreadId;
await Task.Delay(1000);
var after = Thread.CurrentThread.ManagedThreadId;
}
The only reliable way to guarantee that your code could come back on the same thread is to schedule your async code onto a single threaded synchronization context:
class SingleThreadSynchronizationContext : SynchronizationContext
{
private readonly BlockingCollection<Action> _actions = new BlockingCollection<Action>();
private readonly Thread _theThread;
public SingleThreadSynchronizationContext()
{
_theThread = new Thread(DoWork);
_theThread.IsBackground = true;
_theThread.Start();
}
public override void Send(SendOrPostCallback d, object state)
{
// Send requires run the delegate immediately.
d(state);
}
public override void Post(SendOrPostCallback d, object state)
{
// Schedule the action by adding to blocking collection.
_actions.Add(() => d(state));
}
private void DoWork()
{
// Keep picking up actions to run from the collection.
while (!_actions.IsAddingCompleted)
{
try
{
var action = _actions.Take();
action();
}
catch (InvalidOperationException)
{
break;
}
}
}
}
And you need to schedule ScrapeObjects to the custom context:
SynchronizationContext.SetSynchronizationContext(new SingleThreadSynchronizationContext());
await Task.Factory.StartNew(
() => ScrapeObjects(),
CancellationToken.None,
TaskCreationOptions.DenyChildAttach | TaskCreationOptions.LongRunning,
TaskScheduler.FromCurrentSynchronizationContext()
).Unwrap();
By doing that, all your async code shall be scheduled to the same context, and run by the thread on that context.
However
This is typically dangerous, as you suddenly lose the ability to use the thread pool. If you block the thread, the entire async operation is blocked, meaning you will have deadlocks.
Supposing a Task is created and awaited multiple times from a single thread. Is the resume order FIFO?
Simplistic example: Is the Debug.Assert() really an invariant?
Task _longRunningTask;
async void ButtonStartSomething_Click()
{
// Wait for any previous runs to complete before starting the next
if (_longRunningTask != null) await _longRunningTask;
// Check our invariant
Debug.Assert(_longRunningTask == null, "This assumes awaits resume in FIFO order");
// Initialize
_longRunningTask = Task.Delay(10000);
// Yield and wait for completion
await _longRunningTask;
// Clean up
_longRunningTask = null;
}
Initialize and Clean up are kept to a bare minimum for the sake of simplicity, but the general idea is that the previous Clean up MUST be complete before the next Initialize runs.
The short answer is: no, it's not guaranteed.
Furthermore, you should not use ContinueWith; among other problems, it has a confusing default scheduler (more details on my blog). You should use await instead:
private async void ButtonStartSomething_Click()
{
// Wait for any previous runs to complete before starting the next
if (_longRunningTask != null) await _longRunningTask;
_longRunningTask = LongRunningTaskAsync();
await _longRunningTask;
}
private async Task LongRunningTaskAsync()
{
// Initialize
await Task.Delay(10000);
// Clean up
_longRunningTask = null;
}
Note that this could still have "interesting" semantics if the button can be clicked many times while the tasks are still running.
The standard way to prevent the multiple-execution problem for UI applications is to disable the button:
private async void ButtonStartSomething_Click()
{
ButtonStartSomething.Enabled = false;
await LongRunningTaskAsync();
ButtonStartSomething.Enabled = true;
}
private async Task LongRunningTaskAsync()
{
// Initialize
await Task.Delay(10000);
// Clean up
}
This forces your users into a one-operation-at-a-time queue.
The order of execution is pre-defined, however there is potential race condition on _longRunningTask variable if ButtonStartSomething_Click() is called concurrently from more than one thread (not likely the case).
Alternatively, you can explicitly schedule tasks using a queue. As a bonus a work can be scheduled from non-async methods, too:
void ButtonStartSomething_Click()
{
_scheduler.Add(async() =>
{
// Do something
await Task.Delay(10000);
// Do something else
});
}
Scheduler _scheduler;
class Scheduler
{
public Scheduler()
{
_queue = new ConcurrentQueue<Func<Task>>();
_state = STATE_IDLE;
}
public void Add(Func<Task> func)
{
_queue.Enqueue(func);
ScheduleIfNeeded();
}
public Task Completion
{
get
{
var t = _messageLoopTask;
if (t != null)
{
return t;
}
else
{
return Task.FromResult<bool>(true);
}
}
}
void ScheduleIfNeeded()
{
if (_queue.IsEmpty)
{
return;
}
if (Interlocked.CompareExchange(ref _state, STATE_RUNNING, STATE_IDLE) == STATE_IDLE)
{
_messageLoopTask = Task.Run(new Func<Task>(RunMessageLoop));
}
}
async Task RunMessageLoop()
{
Func<Task> item;
while (_queue.TryDequeue(out item))
{
await item();
}
var oldState = Interlocked.Exchange(ref _state, STATE_IDLE);
System.Diagnostics.Debug.Assert(oldState == STATE_RUNNING);
if (!_queue.IsEmpty)
{
ScheduleIfNeeded();
}
}
volatile Task _messageLoopTask;
ConcurrentQueue<Func<Task>> _queue;
static int _state;
const int STATE_IDLE = 0;
const int STATE_RUNNING = 1;
}
Found the answer under Task.ContinueWith(). It appear to be: no
Presuming await is just Task.ContinueWith() under the hood, there's documentation for TaskContinuationOptions.PreferFairness that reads:
A hint to a TaskScheduler to schedule task in the order in which they were scheduled, so that tasks scheduled sooner are more likely to run sooner, and tasks scheduled later are more likely to run later.
(bold-facing added)
This suggests there's no guarantee of any sorts, inherent or otherwise.
Correct ways to do this
For the sake of someone like me (OP), here's a look at the more correct ways to do this.
Based on Stephen Cleary's answer:
private async void ButtonStartSomething_Click()
{
// Wait for any previous runs to complete before starting the next
if (_longRunningTask != null) await _longRunningTask;
// Initialize
_longRunningTask = ((Func<Task>)(async () =>
{
await Task.Delay(10);
// Clean up
_longRunningTask = null;
}))();
// Yield and wait for completion
await _longRunningTask;
}
Suggested by Raymond Chen's comment:
private async void ButtonStartSomething_Click()
{
// Wait for any previous runs to complete before starting the next
if (_longRunningTask != null) await _longRunningTask;
// Initialize
_longRunningTask = Task.Delay(10000)
.ContinueWith(task =>
{
// Clean up
_longRunningTask = null;
}, TaskContinuationOptions.OnlyOnRanToCompletion);
// Yield and wait for completion
await _longRunningTask;
}
Suggested by Kirill Shlenskiy's comment:
readonly SemaphoreSlim _taskSemaphore = new SemaphoreSlim(1);
async void ButtonStartSomething_Click()
{
// Wait for any previous runs to complete before starting the next
await _taskSemaphore.WaitAsync();
try
{
// Do some initialization here
// Yield and wait for completion
await Task.Delay(10000);
// Do any clean up here
}
finally
{
_taskSemaphore.Release();
}
}
(Please -1 or comment if I've messed something up in either.)
Handling exceptions
Using continuations made me realize one thing: awaiting at multiple places gets complicated really quickly if _longRunningTask can throw exceptions.
If I'm going to use continuations, it looks like I need to top it off by handling all exceptions within the continuation as well.
i.e.
_longRunningTask = Task.Delay(10000)
.ContinueWith(task =>
{
// Clean up
_longRunningTask = null;
}, TaskContinuationOptions.OnlyOnRanToCompletion);
.ContinueWith(task =>
{
// Consume or handle exceptions here
}, TaskContinuationOptions.OnlyOnFaulted);
// Yield and wait for completion
await _longRunningTask;
If I use a SemaphoreSlim, I can do the same thing in the try-catch, and have the added option of bubbling exceptions directly out of ButtonStartSomething_Click.
Is there a recommended established pattern for self-cancelling and restarting tasks?
E.g., I'm working on the API for background spellchecker. The spellcheck session is wrapped as Task. Every new session should cancel the previous one and wait for its termination (to properly re-use the resources like spellcheck service provider, etc).
I've come up with something like this:
class Spellchecker
{
Task pendingTask = null; // pending session
CancellationTokenSource cts = null; // CTS for pending session
// SpellcheckAsync is called by the client app
public async Task<bool> SpellcheckAsync(CancellationToken token)
{
// SpellcheckAsync can be re-entered
var previousCts = this.cts;
var newCts = CancellationTokenSource.CreateLinkedTokenSource(token);
this.cts = newCts;
if (IsPendingSession())
{
// cancel the previous session and wait for its termination
if (!previousCts.IsCancellationRequested)
previousCts.Cancel();
// this is not expected to throw
// as the task is wrapped with ContinueWith
await this.pendingTask;
}
newCts.Token.ThrowIfCancellationRequested();
var newTask = SpellcheckAsyncHelper(newCts.Token);
this.pendingTask = newTask.ContinueWith((t) => {
this.pendingTask = null;
// we don't need to know the result here, just log the status
Debug.Print(((object)t.Exception ?? (object)t.Status).ToString());
}, TaskContinuationOptions.ExecuteSynchronously);
return await newTask;
}
// the actual task logic
async Task<bool> SpellcheckAsyncHelper(CancellationToken token)
{
// do not start a new session if the the previous one still pending
if (IsPendingSession())
throw new ApplicationException("Cancel the previous session first.");
// do the work (pretty much IO-bound)
try
{
bool doMore = true;
while (doMore)
{
token.ThrowIfCancellationRequested();
await Task.Delay(500); // placeholder to call the provider
}
return doMore;
}
finally
{
// clean-up the resources
}
}
public bool IsPendingSession()
{
return this.pendingTask != null &&
!this.pendingTask.IsCompleted &&
!this.pendingTask.IsCanceled &&
!this.pendingTask.IsFaulted;
}
}
The client app (the UI) should just be able to call SpellcheckAsync as many times as desired, without worrying about cancelling a pending session. The main doMore loop runs on the UI thread (as it involves the UI, while all spellcheck service provider calls are IO-bound).
I feel a bit uncomfortable about the fact that I had to split the API into two peices, SpellcheckAsync and SpellcheckAsyncHelper, but I can't think of a better way of doing this, and it's yet to be tested.
I think the general concept is pretty good, though I recommend you not use ContinueWith.
I'd just write it using regular await, and a lot of the "am I already running" logic is not necessary:
Task pendingTask = null; // pending session
CancellationTokenSource cts = null; // CTS for pending session
// SpellcheckAsync is called by the client app on the UI thread
public async Task<bool> SpellcheckAsync(CancellationToken token)
{
// SpellcheckAsync can be re-entered
var previousCts = this.cts;
var newCts = CancellationTokenSource.CreateLinkedTokenSource(token);
this.cts = newCts;
if (previousCts != null)
{
// cancel the previous session and wait for its termination
previousCts.Cancel();
try { await this.pendingTask; } catch { }
}
newCts.Token.ThrowIfCancellationRequested();
this.pendingTask = SpellcheckAsyncHelper(newCts.Token);
return await this.pendingTask;
}
// the actual task logic
async Task<bool> SpellcheckAsyncHelper(CancellationToken token)
{
// do the work (pretty much IO-bound)
using (...)
{
bool doMore = true;
while (doMore)
{
token.ThrowIfCancellationRequested();
await Task.Delay(500); // placeholder to call the provider
}
return doMore;
}
}
Here's the most recent version of the cancel-and-restart pattern that I use:
class AsyncWorker
{
Task _pendingTask;
CancellationTokenSource _pendingTaskCts;
// the actual worker task
async Task DoWorkAsync(CancellationToken token)
{
token.ThrowIfCancellationRequested();
Debug.WriteLine("Start.");
await Task.Delay(100, token);
Debug.WriteLine("Done.");
}
// start/restart
public void Start(CancellationToken token)
{
var previousTask = _pendingTask;
var previousTaskCts = _pendingTaskCts;
var thisTaskCts = CancellationTokenSource.CreateLinkedTokenSource(token);
_pendingTask = null;
_pendingTaskCts = thisTaskCts;
// cancel the previous task
if (previousTask != null && !previousTask.IsCompleted)
previousTaskCts.Cancel();
Func<Task> runAsync = async () =>
{
// await the previous task (cancellation requested)
if (previousTask != null)
await previousTask.WaitObservingCancellationAsync();
// if there's a newer task started with Start, this one should be cancelled
thisTaskCts.Token.ThrowIfCancellationRequested();
await DoWorkAsync(thisTaskCts.Token).WaitObservingCancellationAsync();
};
_pendingTask = Task.Factory.StartNew(
runAsync,
CancellationToken.None,
TaskCreationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext()).Unwrap();
}
// stop
public void Stop()
{
if (_pendingTask == null)
return;
if (_pendingTask.IsCanceled)
return;
if (_pendingTask.IsFaulted)
_pendingTask.Wait(); // instantly throw an exception
if (!_pendingTask.IsCompleted)
{
// still running, request cancellation
if (!_pendingTaskCts.IsCancellationRequested)
_pendingTaskCts.Cancel();
// wait for completion
if (System.Threading.Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA)
{
// MTA, blocking wait
_pendingTask.WaitObservingCancellation();
}
else
{
// TODO: STA, async to sync wait bridge with DoEvents,
// similarly to Thread.Join
}
}
}
}
// useful extensions
public static class Extras
{
// check if exception is OperationCanceledException
public static bool IsOperationCanceledException(this Exception ex)
{
if (ex is OperationCanceledException)
return true;
var aggEx = ex as AggregateException;
return aggEx != null && aggEx.InnerException is OperationCanceledException;
}
// wait asynchrnously for the task to complete and observe exceptions
public static async Task WaitObservingCancellationAsync(this Task task)
{
try
{
await task;
}
catch (Exception ex)
{
// rethrow if anything but OperationCanceledException
if (!ex.IsOperationCanceledException())
throw;
}
}
// wait for the task to complete and observe exceptions
public static void WaitObservingCancellation(this Task task)
{
try
{
task.Wait();
}
catch (Exception ex)
{
// rethrow if anything but OperationCanceledException
if (!ex.IsOperationCanceledException())
throw;
}
}
}
Test use (producing only a single "Start/Done" output for DoWorkAsync):
private void MainForm_Load(object sender, EventArgs e)
{
var worker = new AsyncWorker();
for (var i = 0; i < 10; i++)
worker.Start(CancellationToken.None);
}
Hope this will be useful - tried to create Helper class which can be re-used:
class SelfCancelRestartTask
{
private Task _task = null;
public CancellationTokenSource TokenSource { get; set; } = null;
public SelfCancelRestartTask()
{
}
public async Task Run(Action operation)
{
if (this._task != null &&
!this._task.IsCanceled &&
!this._task.IsCompleted &&
!this._task.IsFaulted)
{
TokenSource?.Cancel();
await this._task;
TokenSource = new CancellationTokenSource();
}
else
{
TokenSource = new CancellationTokenSource();
}
this._task = Task.Run(operation, TokenSource.Token);
}
The examples above seem to have problems when the asynchronous method is called multiple times quickly after each other, for example four times. Then all subsequent calls of this method cancel the first task and in the end three new tasks are generated which run at the same time. So I came up with this:
private List<Tuple<Task, CancellationTokenSource>> _parameterExtractionTasks = new List<Tuple<Task, CancellationTokenSource>>();
/// <remarks>This method is asynchronous, i.e. it runs partly in the background. As this method might be called multiple times
/// quickly after each other, a mechanism has been implemented that <b>all</b> tasks from previous method calls are first canceled before the task is started anew.</remarks>
public async void ParameterExtraction() {
CancellationTokenSource newCancellationTokenSource = new CancellationTokenSource();
// Define the task which shall run in the background.
Task newTask = new Task(() => {
// do some work here
}
}
}, newCancellationTokenSource.Token);
_parameterExtractionTasks.Add(new Tuple<Task, CancellationTokenSource>(newTask, newCancellationTokenSource));
/* Convert the list to arrays as an exception is thrown if the number of entries in a list changes while
* we are in a for loop. This can happen if this method is called again while we are waiting for a task. */
Task[] taskArray = _parameterExtractionTasks.ConvertAll(item => item.Item1).ToArray();
CancellationTokenSource[] tokenSourceArray = _parameterExtractionTasks.ConvertAll(item => item.Item2).ToArray();
for (int i = 0; i < taskArray.Length - 1; i++) { // -1: the last task, i.e. the most recent task, shall be run and not canceled.
// Cancel all running tasks which were started by previous calls of this method
if (taskArray[i].Status == TaskStatus.Running) {
tokenSourceArray[i].Cancel();
await taskArray[i]; // wait till the canceling completed
}
}
// Get the most recent task
Task currentThreadToRun = taskArray[taskArray.Length - 1];
// Start this task if, but only if it has not been started before (i.e. if it is still in Created state).
if (currentThreadToRun.Status == TaskStatus.Created) {
currentThreadToRun.Start();
await currentThreadToRun; // wait till this task is completed.
}
// Now the task has been completed once. Thus we can recent the list of tasks to cancel or maybe run.
_parameterExtractionTasks = new List<Tuple<Task, CancellationTokenSource>>();
}
I have a doubt with cancellation token source which I am using as shown in the below code:
void Process()
{
//for the sake of simplicity I am taking 1, in original implementation it is more than 1
var cancellationToken = _cancellationTokenSource.Token;
Task[] tArray = new Task[1];
tArray[0] = Task.Factory.StartNew(() =>
{
cancellationToken.ThrowIfCancellationRequested();
//do some work here
MainTaskRoutine();
}, cancellationToken);
try
{
Task.WaitAll(tArray);
}
catch (Exception ex)
{
//do error handling here
}
}
void MainTaskRoutine()
{
//for the sake of simplicity I am taking 1, in original implementation it is more than 1
//this method shows that a nested task is created
var cancellationToken = _cancellationTokenSource.Token;
Task[] tArray = new Task[1];
tArray[0] = Task.Factory.StartNew(() =>
{
cancellationToken.ThrowIfCancellationRequested();
//do some work here
}, cancellationToken);
try
{
Task.WaitAll(tArray);
}
catch (Exception ex)
{
//do error handling here
}
}
Edit: further elaboration
End objective is: when a user cancels the operation, all the immediate pending tasks(either children or grand children) should cancel.
Scenario:
As per above code:
1. I first check whether user has asked for cancellation
2. If user has not asked for cancellation then only continue with the task (Please see Process method).
sample code shows only one task here but actually there can be three or more
Lets say that CPU started processing Task1 while other tasks are still in the Task queue waiting for some CPU to come and execute them.
User requests cancellation: Task 2,3 in Process method are immediately cancelled, but Task 1 will continue to work since it is already undergoing processing.
In Task 1 it calls method MainTaskRoutine, which in turn creates more tasks.
In the function of MainTaskRoutine I have written: cancellationToken.ThrowIfCancellationRequested();
So the question is: is it correct way of using CancellationTokenSource as it is dependent on Task.WaitAll()?
[EDITED] As you use an array in your code, I assume there could be multiple tasks, not just one. I also assume that within each task that you're starting from Process you want to do some CPU-bound work first (//do some work here), and then run MainTaskRoutine.
How you handle task cancellation exceptions is determined by your project design workflow. E.g., you could do it inside Process method, or from where you call Process. If your only concern is to remove Task objects from the array where you keep track of the pending tasks, this can be done using Task.ContinueWith. The continuation will be executed regardless of the task's completion status (Cancelled, Faulted or RanToCompletion):
Task Process(CancellationToken cancellationToken)
{
var tArray = new List<Task>();
var tArrayLock = new Object();
var task = Task.Run(() =>
{
cancellationToken.ThrowIfCancellationRequested();
//do some work here
return MainTaskRoutine(cancellationToken);
}, cancellationToken);
// add the task to the array,
// use lock as we may remove tasks from this array on a different thread
lock (tArrayLock)
tArray.Add(task);
task.ContinueWith((antecedentTask) =>
{
if (antecedentTask.IsCanceled || antecedentTask.IsFaulted)
{
// handle cancellation or exception inside the task
// ...
}
// remove task from the array,
// could be on a different thread from the Process's thread, use lock
lock (tArrayLock)
tArray.Remove(antecedentTask);
}, TaskContinuationOptions.ExecuteSynchronously);
// add more tasks like the above
// ...
// Return aggregated task
Task[] allTasks = null;
lock (tArrayLock)
allTasks = tArray.ToArray();
return Task.WhenAll(allTasks);
}
Your MainTaskRoutine can be structured in exactly the same way as Process, and have the same method signature (return a Task).
Then you may want to perform a blocking wait on the aggregated task returned by Process, or handle its completion asynchronously, e.g:
// handle the completion asynchronously with a blocking wait
void RunProcessSync()
{
try
{
Process(_cancellationTokenSource.Token).Wait();
MessageBox.Show("Process complete");
}
catch (Exception e)
{
MessageBox.Show("Process cancelled (or faulted): " + e.Message);
}
}
// handle the completion asynchronously using ContinueWith
Task RunProcessAync()
{
return Process(_cancellationTokenSource.Token).ContinueWith((task) =>
{
// check task.Status here
MessageBox.Show("Process complete (or cancelled, or faulted)");
}, TaskScheduler.FromCurrentSynchronizationContext());
}
// handle the completion asynchronously with async/await
async Task RunProcessAync()
{
try
{
await Process(_cancellationTokenSource.Token);
MessageBox.Show("Process complete");
}
catch (Exception e)
{
MessageBox.Show("Process cancelled (or faulted): " + e.Message);
}
}
After doing some research I found this link.
The code now looks like this:
see the usage of CancellationTokenSource.CreateLinkedTokenSource in below code
void Process()
{
//for the sake of simplicity I am taking 1, in original implementation it is more than 1
var cancellationToken = _cancellationTokenSource.Token;
Task[] tArray = new Task[1];
tArray[0] = Task.Factory.StartNew(() =>
{
cancellationToken.ThrowIfCancellationRequested();
//do some work here
MainTaskRoutine(cancellationToken);
}, cancellationToken);
try
{
Task.WaitAll(tArray);
}
catch (Exception ex)
{
//do error handling here
}
}
void MainTaskRoutine(CancellationToken cancellationToken)
{
//for the sake of simplicity I am taking 1, in original implementation it is more than 1
//this method shows that a nested task is created
using (var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{
var cancelToken = cancellationTokenSource.Token;
Task[] tArray = new Task[1];
tArray[0] = Task.Factory.StartNew(() =>
{
cancelToken.ThrowIfCancellationRequested();
//do some work here
}, cancelToken);
try
{
Task.WaitAll(tArray);
}
catch (Exception ex)
{
//do error handling here
}
}
}
Note: I haven't used it, but I will let You know once it is done :)