I have multiple heavy job calculation requests. The job may take different time. By using async and await I want to take the last requested result with canceling eventually unfinished previous tasks.
Currently I'm using BackGroundWorker with setting a job ID. I used only the the result with the last requested ID.
Can I rewrite the code with using async await?
private int backtestId;
private void PrepareStrategyCalculation()
{
backtestId = backtestManager.GetNextBacktestId();
strategy.BacktestId = backtestId;
backtestManager.StartBacktestWorker(strategy.Clone());
}
private void BacktestManager_StrategyBacktested(object sender, StrategyBacktestEventArgs e)
{
if (e.BacktestObject.Strategy.BacktestId != backtestId) return;
var calculatedStrategy = e.BacktestObject.Strategy;
...
}
EDIT:
Is this a solution?
private int backtestId;
private async void PrepareStrategyCalculation()
{
backtestId = backtestManager.GetNextBacktestId();
strategy.BacktestId = backtestId;
var calculatedStrategy = await backtestManager.StartBacktestAsync(strategy.Clone());
if (calculatedStrategy.BacktestId != backtestId) return;
...
}
Assuming your code is CPU-bound, then Task.Run is a suitable substitute for BackgroundWorker.
You can use CancellationTokenSource to cancel tasks. So, something like this would work, assuming that StartBacktestAsync is called from a single-threaded context such as a UI thread:
private CancellationTokenSource _cts;
async Task StartBacktestAsync()
{
if (_cts != null)
_cts.Cancel();
_cts = new CancellationTokenSource();
try
{
var token = _cts.Token;
await Task.Run(() => Backtest(token));
}
catch (OperationCanceledException)
{
// Any special logic for a canceled operation.
}
}
void Backtest(CancellationToken token)
{
... // periodically call token.ThrowIfCancellationRequested();
}
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 am trying to use the following technique to be able to have a worker task executing some operations, with a 10 sec timeout and without blocking the application.
internal void ReadAll()
{
var data = new byte[1];
Task.Factory.StartNew(() =>
{
var ct = new CancellationTokenSource();
var ReadAllTask = Task.Factory.StartNew(() =>
{
// Read all information
// [omit communication exchange via COM port]
ct.Cancel();
}, ct.Token);
// First thread waiting 10s for the worker to finish
ReadAllTask.Wait(10000, ct.Token);
if (ReadAllTask.Status == TaskStatus.RanToCompletion)
{
ReadAllComplete?.Invoke(true);
}
else
{
ct.Cancel();
ReadAllComplete?.Invoke(false);
}
});
}
This method is called by pressing a button. It seems to me that in debug configuration works properly, but not in release configuration where the "first thread" never reach the wait and no event is thrown.
Your code could be a lot simpler than current version. Easiest way to make a non-blocking method for event is to mark it with async keyword and use the await keyword to start the asynchronous read operation from SerialPort.BaseStream property.
Also, CancellationTokenSource could be created with time, after that it get cancelled automatically, and the right way to cancel is to call CancellationToken.ThrowIfCancellationRequested method. async/await mechanism will invoke the event in UI context, so code could be something like this:
// async void is a recommended way to use asynchronous event handlers
private async void btnReadAll_Click(object sebder, EventArgs e)
{
var data = new byte[2];
// cancel source after 10 seconds
var cts = new CancellationTokenSource(10000);
// Read all information
// [omit communication exchange via COM port]
// async operation with BaseStream
var result = await SerialPort.BaseStream.ReadAsync(data, 0, 2, cts.Token);
/*
* if you can't use the BaseStream methods, simply call this method here
* cts.Token.ThrowIfCancellationRequested();
*/
// this code would run only if everything is ok
// check result here in your own way
var boolFlag = result != null;
ReadAllComplete?.Invoke(boolFlag);
}
Here's just a quick rewrite to remove the event and wrap what appears to be a synchronous IO API in an async one. If at all possible you should switch to a true async API and drop the Task.Run.
private CancellationTokenSource cts;
public async void MyButtonhandler(object sender, EventArgs e) {
cts = new CancellationTokenSource();
try {
var result = await Task.Run(() => ReadAll(cts));
if (result) {
//success
} else {
//failure
}
} catch (TaskCanceledException ex) {
}
}
internal async Task<bool> ReadAll(CancellationTokenSource cts) {
byte[] data = new byte[1];
var timeout = TimeSpan.FromSeconds(10);
var ReadAllTask = Task.Run(() => {
// Read all information
// [omit communication exchange via COM port]
}, cts.Token);
if (await Task.WhenAny(ReadAllTask, Task.Delay(timeout)) == ReadAllTask) {
return true;
}
cts.Cancel();
return false;
}
Reading comments and answers to my question I learned a couple of useful things that solve my problem:
CancellationTokenSource can have an implicit timeout
use Task.Run instead Task.Factory.StartNew
don't need to cancel the task, the cts will do the work
Now my code is simpler and it works:
private void Read_All_Button_Click(object sender, RoutedEventArgs e)
{
// Start timedout task that will send all necessary commands
CancellationTokenSource cts = new CancellationTokenSource(10000);
Task.Run(() =>
{
oCommandSets.ReadAll(cts);
}, cts.Token);
}
and
internal void ReadAll(CancellationTokenSource cts)
{
// [communication]
if (cts.IsCancellationRequested)
{
ReadAllComplete?.Invoke(false);
}
else
{
ReadAllComplete?.Invoke(true);
}
}
In any case I need to learn more about multithreading.
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.
Let's suppose I have this simple snippet:
async void button_Click(object sender, RoutedEventArgs e)
{
await Task.Factory.StartNew(() =>
{
Console.WriteLine("start");
Thread.Sleep(5000);
Console.WriteLine("end");
});
}
Obviously, everytime I push that button a new task is started even when a previous task still runs. How would I postpone any new task until all previous tasks have finished?
Some more details:
In the example above, each new task is identical to the task before. However, in the original context the sequence of tasks matters: Parameters may change (I could "simulate" it by using DateTime.Now.Ticks).
The tasks should be executed in the order they are "registered". Specificly, my program will talk to a serial device. I've done this before with a background thread utilizing a BlockingCollection. However, this time there's a strict request/response-protocol and I'd like to use async/await if it is possible.
Possible solution:
I could imagine creating tasks and storing them in a list. But how would I execute the tasks with respect to the requirements? Or should I return to the thread-based solution I have used before?
I recommend using a SemaphoreSlim for synchronization. However, you want to avoid Task.Factory.StartNew (as I explain on my blog), and also definitely avoid async void (as I explain in the MSDN article).
private SemaphoreSlim _mutex = new SemaphoreSlim(1);
async void button_Click(object sender, RoutedEventArgs e)
{
await Task.Run(async () =>
{
await _mutex.WaitAsync();
try
{
Console.WriteLine("start");
Thread.Sleep(5000);
Console.WriteLine("end");
}
finally
{
_mutex.Release();
}
});
}
You could wait on a SemaphoreSlim asynchronously and release it once the job is done. Don't forget to configure the semaphore initialcount to 1.
private static SemaphoreSlim semaphore = new SemaphoreSlim(1);
private async static void DoSomethingAsync()
{
await semaphore.WaitAsync();
try
{
await Task.Factory.StartNew(() =>
{
Console.WriteLine("start");
Thread.Sleep(5000);
Console.WriteLine("end");
});
}
finally
{
semaphore.Release();
}
}
private static void Main(string[] args)
{
DoSomethingAsync();
DoSomethingAsync();
Console.Read();
}
What about trying the Dataflow.ActionBlock<T> with the (default) max degree of parallelism of 1. This way you don't need to worry about any of the thread safety / locking concerns.
It could look something like:
...
var _block = new ActionBlock<bool>(async b =>
{
Console.WriteLine("start");
await Task.Delay(5000);
Console.WriteLine("end");
});
...
async void button_Click(object sender, RoutedEventArgs e)
{
await _block.SendAsync(true);
}
You could also setup the ActionBlock to receive a Task or Func<Task>, and simply run / await this input. Which would allow multiple operations to be queued and awaited from different sources.
I might be missing something, but I don't think SemaphoreSlim is needed for the OP's scenario. I'd do it the following way. Basically, the code just await the previous pending instance of the task before continuing (no exception handling for clarity):
// the current pending task (initially a completed stub)
Task _pendingTask = Task.FromResult<bool>(true);
async void button_Click(object sender, RoutedEventArgs e)
{
var previousTask = _pendingTask;
_pendingTask = Task.Run(async () =>
{
await previousTask;
Console.WriteLine("start");
Thread.Sleep(5000);
Console.WriteLine("end");
});
// the following "await" is optional,
// you only need it if you have other things to do
// inside "button_Click" when "_pendingTask" is completed
await _pendingTask;
}
[UPDATE] To address the comment, here's a thread-safe version, when button_Click can be called concurrently:
Task _pendingTask = Task.FromResult<bool>(true);
object _pendingTaskLock = new Object();
async void button_Click(object sender, RoutedEventArgs e)
{
Task thisTask;
lock (_pendingTaskLock)
{
var previousTask = _pendingTask;
// note the "Task.Run" lambda doesn't stay in the lock
thisTask = Task.Run(async () =>
{
await previousTask;
Console.WriteLine("start");
Thread.Sleep(5000);
Console.WriteLine("end");
});
_pendingTask = thisTask;
}
await thisTask;
}
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>>();
}