How to Use a Cancellation Token to Cancel a Running Task in Xamarin

Objective: Learn to use a Cancellation Token for Asynchronous Tasks.

Prerequisites: You need to know C#, basic understanding of how Async/Await works, and the basics of Xamarin.

Let’s begin:

For this tutorial we will use a HttpClient to get the status of ten websites and we will also be able to stop the operation at any moment.

First things first:

Create a new project and replace the MainPage.xaml code with this one:


<?xml version="1.0" encoding="utf-8"?>

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"

xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"

x:Class="TTXCancellationToken.MainPage">

<StackLayout Padding="20">

 <Button Text="Start operation" Clicked="BtnStartOperation"/>

 <ListView x:Name="LstResults"/>

</StackLayout>

</ContentPage>

 

And the MainPage.xaml.cs with this one:

using System;
using System.Collections.Generic;
using Xamarin.Forms;

namespace TTXCancellationToken
{
public partial class MainPage : ContentPage
 {
  public MainPage()
  {
    InitializeComponent();
  }
  private async void BtnStartOperation(object sender, System.EventArgs e)
  {
  }

 }
}

 

Read the code from both files and put the name of your project instead of “TTXCancellationToken”.

Run it and you should see something like this:

 

Now create a new class called WebEngine and add a HttpClient:


private HttpClient _httpClient;

You will need the following Using Directive:


using System.Net.Http;

And in the constructor initialize it:

public WebEngine()
{
  _httpClient = new HttpClient();
}

 

Now we will need two methods in this WebEngine class, the first one will return the list of ten websites we will check:

public List<string> GetWebsites()
{
 var output = new List<string>
{
 "https://www.google.com.mx",

 "https://www.google.co.uk",

 "https://www.google.com",

 "https://www.google.de",

 "https://www.google.ca",

 "https://www.google.nl",

 "https://www.google.fr",

 "https://www.google.it",

 "https://www.google.pl",

 "https://www.google.com.gr"
};

return output;
}

 

Before adding the next method first create a new class called WebsiteModel, we will use it to store website’s data:

public class WebsiteModel

{

 public string URL { get; set; } = "";

 public string Status { get; set; } = "";

}

 

Returning to the WebEngine class, the second method will check every website from the list and return a list with the results:

public async Task<List<WebsiteModel>> GetWebsitesDataAsync()

{

 var output = new List<WebsiteModel>();

 var websites = GetWebsites();

 foreach (var site in websites)

 {

  using (HttpResponseMessage response = await _httpClient.GetAsync(site))

 {

  string serverStatus = "";

  serverStatus = Convert.ToString(response.StatusCode);

  output.Add(new WebsiteModel { URL = site, Status = $"Status: {serverStatus}" });

 }

 }

return output;

}

1- We use the generic version of Task to indicate that a List of WebsiteModels will be returned.

You will need the following Using Directive to use Task: using System.Threading.Tasks;

2- With the foreach we iterate over all URLs to use the HttpClient for each one.

3- Since HttpResponseMessage isn’t managed by the garbage collector is good practice to use the using statement to automatically call the Dispose method to delete the object from memory.

The GetAsync method from the HtttpClient is awaited so this method runs asynchronously.

By making the “response” variable HttpResponseMessage we store the status of each website passed to the GetAsync method from the HttpClient.

4- For each website we create a new WebsiteModel with its URL and Status and add it to the list of WebsiteModels this method will return.

 

Return to MainPage.xaml.cs and add this to the BtnStartOperation method:

private async void BtnStartOperation(object sender, System.EventArgs e)
{
 var webEngine = new WebEngine();
 var source = await webEngine.GetWebsitesDataAsync();
 
 ShowResults(source);
}

Remember to make it async.

1- We store the list of WebsiteModels returned from GetWebsitesDataAsync in the variable “source”.

2- Then we execute the ShowResults method passing it the list. This method isn’t yet created.

Create the ShowResults method and add this code:

private void ShowResults(List<WebsiteModel> source)

{

var output = new List<string>();



foreach (var site in source)

{

 output.Add($"{site.URL} - {site.Status} ");

}

LstResults.ItemsSource = output;

}

 

1- This list of strings is the one we will use to fill the ListView.

2- For each WebsiteModel in the list passed to this method we add a new string to the list this method will return.

Each string representing a WebsiteModel will contain the URL of the site and its Status.

3- The final list of strings is assigned as the source of the ListView and its displayed on screen.

 

Run the app and you should see something like this (click to play video):

 

Return to MainPage.xaml and add the following:


<Button Text="Cancel operation" Clicked="BtnCancel"/>

