I have this code and I would like to get some clarification on the use of the CancellationToken.
I read this question about the difference between using a cancellation token and a flag:
Difference between CancellationTokenSource and exit flag for Task loop exit
One thing I noticed is that it mentions nothing about Exceptions. So here's my question. If the Disappearing() method is called then will this cause a TaskCanceledException() to occur and would this be a good reason to use the CancellationToken instead of a flag?
public partial class PhrasesFrame : Frame
{
CancellationTokenSource cts = new CancellationTokenSource();
public PhrasesFrame(PhrasesPage phrasesPage)
{
Device.BeginInvokeOnMainThread(() => ShowCards(cts.Token).ContinueWith((arg) => { }));
}
public void Disappearing()
{
cts.Cancel();
}
public async Task ShowCards(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
await PickCard();
}
}
public async Task PickCard()
{
await ShowCard();
}
private async Task ShowCard()
{
await ShowPhrase();
await ShowDetail();
}
private async Task ShowPhrase()
{
while (App.pauseCard || timer1Seconds > 0)
{
try
{
await Task.Delay(1000, tokenSource1.Token);
}
catch (TaskCanceledException)
{
// do action
break;
}
}
CancellationTokenSource.Cancel by itself doesn't throw such exception, but it "moves" all related cancellation tokens to cancelled state. When some code notifies that cancellation token is now in cancelled state - it might throw such exception. If look at your example, this part will not throw such exception:
public async Task ShowCards(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
await PickCard();
}
}
Because you just don't throw it in this block. If however you instead did something like this:
public async Task ShowCards(CancellationToken ct)
{
while (true)
{
ct.ThrowIfCancellationRequested();
await PickCard();
}
}
Then exception will be thrown, because, well, you throw it almost explicitly.
Now if look at another method from your example:
private async Task ShowPhrase()
{
while (App.pauseCard || timer1Seconds > 0)
{
try
{
await Task.Delay(1000, tokenSource1.Token);
}
catch (TaskCanceledException)
{
// do action
break;
}
}
}
If you were awaiting Task.Delay(1000, tokenSource1.Token); and then cancel tokenSource1 - then TaskCancelledException will indeed be thrown immediately, without waiting for the whole duration of Task.Delay. This is something you cannot easily achieve with just a boolean flag. If you used Thread.Sleep(1000) and boolean flag - change to that flag won't be noticed until whole duration of sleep is finished.
So to answer your question: in your example exception might or might not be thrown, depending on what part of code is currently executing at the moment you cancel your CancellationTokenSource (I assume that using two cancellation token sources with names cts and tokenSource1 is just a typo in your code, but if it's real code - then such exception cannot be thrown at all, because you cancel cts but Task.Delay waits on tokenSource1).
Related
I don't think this question is a duplicate of "Proper way to deal with exceptions in DisposeAsync".
Let's say my class that implements IAsynsDisposable because it has a long-running background task, and DisposeAsync terminates that task. A familiar pattern might be the Completion property, e.g. ChannelReader<T>.Completion (despite ChannelReader doesn't implement IAsynsDisposable).
Is it considered a good practice to propagate the Completion task's exceptions outside DisposeAsync?
Here is a complete example that can be copied/pasted into a dotnet new console project. Note await this.Completion inside DisposeAsync:
try
{
await using var service = new BackgroundService(TimeSpan.FromSeconds(2));
await Task.Delay(TimeSpan.FromSeconds(3));
}
catch (Exception ex)
{
Console.WriteLine(ex);
Console.ReadLine();
}
class BackgroundService: IAsyncDisposable
{
public Task Completion { get; }
private CancellationTokenSource _diposalCts = new();
public BackgroundService(TimeSpan timeSpan)
{
this.Completion = Run(timeSpan);
}
public async ValueTask DisposeAsync()
{
_diposalCts.Cancel();
try
{
await this.Completion;
}
finally
{
_diposalCts.Dispose();
}
}
private async Task Run(TimeSpan timeSpan)
{
try
{
await Task.Delay(timeSpan, _diposalCts.Token);
throw new InvalidOperationException("Boo!");
}
catch (OperationCanceledException)
{
}
}
}
Alternatively, I can observe service.Completion explicitly in the client code (and ignore its exceptions inside DiposeAsync to avoid them being potentially thrown twice), like below:
try
{
await using var service = new BackgroundService(TimeSpan.FromSeconds(2));
await Task.Delay(TimeSpan.FromSeconds(3));
await service.Completion;
}
catch (Exception ex)
{
Console.WriteLine(ex);
Console.ReadLine();
}
class BackgroundService: IAsyncDisposable
{
public Task Completion { get; }
private CancellationTokenSource _diposalCts = new();
public BackgroundService(TimeSpan timeSpan)
{
this.Completion = Run(timeSpan);
}
public async ValueTask DisposeAsync()
{
_diposalCts.Cancel();
try
{
await this.Completion;
}
catch
{
// the client should observe this.Completion
}
finally
{
_diposalCts.Dispose();
}
}
private async Task Run(TimeSpan timeSpan)
{
try
{
await Task.Delay(timeSpan, _diposalCts.Token);
throw new InvalidOperationException("Boo!");
}
catch (OperationCanceledException)
{
}
}
}
Is there a concensus about which option is better?
For now, I've settled on a reusable helper class LongRunningAsyncDisposable (here's a gist, warning: barely tested yet), which allows:
to start a background task;
stop this task (via a cancellation token) by calling IAsyncDisposable.DisposeAsync at any time, in a thread-safe, concurrency-friendly way;
configure whether DisposeAsync should re-throw the task's exceptions (DisposeAsync will await the task's completion either way, before doing a cleanup);
observe the task's status, result and exceptions at any time via LongRunningAsyncDisposable.Completion property.
I have the following program:
class myClass
{
CancellationTokenSource cts;
public string someMethod(){
someMethodWhichIsAsync("10")
}
private async Task<string> someMethodWhichIsAsync(string data)
{
if(cts != null)
{
cts.Cancel();
}
cts = new CancellationTokenSource();
string myString = await Task.Run(() => someLoop(data,cts.Token) );
return "success";
}
private string someLoop(string data, CancellationToken token)
{
while (True)
{
if (token.IsCancellationRequested == true)
{
return "Canceled";
}
//Do some work in a continuous loop
}
return "successful end";
}
}
I have placed CancellationTokenSource cts; in the class global scope so that it is available every time any function runs.
However cts.Cancel(); only works if I include it inside the function which runs.
if (token.IsCancellationRequested == true)
{
return "Canceled";
}
Why is this? None of the example code I found requires this.
Well, as I see from documentation, CancellationTokenSource.Cancel is only sending a SIGNAL that the process should be cancelled. Then, with the Token of the Source, you check whether the Cancellation had been requested (which holds True only if you called the Cancel method before). So, this line is necessary to actually Cancel the operation.
This question has already been asked but I still don't get it... I'm trying to cancel a task but when I include ThrowIfCancellationRequested(), it is not catched and if I don't include it, the GUI freezes...
Here is my code that run without the ThrowIfCancellationRequested() but freeze the GUI:
public void StartProcess(double myvariable)
{
tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
processThread = Task.Factory.StartNew(() =>
{
while (true)
{
//Do some work with myvariable
if (token.IsCancellationRequested)
{
break;
}
}
}, token);
}
And the code with the ThrowIfCancellationRequested() that is not catched, the debugger stops on the line token.ThrowIfCancellationRequested():
public void StartProcess(double myvariable)
{
tokenSource = new CancellationTokenSource();
CancellationToken tokenDispatcher = tokenSource.Token;
processThread = Task.Factory.StartNew(() =>
{
try
{
while (true)
{
//Do some work with myvariable
if (token.IsCancellationRequested)
{
token.ThrowIfCancellationRequested();
break;
}
}
}
catch (AggregateException ae)
{
if (ae.InnerException is OperationCanceledException)
{
Console.WriteLine("Process Thread Cancelled");
}
else
{
Console.WriteLine("ERROR");
}
}
}, token);
}
What am I doing wrong?
The ThrowIfCancellationRequested is the proper way to cancel the task. What you need to understand is that there will be no exception when you cancel a Task. Only the Status of the task will be set to Canceled. The try catch block you have inside the task will do nothing.
To get an exception you need to await the Task in an async method or use the Wait method to wait for the finish of the task. Using the Wait method is not recommended because you are blocking the current thread until the task finishes.
Here is a suggestion how you could implement this from your code.
public static Task StartProcess(double myvariable)
{
tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
return Task.Factory.StartNew(() =>
{
while (true)
{
//Do some work with myvariable
token.ThrowIfCancellationRequested();
}
}, token);
}
public async Task ExecuteProcess(double myvariable)
{
try
{
await StartProcess(myvariable);
}
catch (OperationCanceledException ex)
{
Console.WriteLine("Process Thread Canceled");
}
catch (Exception ex)
{
Console.WriteLine("ERROR");
}
}
The ExecuteProcess method demonstrates how you need to call your function to get the exception. This way you also prevent the GUI from blocking if you call the method from the UI thread.
I recommend you read the documentation on asynchronous programming to get more understanding on how it works.
How do you wait until a function with a task inside is done before continuing?
public void A()
{
Debug.Log("before")
CopyInfoFromDB();
Debug.Log("after")
}
public void CopyInfoFromDB()
{
FirebaseDatabase.DefaultInstance.GetReference(path)
.GetValueAsync().ContinueWith(task =>
{
if (task.IsFaulted)
{
Debug.Log("failed");
}
name = ...// loading local varibles from Task.result
});
}
I want it to wait for CopyInfoFromDB to be completed before printing "after". How should I write function A differently?
If you are going to use async-await - be prepared to make all methods involved in pipeline to be asynchronous as well
public async Task A()
{
Debug.Log("before")
await CopyInfoFromDB();
Debug.Log("after")
}
public Task CopyInfoFromDB()
{
return FirebaseDatabase.DefaultInstance
.GetReference(path)
.GetValueAsync();
}
In case GetValueAsync fails, exception will be thrown on the line where you are awaiting for it.
I have an event in my WPF .NET 4.5 application that can be triggered up to 10 times per second, the event is computing data that is not required that often so I'm looking for a way to remove unnecessary computation stress and call the method if event wasn't triggered for 3 seconds. I thought about using async functionality like that:
private async Task DoWork()
{
await Task.Delay(3000);
...
}
private void Event()
{
Task.Run(() => DoWork());
}
I'm not really sure about how to elegantly handle disposing of unwanted Tasks, ie. when user trigger the event, every task that was created by that event should be terminated and it should be as fast as possible. I've tried with CancellationToken but I'm not sure this is the right approach for my case
You can use CancellationTokenSource to let the code inside task know that it is canceled:
private CancellationTokenSource CancellationTokenSource { get; } = new CancellationTokenSource ();
Let's change:
private async Task DoWork()
{
await Task.Delay(3000);
...
}
To:
private async Task DoWork(int timeout = 3000)
{
await Task.Delay(timeout, CancellationTokenSource.Token);
if(!CancellationTokenSource.Token.IsCancellationRequested)
{
...
}
}
Now we can cancel our task if required:
CancellationTokenSource.Cancel();
Task.Delay will observe the CancellationToken and if it is in a Canceled state it will abort the task execution. Later in code we check whether we need to do anything or not.
That was my solution based on #Fabjan answer.
private static void RunSingleTask(ref CancellationTokenSource cts, int delay, Action func)
{
if (cts != null)
{
cts.Cancel();
cts.Dispose();
}
cts = new CancellationTokenSource();
var token = cts.Token;
Task.Run(async () =>
{
try
{
await Task.Delay(delay, token);
}
catch (TaskCanceledException)
{
return;
}
await Application.Current.Dispatcher.BeginInvoke(func);
});
}