A method marked with async
is awaitable like this:
public async Task Foo() { ... }
await Foo();
Because it is awaited, Foo is scheduled on a separate thread and when it returns, the result (in this case void or an exception) is unwrapped and returned to the caller. We have no control over the Task itself.
If work is scheduled with Task.Run
a cancellation token can be passed to the Run method like this:
var cts = new CancellationTokenSource();
var token = cts.Token;
Task.Run(() => {
while (!token.IsCancellationRequested)
{
}
}, token);
Let’s write the Action from above in a separate method:
private async Task Foo()
{
while (true) ???
{
}
}
While we can call and cancel Foo like this:
var task = Foo2().AsAsyncAction();
task.Cancel();
the only thing that would be cancelled, is the async operation itself. To cancel the action, we need access to the CancellationToken
so we can respond to IsCancellationRequested
:
private async Task Foo(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
}
}
Now we can pass the token when we call Foo. That way Foo can take action to cancel if needed.
If the caller of an async method is set null, the Task keeps running. Let’s check this behavior with a small Universal Windows demo Project.
Assume a UWP with a MainPage.xaml.cs
that instantiates class Caller
that instantiates class Worker
MainPage.xaml.cs
private Caller _caller;
private async void Button_Click(object sender, RoutedEventArgs e)
{
_caller = new Caller();
await _caller.Start();
}
Caller.cs
public async Task Start()
{
var worker = new Worker();
await worker.Foo();
}
Worker.cs
public async Task Foo()
{
while (true)
{
await Task.Delay(2000);
}
}
Now let’s set the Caller to null while a task is running:
private void Button_Click_2(object sender, RoutedEventArgs e)
{
_caller = null;
}
In the above example, if the MainPage sets the instance of Caller to null, the task keeps running. That might happen when you navigate away from the MainPage. To prevent orphan Tasks, we need to cancel them.
Just like in the previous example, pass CancellationToken
to the working method like this:
Worker.cs:
public async Task Foo(CancellationToken cancel)
{
while (!cancel.IsCancellationRequested)
{
// after cancelling, this code will not be executed anymore
// but when setting the caller to null, this code keeps executing.
// test it by putting a breakpoint on the next line
await Task.Delay(2000);
}
}
Create a Cancel method in the Caller:
Caller.cs:
public void Cancel()
{
_cancel.Cancel();
}
and now you can safely cancel and set _caller
to null in the MainPage:
private void Button_Click_2(object sender, RoutedEventArgs e)
{
_caller.Cancel();
_caller = null;
}
If you want to experiment yourself, download a demo that includes progress reporting.