<ProgressBar x:Name="ProgressBar"/>

<Label x:Name="LblInfo" TextColor="Red"/>

 

The StackLayout should now look like this:

 

Go to MainPage.xaml.cs and add:

void BtnCancel(object sender, System.EventArgs e)

{

}

 

Leave it empty for now.

Now we will use the Progress class to update the ProgressBar and the ListView in real time, but first create a new class called ProgressReportModel:

public class ProgressReportModel

{

public double Progress { get; set; } = 0;

public List<WebsiteModel> CheckedWebsites { get; set; } = new List<WebsiteModel>();

}

 

1- Here we will store the progress so that later the ProgressBar can use it as its progress.

2- And here we will store the websites as they are checked so that this list can be used to update the ListView in real time.

 

Return to MainPage.xaml.cs and in the method BtnStartOperation add the following:

var progress = new Progress<ProgressReportModel>();

progress.ProgressChanged += Update;

 

As you can see Update is a method(reference) we add to the event ProgressChanged from our Progress object, this method will be called every time we finish getting the data of a website, more on that later, but first we need to create this Update method in this same class:

private void Update(object sender, ProgressReportModel e)

{

if (e.Progress < 100)

ProgressBar.Progress = Convert.ToDouble("." + e.Progress);

else

ProgressBar.Progress = 1;

ShowResults(e.CheckedWebsites);

}

 

1- We will make the GetWebsitesDataAsync method from WebEngine send a report with the progress of the checked websites and since the ProgressBar considers its progress from 0 to 1 it means when we first process the first website of ten we will make it .10 so the ProgressBar increases by 10%

2- One of the properties of the ProgressReportModel is CheckedWebsites this one will store a list of the checked websites and here we access it and use the ShowResults method to update the ListView.

 

Return to the BtnStartOperation method and in this line:

var source = await webEngine.GetWebsitesDataAsync();

 

Pass the recently created Progress object to the GetWebsitesDataAsync method, it should now look like this:

var source = await webEngine.GetWebsitesDataAsync(progress);

 

Now go to the WebEngine class and we need to make the GetWebsitesDataAsync method ask for the Progress object as an IProgress interface:

public async Task<List<WebsiteModel>> GetWebsitesDataAsync(IProgress<ProgressReportModel> progress)

 

Now inside this same method we need to create a ProgressReportModel:

var report = new ProgressReportModel();

 

Add it below the websites variable, so it will look like this:

 

In this same method, just after finishing checking a website we create a report to send it to the Progress object:

report.CheckedWebsites = output;

report.Progress = (output.Count * 100) / websites.Count;

progress.Report(report);

 

The method now should look like this:

1- In the report we are creating we assign its CheckedWebsites property the current list of checked websites.

2- For the Progress property we calculate the progress by multiplying the number of checked websites times 100 and dividing that for the total number of websites that will be checked.

3- We send the report by calling the Report method of the Progress object passing it the recently created report. This will activate the event ProgressChanged from the Progress object and it will call the Update method.

 

Run the app and now it should look like this (click to play video):

 

Finally, to cancel this running operation we need to create a CancellationTokenSource, go back to MainPage.xaml.cs and at the top create it:

CancellationTokenSource _cts = new CancellationTokenSource();

 

You will need the following Using Directive:

using System.Threading;

 

This is how the top of the class should look:

 

Go to the BtnStartOperation method and send the Token to the GetWebsitesDataAsync method:

var source = await webEngine.GetWebsitesDataAsync(progress, _cts.Token);

 

And we need to put that inside a try-catch block since the moment the operation is canceled an OperationCanceledException will be thrown, the code should now look like this:

1- Red since we still haven’t made the GetWebsitesDataAsync method ask for a Cancellation Token.

2- The UI will show “Operation canceled.” the moment OperationCanceledException is thrown.

 

In the BtnCancel method add this:

_cts.Cancel();

 

That will make a cancellation request.

Finally, go to the WebEngine class and make the GetWebsitesDataAsync method ask for a Cancellation Token:

public async Task<List<WebsiteModel>> GetWebsitesDataAsync(IProgress<ProgressReportModel> progress, CancellationToken cancellationToken)

 

For that the following Using Directive is needed:

using System.Threading;

 

And inside this same method just after adding a new WebsiteModel and before filling the report we add the following:

cancellationToken.ThrowIfCancellationRequested();

 

This will throw the OperationCanceledException exception if a cancellation request was made (in this case by clicking the BtnCancel button).

This is how the code should look:

 

Run the app and it should look like this (click to play video):

 

About the author

AezioX

Programmer


>