C# 中的取消标记
你好!👋🏻
我已经有一段时间没写过关于 C# 的文章了,或者说,写过任何关于 C# 的东西了。不过,现在我有一个有趣的概念,我相信你一定在 C# 代码库的某个地方偶然发现或见过。
如果你使用过 Entity Framework、HttpClient 或任何包含异步方法的框架,那么几乎可以肯定,那里存在一个接受 a 的重载CancellationToken
。
那么,取消令牌是什么?为什么要使用它们?它们有什么好处?系好安全带,因为这就是我们将在这篇文章中回答的问题!
定义取消令牌🪙
C# 中的取消令牌是一个只读结构,根据 Microsoft 文档,其定义如下:
🔔 传播应取消操作的通知。
这意味着,取消令牌是一个对象,用于指示操作是否应在执行完成之前取消。
您必须了解这种机制,并且应该知道如何以及何时使用它。
如果您不相信它的重要性,我想展示一个真实的例子,这个例子应该足以吸引您并让您成为尽可能使用取消令牌的坚定支持者。
想象一下,你访问一个像亚马逊这样的网站,导航到某个产品类别,亚马逊开始加载该类别的大量产品,但你点错了类别,然后立即返回。假设亚马逊通常不使用任务取消的概念,那么即使你返回,网站仍会继续处理请求,只是任务没有被取消,数据库查询仍会运行并返回永远不会使用的数据。
这本质上就是在浪费应用程序资源。应用程序不得不执行一个可能要求很高的数据库查询,但检索到的数据却无处可寻。这不仅适用于数据检索,也适用于任何其他操作,例如对远程 API 的网络调用或 IO 操作。
C#中CancellationToken 的起源
既然您可能已经确信应该在异步 C# 代码中加入取消标记,那么让我们了解有关此概念的 C# 细节。
在 C# 中,若要获取取消令牌,我们需要创建一个 的实例CancellationTokenSource
,然后通过该源对象,我们可以使用该对象上的 属性获取关联的取消令牌。以下代码演示了如何在 C# 控制台应用中Token
创建:CancellationToken
CancellationTokenSource cts = new();
CancellationToken cancellationToken = cts.Token;
关于CancellationTokenSource
,它是向取消令牌发出信号表示应该取消的对象,它可以通过调用取消令牌的Cancel
方法立即取消,或者CancelAfter
指定令牌何时应该切换到取消状态。
源类有 4 个不同的构造函数:
CancellationTokenSource()
CancellationTokenSource(int millisecondsDelay)
CancellationTokenSource(TimeSpan delay)
CancellationTokenSource(TimeSpan delay, TimeProvider timeProvider)
总而言之,所有具有延迟的实例都会创建一个实例,该实例将在构造函数中提供的延迟之后在实例的取消令牌上发出取消信号。
基本编码示例
我们已经讨论了理论,但现在我想向您展示一个基本的代码片段,它应该可以从代码角度说明这个概念。
CancellationTokenSource cts = new(3000);
CancellationToken cancellationToken = cts.Token;
Task lazyCountingTask = Task.Run(() => LazyCounterFunction(cancellationToken), cancellationToken);
try
{
await lazyCountingTask;
}
catch (OperationCanceledException ex)
{
Console.WriteLine("Cancellation Was Requested! Lazy counter defeated");
}
static async Task LazyCounterFunction(CancellationToken cancellationToken)
{
int counter = 0;
while (true)
{
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("WE'VE BEEN COMMANDED TO CANCEL!!!");
cancellationToken.ThrowIfCancellationRequested();
}
Console.WriteLine($"Counting... Currently At: {counter++}");
await Task.Delay(1000);
}
}
提供的代码创建了一个计数器函数,该函数每秒从 0 开始递增一个计数器值。在 while 循环中,我们检查IsCancellationRequested
取消令牌实例上的属性,以便在触发取消时,将一条消息记录到控制台,然后调用ThrowIfCancellationRequested()
该令牌。此方法将抛出一个OperationCancelledException
,调用代码可以捕获该 ,以便在任务被取消时执行某些逻辑。
在 中Main
,我们创建一个任务,然后在一个try catch
块内等待它。然而,当我实例化取消令牌源时,我传递了3000
一个参数,该参数是一个以毫秒为单位的延迟时间,这应该模拟一个进程在被取消之前需要 3 秒的时间。
取消令牌的实际用例⚙️
现在,有了示例计数器程序,让我们研究一些现实世界的用例,以突出取消令牌的好处。
[HttpGet("get-users")]
public async Task<IActionResult> GetUsersAsync(CancellationToken cancellationToken)
{
try
{
var users = await UsersRepo.GetAllUsersAsync(cancellationToken);
return Ok(users);
}
catch (OperationCanceledException ex)
{
return Ok("Cancelled loading users query");
}
}
从这个演示 API 端点开始,这个端点应该加载数据库中所有可用的用户并返回它们。
⚠️请注意,这是一个不完整的实现,在这种情况下,您需要处理其他类型的异常,添加分页,过滤等。
在我们的端点参数中,我们添加了一个取消令牌参数,现在当您编写一个用于HttpClient
调用此端点的服务时,您可以传递一个取消令牌,正如我们之前所说,您可以从取消令牌源对象中获取,也许在依赖于该 http 服务的用户界面中,您可以添加一个取消按钮,或者检查用户何时从页面导航,如果任务仍然未完成,您可以调用该Cancel
方法以便正确取消任务。
使用 Entity Framework Core 的取消令牌
当使用Async
Entity Framework Core LINQ 方法的变体时,您总会发现一个接受取消令牌的重载,现在既然您已经学会了如何使用这个强大的工具,那么您应该尽可能地传递一个。
让我们研究一下这段代码:
public class EventsManagementService
{
private readonly ApplicationDbContext _dbContext;
public EventsManagementService(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
public async Task<List<LogStore>> GetEventsAsync(CancellationToken cancellationToken)
{
// Use AsNoTracking() to avoid change tracking overhead
var events = await _dbContext.LogStore.AsNoTracking().ToListAsync(cancellationToken);
return events;
}
}
在这个事件管理服务类中,我们有一个用于检索对象列表的方法LogStore
,该方法接受一个取消令牌,该令牌会传递给该ToListAsync
方法。EF Core 的这个方法会通过枚举查询结果异步创建一个列表IQueryable
。这样,当请求取消时,该方法将不会继续创建列表,从而避免应用程序资源的浪费。
结论
在本文中,我们大致了解了任务取消的概念,以及它在 C# 中的工作原理。我不想深入探讨理论,而是通过实际代码来展示这个概念的实际应用。掌握了这些新知识后,您就可以顺利地获取取消令牌、设置取消延迟,并实际取消异步操作了。
希望您能从本文中学到一些新东西!