Asp.Net Core Using Tasks And Cancellation Tokens Complete Guide
Understanding the Core Concepts of ASP.NET Core Using Tasks and Cancellation Tokens
ASP.NET Core Using Tasks and Cancellation Tokens
Understanding Tasks
In .NET, a Task
represents an asynchronous operation. It enables you to perform work asynchronously and handle the result once the operation is completed. Using Task
in ASP.NET Core is essential for handling I/O-bound and long-running operations without blocking the main thread.
Creating and Running Tasks
In ASP.NET Core, tasks can be initiated using the Task.Run
or Task.Factory.StartNew
methods. Functions like HttpClient.SendAsync
, DbContext.SaveChangesAsync
, and file I/O operations are typically performed asynchronously using tasks. Here’s an example of running an asynchronous task:
public async Task PerformOperationAsync()
{
await Task.Run(() =>
{
// Simulate an IO operation
Thread.Sleep(3000);
// Perform some operation here
});
}
In the example above, Task.Run
is used to offload the operation to a background thread, preventing the main thread from becoming blocked.
Async/Await Pattern
The async
and await
keywords simplify writing asynchronous code. They transform regular synchronous methods into asynchronous methods that can be non-blocking. Using async/await
helps in readability and maintainability:
public async Task<string> FetchDataAsync()
{
var httpClient = new HttpClient();
return await httpClient.GetStringAsync("https://api.example.com/data");
}
Here, GetStringAsync
is called asynchronously using await
, and the method itself is declared as async
to enable the use of await
.
Cancellation Tokens
A CancellationToken
is used to communicate cancellation requests to the method performing the operation. This is important for scenarios where the operation may be lengthy and you need the ability to cancel it. Here’s how to use a CancellationToken
:
public async Task PerformCancelableOperationAsync(CancellationToken cancellationToken)
{
using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
{
cts.CancelAfter(5000); // Cancel after 5 seconds
try
{
await Task.Delay(10000, cts.Token); // Simulate a long-running operation
}
catch (OperationCanceledException)
{
// Handle cancellation
}
}
}
In this code:
- A
CancellationTokenSource
is created linked to the providedcancellationToken
. CancelAfter
schedules the cancellation to occur after a specified delay.Task.Delay
simulates a long-running operation.- An
OperationCanceledException
is caught and handled if the operation is canceled.
Important Use Cases
- Web APIs: For long-running web API calls, cancellation tokens ensure that client aborts can be respected, preventing unnecessary processing.
- Background Services: In background worker services, tasks can be canceled gracefully, reducing resource consumption.
- File Operations: Large file upload or download operations can be canceled if the user decides to abort.
Key Important Info
- Thread Safety: Ensure that shared resources accessed by multiple tasks are thread-safe.
- Exception Handling: Properly handle exceptions within tasks to prevent unhandled exceptions propagating.
- Resource Cleanup: Use
finally
blocks orusing
statements to ensure that resources are cleaned up correctly. - Performance Considerations: Avoid unnecessary synchronous operations, as they can block threads and degrade performance.
Conclusion
Online Code run
Step-by-Step Guide: How to Implement ASP.NET Core Using Tasks and Cancellation Tokens
Prerequisites
- Basic knowledge of ASP.NET Core.
- Basic understanding of C# and asynchronous programming.
Example Overview
In this example, we will create an ASP.NET Core web application that:
- Starts a long-running task using
Task.Run
when the user navigates to the/Process
endpoint. - Allows the user to cancel this task via the
/Cancel
endpoint.
Steps
Step 1: Create an ASP.NET Core Web Application
First, let's create a new ASP.NET Core Web API project.
Command Line (dotnet)
dotnet new webapi -n AspNetCoreTasksAndCancellationTokenExample
cd AspNetCoreTasksAndCancellationTokenExample
Step 2: Define a Background Service
We will create a service that manages our long-running task and its cancellation token.
- Create a
BackgroundService.cs
file in theServices
folder inside your project.
Services/BackgroundService.cs
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;
public class BackgroundService
{
private readonly ILogger<BackgroundService> _logger;
private CancellationTokenSource _cancellationTokenSource;
private Task _backgroundTask;
public BackgroundService(ILogger<BackgroundService> logger)
{
_logger = logger;
_cancellationTokenSource = new CancellationTokenSource();
}
public void StartProcessing()
{
if (_backgroundTask != null && _backgroundTask.Status == TaskStatus.Running)
{
_logger.LogInformation("Task is already running.");
return;
}
_cancellationTokenSource = new CancellationTokenSource();
_backgroundTask = Task.Run(AsyncLongRunningProcess, _cancellationTokenSource.Token);
_logger.LogInformation("Process started.");
}
private async Task AsyncLongRunningProcess()
{
try
{
var cancellationToken = _cancellationTokenSource.Token;
for (var i = 0; i < 100; i++)
{
cancellationToken.ThrowIfCancellationRequested();
// Simulate work with Delay and LogInformation
await Task.Delay(200, cancellationToken);
_logger.LogInformation($"Processing iteration {i + 1}.");
}
_logger.LogInformation("Process completed successfully.");
}
catch (OperationCanceledException ex)
{
_logger.LogWarning(ex, "Process was cancelled.");
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred during the process.");
}
}
public void CancelProcessing()
{
if (_backgroundTask == null || _backgroundTask.Status != TaskStatus.Running)
{
_logger.LogInformation("No running task to cancel.");
return;
}
_cancellationTokenSource.Cancel();
// Optionally, wait the task to be cancelled.
try
{
_backgroundTask.Wait();
}
catch (AggregateException)
{
// Expected exception from the canceled task
_logger.LogInformation("Waited for task to be cancelled.");
}
_logger.LogInformation("Process cancelled.");
}
}
Step 3: Register the Service in Startup.cs
Next, let's register our service with ASP.NET Core dependency injection system.
Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Startup.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
namespace AspNetCoreTasksAndCancellationTokenExample
{
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Register BackgroundService as a singleton.
services.AddSingleton<BackgroundService>();
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
Step 4: Add Controllers to Handle HTTP Requests
Now, we add controllers to start and cancel the task.
Controllers/TaskController.cs
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System.Threading.Tasks;
[ApiController]
[Route("[controller]")]
public class TaskController : ControllerBase
{
private readonly BackgroundService _backgroundService;
private readonly ILogger<TaskController> _logger;
public TaskController(BackgroundService backgroundService, ILogger<TaskController> logger)
{
_backgroundService = backgroundService;
_logger = logger;
}
[HttpPost("process")]
public IActionResult Process()
{
_backgroundService.StartProcessing();
_logger.LogInformation("Received request to start processing.");
return Ok("Processing started.");
}
[HttpPost("cancel")]
public IActionResult Cancel()
{
_backgroundService.CancelProcessing();
_logger.LogInformation("Received request to cancel processing.");
return Ok("Processing cancelled.");
}
}
Step 5: Set Up Logging (Optional)
For better logging in this example, you might want to set up Serilog.
Program.cs Make sure to install the Serilog.AspNetCore package via NuGet:
dotnet add package Serilog.AspNetCore
Then configure Serilog:
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
public class Program
{
public static void Main(string[] args)
{
Log.Logger = new LoggerConfiguration()
.MinimumLevel.Debug()
.WriteTo.Console()
.CreateLogger();
try
{
Log.Information("Starting web host");
CreateHostBuilder(args).Build().Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "Host terminated unexpectedly");
}
finally
{
Log.CloseAndFlush();
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.UseSerilog() // Use Serilog
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
Step 6: Test the Endpoints
With the application running, you can test the endpoints:
Start the Process Send a POST request to
http://localhost:<port>/task/process
using a tool like Postman or curl.curl -X POST http://localhost:<port>/task/process
Cancel the Process While the process is running, send another POST request to
http://localhost:<port>/task/cancel
.curl -X POST http://localhost:<port>/task/cancel
You should see logs in the console indicating that the process started, and then either completed or was cancelled based on when you made the cancel request.
Summary
This example covers the basics of using tasks (Task.Run
) and cancellation tokens (CancellationTokenSource
) within an ASP.NET Core application to manage long-running processes. The BackgroundService
class is responsible for starting and cancelling the task. The TaskController
provides HTTP endpoints to control these operations.
Top 10 Interview Questions & Answers on ASP.NET Core Using Tasks and Cancellation Tokens
1. What are Tasks in ASP.NET Core?
Answer:
Tasks represent work that is executing asynchronously in .NET applications, including ASP.NET Core. They are used to handle long-running operations without blocking the main thread, improving performance and responsiveness. A Task
is an object representing work that has been queued for execution but hasn't yet completed.
2. How do I create a Task in ASP.NET Core?
Answer:
In ASP.NET Core, you can create tasks by using methods of the Task
class or by using async and await keywords. The simplest way to get started is with an async method:
public async Task<string> MyAsyncMethod()
{
await Task.Delay(1000); // Simulates a delay, often a network operation
return "Completed!";
}
The Task.Delay
method creates a task that completes after a specified amount of time.
3. Why should I use Tasks instead of synchronous code?
Answer:
Using tasks enhances application performance by preventing blocking of threads. Synchronous operations tie up the main thread, which can lead to deadlocks in web applications when I/O-bound operations (like database queries) take time to complete. Tasks allow these operations to be performed asynchronously, thereby making your application more responsive and scalable.
4. How does Cancellation Token work with Tasks?
Answer:
A CancellationToken
is a notification to a Task that it should attempt to cancel its operations as soon as possible. This is useful for stopping long-running tasks when necessary, such as when a user aborts an HTTP request.
public async Task DoWorkAsync(CancellationToken cancellationToken)
{
for (int i = 0; i < 10; i++)
{
cancellationToken.ThrowIfCancellationRequested(); // Throws if cancellation token is cancelled
await Task.Delay(1000); // Delay for a second
}
}
You can pass a CancellationToken
to asynchronous methods.
5. How do I request cancellation of a Task using Cancellation Token?
Answer:
To request the cancellation of a Task, you use the CancellationTokenSource
. When you call its Cancel()
method, it signals all CancellationToken
s derived from it to throw an exception on their next cancellation check.
CancellationTokenSource source = new CancellationTokenSource();
source.CancelAfter(TimeSpan.FromSeconds(2));
try
{
await DoWorkAsync(source.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("Operation was cancelled!");
}
Here, DoWorkAsync
will run for a maximum of 2 seconds before being cancelled.
6. Can I use multiple Cancellation Tokens with a single Task?
Answer:
No, you cannot directly use multiple CancellationToken
instances with a single task. However, you can combine tokens using the CancellationTokenSource.CreateLinkedTokenSource
method, which creates a new token that triggers cancellation when any of the combined tokens are cancelled.
CancellationTokenSource parentSource = new CancellationTokenSource();
parentSource.CancelAfter(TimeSpan.FromSeconds(5));
CancellationTokenSource childSource = new CancellationTokenSource();
childSource.CancelAfter(TimeSpan.FromSeconds(3));
using (CancellationTokenSource linkedSource =
CancellationTokenSource.CreateLinkedTokenSource(parentSource.Token, childSource.Token))
{
await DoWorkAsync(linkedSource.Token); // Will cancel earlier if either parent or child cancels
}
7. When would it be appropriate to use Cancellation Tokens in ASP.NET Core?
Answer:
Cancellation tokens are appropriate in scenarios where operations might need to be interrupted. Common examples include:
- User requests that are aborted.
- Long-running background tasks.
- API calls where responses may be delayed and the task can be stopped if necessary.
8. Are there any best practices when using Tasks and Cancellation Tokens in ASP.NET Core?
Answer:
Yes, here are some best practices:
- Ensure proper cancellation checks within your async methods so that they can respond to cancellation requests.
- Avoid catching
OperationCanceledException
unless you intend to handle it specifically, as this may suppress the notification intended for cancellation. - Use
HttpClient
with cancellation tokens to avoid hanging requests. - For long-running tasks, provide feedback or logs to users/administrators about the status.
9. How do I handle exceptions within a Task in ASP.NET Core?
Answer:
You should handle exceptions within the task code using try-catch blocks. If an exception occurs, it is wrapped inside an AggregateException
when using .Result
or accessed via the Exception
property of the Task.
public async Task TryCatchDemoAsync()
{
try
{
await Task.Run(() => { throw new InvalidOperationException("Boom!"); });
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"Caught exception: {ex.Message}");
}
}
Alternatively, you can handle exceptions after the task completes:
var task = Task.Run(() => { throw new InvalidOperationException("Boom!"); });
try
{
await task;
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"Caught exception: {ex.Message}");
}
Using await directly handles exceptions better as it doesn’t wrap them in AggregateException
.
10. Can I cancel a Task that hasn't started?
Answer:
No, CancellationTokenSource.Cancel()
doesn't prevent a task from starting. It only affects tasks that already observe the cancellation token. If a task isn't started yet, calling Cancel()
beforehand won't stop it; it will still execute unless you have your cancellation checks set appropriately at the task's start.
CancellationTokenSource source = new CancellationTokenSource();
source.Cancel();
var task = Task.Run(() =>
{
source.Token.ThrowIfCancellationRequested(); // Throws immediately as cancelled
}, source.Token);
// task will throw and hence not run till the lambda is reached
This code ensures that the task throws an exception and stops right away because the token is already cancelled before the task starts.
Login to post a comment.