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 :)
Related
I have a console app,
{
StartThread();
//must be true, windows system wants to know it is started
return true;
}
I'm trying to create a safety timeout function for this Task. But the task keeps running...
The method DoSomething calls other async methods and awaits their result
Do anyone have an idea why my task don't stop? Maybe a good code example on what to do
public async void StartThread()
{
var tokenSource = new CancellationTokenSource();
var token = tokenSource.Token;
try
{
var timeout = 1000;
Task task = new Task(() => DoSomething(token), token);
task.Start();
if (await Task.WhenAny(task, Task.Delay(timeout, token)) == task)
{
if (token.IsCancellationRequested)
task.Dispose();
await task;
if (task.IsCompleted)
{
task.Dispose();
tokenSource.Cancel();
tokenSource.Dispose();
}
else
{
log.WriteToFile("Timeout_ ");
}
}
else
tokenSource.Cancel();
}
catch (Exception e)
{
Console.WriteLine("--StartThread ...there is an exception----");
}
finally
{
Thread.Sleep(300000); // 5 minutter
StartThread();
}
}
While not create CancellationTokenSource from given timeout?
var timeout = 1000;
//DONE: don't forget to dispose CancellationTokenSource instance
using (var tokenSource = new CancellationTokenSource(timeout)) {
try {
var token = tokenSource.Token;
//TODO: May be you'll want to add .ConfigureAwait(false);
Task task = Task.Run(() => DoSomething(token), token);
await task;
// Completed
}
catch (TaskCanceledException) {
// Cancelled due to timeout
log.WriteToFile("Timeout_ ");
}
catch (Exception e) {
// Failed to complete due to e exception
Console.WriteLine("--StartThread ...there is an exception----");
//DONE: let's be nice and don't swallow the exception
throw;
}
}
You should hardly ever Dispose a Task, since iot is managed by C# internals and it is taken care of. Also, you Dispose way too eagerly, for example:
if (token.IsCancellationRequested)
task.Dispose();
await task;
I do not think so you want still await task if it cancelled and disposed. I guess it will not work at all.
Also if you use async, do not mix blocking calls such as Thread.Sleep - that can lead to disaster...
After all, you use cancellation token with some delay task to imitate a timeout - that's OK, but why do put unnecessary code, when you have great API at hand. Just make use of special contructor of CancellationTokenSource:
public CancellationTokenSource (int millisecondsDelay);
Here's the docs
After a timeout you are setting the CancellationToken and then immediately sleeping the thread for 5 minutes. Thus DoSomething() never gets a chance to continue running and react to the token being cancelled.
I need to run many tasks in parallel as fast as possible. But if my program runs more than 30 tasks per 1 second, it will be blocked. How to ensure that tasks run no more than 30 per any 1-second interval?
In other words, we must prevent the new task from starting if 30 tasks were completed in the last 1-second interval.
My ugly possible solution:
private async Task Process(List<Task> taskList, int maxIntervalCount, int timeIntervalSeconds)
{
var timeList = new List<DateTime>();
var sem = new Semaphore(maxIntervalCount, maxIntervalCount);
var tasksToRun = taskList.Select(async task =>
{
do
{
sem.WaitOne();
}
while (HasAllowance(timeList, maxIntervalCount, timeIntervalSeconds));
await task;
timeList.Add(DateTime.Now);
sem.Release();
});
await Task.WhenAll(tasksToRun);
}
private bool HasAllowance(List<DateTime> timeList, int maxIntervalCount, int timeIntervalSeconds)
{
return timeList.Count <= maxIntervalCount
|| DateTime.Now.Subtract(TimeSpan.FromSeconds(timeIntervalSeconds)) > timeList[timeList.Count - maxIntervalCount];
}
User code should never have to control how tasks are scheduled directly. For one thing, it can't - controlling how tasks run is the job of the TaskScheduler. When user code calls .Start(), it simply adds a task to a threadpool queue for execution. await executes already executing tasks.
The TaskScheduler samples show how to create limited concurrency schedulers, but again, there are better, high-level options.
The question's code doesn't throttle the queued tasks anyway, it limits how many of them can be awaited. They are all running already. This is similar to batching the previous asynchronous operation in a pipeline, allowing only a limited number of messages to pass to the next level.
ActionBlock with delay
The easy, out-of-the-box way would be to use an ActionBlock with a limited MaxDegreeOfParallelism, to ensure no more than N concurrent operations can run at the same time. If we know how long each operation takes, we could add a bit of delay to ensure we don't overshoot the throttle limit.
In this case, 7 concurrent workers perform 4 requests/second, for a total of 28 maximum request per second. The BoundedCapacity means that only up to 7 items will be stored in the input buffer before downloader.SendAsync blocks. This way we avoid flooding the ActionBlock if the operations take too long.
var downloader = new ActionBlock<string>(
async url => {
await Task.Delay(250);
var response=await httpClient.GetStringAsync(url);
//Do something with it.
},
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 7, BoundedCapacity=7 }
);
//Start posting to the downloader
foreach(var item in urls)
{
await downloader.SendAsync(item);
}
downloader.Complete();
await downloader.Completion;
ActionBlock with SemaphoreSlim
Another option would be to combine this with a SemaphoreSlim that gets reset periodically by a timer.
var refreshTimer = new Timer(_=>sm.Release(30));
var downloader = new ActionBlock<string>(
async url => {
await semaphore.WaitAsync();
try
{
var response=await httpClient.GetStringAsync(url);
//Do something with it.
}
finally
{
semaphore.Release();
}
},
new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 5, BoundedCapacity=5 }
);
//Start the timer right before we start posting
refreshTimer.Change(1000,1000);
foreach(....)
{
}
This is the snippet:
var tasks = new List<Task>();
foreach(item in listNeedInsert)
{
var task = TaskToRun(item);
tasks.Add(task);
if(tasks.Count == 100)
{
await Task.WhenAll(tasks);
tasks.Clear();
}
}
// Wait for anything left to finish
await Task.WhenAll(tasks);
Notice that I rather add the task into a List<Task>(); and after all is added, I await all in the same List<Task>();
What you do here:
var tasks = taskList.Select(async task =>
{
do
{
sem.WaitOne();
}
while (timeList.Count <= maxIntervalCount
|| DateTime.Now.Subtract(TimeSpan.FromSeconds(timeIntervalSeconds)) > timeList[timeList.Count - maxIntervalCount]);
await task;
is blocking until the task finishes it's work thus making this call:
Task.WhenAll(tasks).Wait();
completely redundant. Furthermore, this line Task.WhenAll(tasks).Wait(); is performing unnecessary blocking on the WhenAll method.
Is the blocking due to some server/firewall/hardware limit or it is based on observation?
You should try to use BlockingCollection<Task> or similar thread safe collections especially if the job of your tasks are I/O-bound. You can even set the capacity to 30:
var collection = BlockingCollection<Task>(30);
Then you can start 2 async method:
var population = Task.Factory.Start(Populate);
var processing = Task.Factory.Start(Dequeue);
await Task.WhenAll(population, processing);
Task Populate()
{
foreach (...)
collection.Add(...);
collection.CompleteAdding();
}
Task Dequeue
{
while(!collection.IsComplete)
await collection.Take(); //consider using TryTake()
}
If the limit presists due to some true limitation (should be very rare) change Populate() as follows:
var stopper = Stopwatch.StartNew();
for (var i = ....) //instead of foreach
{
if (i % 30 == 0)
{
if (stopper.ElapsedMilliseconds < 1000)
Task.Delay(1000 - stopper.ElapsedMilliseconds); //note that this race condition should be avoided in your code
stopper.Restart();
}
collection.Add(...);
}
collection.CompleteAdding();
I think that this problem can be solved by a SemaphoreSlim limited to the number of maximum tasks per interval, and also by a Task.Delay that delays the release of the SemaphoreSlim after each task's completion, for an interval equal to the required throttling interval. Below is an implementation based on this idea. The rate limiting can be applied in two ways:
With includeAsynchronousDuration: false the rate limit affects how many operations can be started during the specified time span. The duration of each operation is not taken into account.
With includeAsynchronousDuration: true the rate limit affects how many operations can be counted as "active" during the specified time span, and is more restrictive (makes the enumeration slower). Instead of counting each operation as a moment in time (when started), it is counted as a time span (between start and completion). An operation is counted as "active" for a specified time span, if and only if its own time span intersects with the specified time span.
/// <summary>
/// Applies an asynchronous transformation for each element of a sequence,
/// limiting the number of transformations that can start or be active during
/// the specified time span.
/// </summary>
public static async Task<TResult[]> ForEachAsync<TSource, TResult>(
this IEnumerable<TSource> source,
Func<TSource, Task<TResult>> action,
int maxActionsPerTimeUnit,
TimeSpan timeUnit,
bool includeAsynchronousDuration = false,
bool onErrorContinue = false, /* Affects only asynchronous errors */
bool executeOnCapturedContext = false)
{
if (source == null) throw new ArgumentNullException(nameof(source));
if (action == null) throw new ArgumentNullException(nameof(action));
if (maxActionsPerTimeUnit < 1)
throw new ArgumentOutOfRangeException(nameof(maxActionsPerTimeUnit));
if (timeUnit < TimeSpan.Zero || timeUnit.TotalMilliseconds > Int32.MaxValue)
throw new ArgumentOutOfRangeException(nameof(timeUnit));
using var semaphore = new SemaphoreSlim(maxActionsPerTimeUnit,
maxActionsPerTimeUnit);
using var cts = new CancellationTokenSource();
var tasks = new List<Task<TResult>>();
var releaseTasks = new List<Task>();
try // Watch for exceptions thrown by the source enumerator
{
foreach (var item in source)
{
try
{
await semaphore.WaitAsync(cts.Token)
.ConfigureAwait(executeOnCapturedContext);
}
catch (OperationCanceledException) { break; }
// Exceptions thrown synchronously by invoking the action are breaking
// the loop unconditionally (the onErrorContinue has no effect on them).
var task = action(item);
if (!onErrorContinue) task = ObserveFailureAsync(task);
tasks.Add(task);
releaseTasks.Add(ScheduleSemaphoreReleaseAsync(task));
}
}
catch (Exception ex) { tasks.Add(Task.FromException<TResult>(ex)); }
cts.Cancel(); // Cancel all release tasks
Task<TResult[]> whenAll = Task.WhenAll(tasks);
try { return await whenAll.ConfigureAwait(false); }
catch (OperationCanceledException) when (whenAll.IsCanceled) { throw; }
catch { whenAll.Wait(); throw; } // Propagate AggregateException
finally { await Task.WhenAll(releaseTasks); }
async Task<TResult> ObserveFailureAsync(Task<TResult> task)
{
try { return await task.ConfigureAwait(false); }
catch { cts.Cancel(); throw; }
}
async Task ScheduleSemaphoreReleaseAsync(Task<TResult> task)
{
if (includeAsynchronousDuration)
try { await task.ConfigureAwait(false); } catch { } // Ignore exceptions
// Release only if the Task.Delay completed successfully
try { await Task.Delay(timeUnit, cts.Token).ConfigureAwait(false); }
catch (OperationCanceledException) { return; }
semaphore.Release();
}
}
Usage example:
int[] results = await ForEachAsync(Enumerable.Range(1, 100), async n =>
{
await Task.Delay(500); // Simulate some asynchronous I/O-bound operation
return n;
}, maxActionsPerTimeUnit: 30, timeUnit: TimeSpan.FromSeconds(1.0),
includeAsynchronousDuration: true);
The reasons for propagating an AggregateException using the catch+Wait technique, are explained here.
I have code which calls an external lib synchronous operation, which can take a very long time to finish. I can't rewrite this lib, and there is not a method to stop the operation. Is there any solution to stop this task after some timeout?
I have tried this code, but it does not work not as I expected. It awaits until the calculation is not completed.
How can I solve this task?
private static async Task<ResultData> GetResultAsync(string fileName)
{
var timeoutSource = new CancellationTokenSource(new TimeSpan(0, 5, 0));
try
{
return await Task.Run(() =>
{
var result = ExternLib.Calculate(fileName);
if (result == null)
{
throw new CalculationException(fileName);
}
return result;
},
timeoutSource.Token
).ConfigureAwait(false);
}
catch (AggregateException e)
{
SomeCode(e);
}
catch (OperationCanceledException e)
{
SomeCode2(e);
}
catch (Exception e)
{
SomeCode3(e);
}
return await Task.FromResult<ResultData>(null).ConfigureAwait(false);
}
Create two tasks, one which does the work, and one which acts as a timer:
var workTask = Task.Run(() => // etc );
var timerTask = Task.Delay(TimeSpan.FromMinutes(10));
The wait for either task to complete:
var completedTask = Task.WaitAny(new[] { workTask, timerTask });
Then, if completedTask is the timer task, your timeout has expired, and you can take appropriate action: whether or not you can stop the long running task depends on how it's structured, but you do know you can stop waiting for it.
I want to make Task.WaitAll() to break out if any of the running tasks throws an exception, so that I don't have to wait for 60 seconds to finish. How do I achieve such behavior? If WaitAll() cannot achieve that, is there any other c# feature or workaround?
Task task1 = Task.Run(() => throw new InvalidOperationException());
Task task2 = ...
...
try
{
Task.WaitAll(new Task[]{task1, task2, ...}, TimeSpan.FromSeconds(60));
}
catch (AggregateException)
{
// If any exception thrown on any of the tasks, break out immediately instead of wait all the way to 60 seconds.
}
The following should do it without altering the code of the original tasks (untested):
static bool WaitAll(Task[] tasks, int timeout, CancellationToken token)
{
var cts = CancellationTokenSource.CreateLinkedTokenSource(token);
var proxyTasks = tasks.Select(task =>
task.ContinueWith(t => {
if (t.IsFaulted) cts.Cancel();
return t;
},
cts.Token,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Current).Unwrap());
return Task.WaitAll(proxyTasks.ToArray(), timeout, cts.Token);
}
Note it only tracks faulted tasks (those which threw). If you need to track cancelled tasks as well, make this change:
if (t.IsFaulted || t.IsCancelled) cts.Cancel();
Updated, waiting on the task proxies is redundant here, as pointed out by #svick in the comments. He proposes an improved version: https://gist.github.com/svick/9992598.
One way of doing that is to use CancellationTokenSource. You create cancellationtokensource, and pass it as an argument to Task.WaitAll. The idea is to wrap your task in try/catch block, and in case of exception, call cancel on cancellationtokensource.
Here's sample code
CancellationTokenSource mainCancellationTokenSource = new CancellationTokenSource();
Task task1 = new Task(() =>
{
try
{
throw new Exception("Exception message");
}
catch (Exception ex)
{
mainCancellationTokenSource.Cancel();
}
}, mainCancellationTokenSource.Token);
Task task2 = new Task(() =>
{
Thread.Sleep(TimeSpan.FromSeconds(3));
Console.WriteLine("Task is running");
}, mainCancellationTokenSource.Token);
task1.Start();
task2.Start();
Task.WaitAll(new[] { task1, task2},
6000, // 6 seconds
mainCancellationTokenSource.Token
);
}
catch (Exception ex)
{
// If any exception thrown on any of the tasks, break out immediately instead of wait all the way to 60 seconds.
}
Parallel class can do the job for you. You can use Parallel.For, ForEach or Invoke.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Sample_04_04_2014_01
{
class Program
{
public static void Main(string[] args)
{
try
{
Parallel.For(0,20, i => {
Console.WriteLine(i);
if(i == 5)
throw new InvalidOperationException();
Thread.Sleep(100);
});
}
catch(AggregateException){}
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
}
}
}
If one of these tasks throws an exception then no other task will be executed excepting those whose execution was already started. For, ForEach and Invoke are waiting for all tasks to complete before to resume control to the calling code. You can have even a finer grain control if you use ParallelLoopState.IsExceptional. Parallel.Invoke is more suited for your case.
I wanted to suggest a slight modification to Noseratio's excellent answer above. In my case I needed to preserve the original exception thrown, and in a surrounding try/catch distinguish between cancelled and exception states.
public static void WaitUnlessFault( Task[] tasks, CancellationToken token )
{
var cts = CancellationTokenSource.CreateLinkedTokenSource(token);
foreach ( var task in tasks ) {
task.ContinueWith(t =>
{
if ( t.IsFaulted ) cts.Cancel();
},
cts.Token,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Current);
}
try {
Task.WaitAll(tasks, cts.Token);
}
catch ( OperationCanceledException ex ) {
var faultedTaskEx = tasks.Where(t => t.IsFaulted)
.Select(t => t.Exception)
.FirstOrDefault();
if ( faultedTaskEx != null )
throw faultedTaskEx;
else
throw;
}
}
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>>();
}