VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • C#中关于取消令牌(CancellationToken)——异步任务中断

第一部分:C#基础入门
取消令牌(CancellationToken)——异步任务中断
实例介绍
你有没有遇到过这样的场景?
下载一个10GB的大文件,下到一半不想下了,点击取消按钮,但程序还在后台偷偷下载,浪费流量和内存;
调用第三方API接口,等了10秒还没响应,想自动终止请求,避免一直阻塞;
并行处理多个任务,其中一个出错了,想让所有任务都停止运行,而不是继续浪费资源。
这些场景的核心需求都是优雅中断异步任务。而C#中的取vb.net教程C#教程python教程SQL教程access 2010教程
消令牌(CancellationToken) 就是为解决这类问题而生——它通过协作式取消机制,让任务和调用方“商量着来”,任务可以在关键节点检查取消信号,清理资源后再终止,避免资源泄漏。
需求分析
本节通过三个典型场景,带你掌握取消令牌的用法:
1.用户主动取消:WPF/WinForms中,用户点击“取消”按钮中断下载任务;
2.超时自动取消:API调用超过指定时间(比如5秒),自动终止请求;
3.多任务共享取消:多个并行任务共享同一个取消信号,一个取消则全部停止;
4.资源清理:任务取消时,确保文件流、网络连接等资源被正确释放。
代码实现
前置条件:
目标框架:.NET 6+;
场景1需WPF项目(模拟UI交互);
需引用System.Net.Http(HttpClient)。
场景1:用户主动取消(WPF示例)
错误写法(无取消机制,任务继续运行):
csharp

	// WPF按钮点击事件(错误:无取消功能)
	private async void BtnStartDownload_Click(object sender, RoutedEventArgs e)
	{
	try
	{
	await DownloadLargeFileAsync("https://example.com/largefile.zip");
	MessageBox.Show("下载成功!");
	}
	catch (Exception ex)
	{
	MessageBox.Show($"下载失败:{ex.Message}");
	}
	}
	
	// 无取消机制的下载方法
	private async Task DownloadLargeFileAsync(string url)
	{
	using var client = new HttpClient();
	var response = await client.GetAsync(url); // 无法中断
	var content = await response.Content.ReadAsByteArrayAsync(); // 无法中断
	await File.WriteAllBytesAsync("largefile.zip", content); // 无法中断
	}

正确写法(用CancellationTokenSource支持取消):
csharp

	using System.Net.Http;
	using System.IO;
	using System.Windows;
	
	namespace CancelTokenDemo
	{
	public partial class MainWindow : Window
	{
	private CancellationTokenSource _cts; // 取消令牌源(生产者)
	
	public MainWindow()
	{
	InitializeComponent();
	}
	
	// 开始下载按钮
	private async void BtnStartDownload_Click(object sender, RoutedEventArgs e)
	{
	// 先取消之前的任务(避免多个任务并行)
	_cts?.Cancel();
	_cts = new CancellationTokenSource(); // 创建新的取消源
	
	try
	{
	// 传递取消令牌(Token)给下载方法
	await DownloadLargeFileAsync("https://example.com/largefile.zip", _cts.Token);
	MessageBox.Show("下载成功!");
	}
	catch (OperationCanceledException)
	{
	MessageBox.Show("下载已取消");
	}
	catch (Exception ex)
	{
	MessageBox.Show($"下载失败:{ex.Message}");
	}
	finally
	{
	_cts.Dispose(); // 释放资源(必须!)
	_cts = null;
	}
	}
	
	// 取消下载按钮
	private void BtnCancelDownload_Click(object sender, RoutedEventArgs e)
	{
	_cts?.Cancel(); // 触发取消信号
	}
	
	// 支持取消的下载方法
	private async Task DownloadLargeFileAsync(string url, CancellationToken cancellationToken)
	{
	using var client = new HttpClient();
	// 传递Token给GetAsync(HttpClient支持取消)
	using var response = await client.GetAsync(url, cancellationToken);
	response.EnsureSuccessStatusCode(); // 检查HTTP响应是否成功
	
	// 传递Token给ReadAsStreamAsync(逐流读取,节省内存)
	using var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
	using var fileStream = new FileStream("largefile.zip", FileMode.Create, FileAccess.Write);
	
	// 逐块读取,每块检查取消状态
	var buffer = new byte[8192]; // 8KB缓冲区
	int bytesRead;
	while ((bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0)
	{
	// 检查取消(如果已取消,抛出OperationCanceledException)
	cancellationToken.ThrowIfCancellationRequested();
	await fileStream.WriteAsync(buffer, 0, bytesRead, cancellationToken);
	// 更新进度条(示例):ProgressBar.Value = (stream.Position * 100) / response.Content.Headers.ContentLength;
	}
	}
	}
	}

场景2:超时自动取消(API调用示例)
csharp

	// 超时自动取消:API调用超过5秒终止
	private async Task CallWeatherApiWithTimeoutAsync()
	{
	using var cts = new CancellationTokenSource();
	// 设置超时:5秒后自动触发取消
	cts.CancelAfter(TimeSpan.FromSeconds(5)); 
	
	try
	{
	var weather = await GetWeatherDataAsync("Beijing", cts.Token);
	Console.WriteLine($"北京天气:{weather}");
	}
	catch (OperationCanceledException)
	{
	Console.WriteLine("API调用超时(>5秒)");
	}
	catch (Exception ex)
	{
	Console.WriteLine($"API调用失败:{ex.Message}");
	}
	}
	
	// 支持取消的API调用方法
	private async Task<string> GetWeatherDataAsync(string city, CancellationToken cancellationToken)
	{
	using var client = new HttpClient();
	// 传递Token给GetAsync,超时会抛出OperationCanceledException
	var response = await client.GetAsync($"https://api.weatherapi.com/v1/current.json?key=YOUR_API_KEY&q={city}", cancellationToken);
	response.EnsureSuccessStatusCode();
	return await response.Content.ReadAsStringAsync(cancellationToken);
	}

场景3:多任务共享取消令牌
csharp

	// 多个并行任务共享取消信号
	private async Task ParallelDownloadsWithCancelAsync()
	{
	using var cts = new CancellationTokenSource();
	
	// 启动后台任务:监听用户输入,按任意键取消所有任务
	_ = Task.Run(() =>
	{
	Console.WriteLine("按任意键取消所有下载任务...");
	Console.ReadKey();
	cts.Cancel(); // 触发全局取消
	});
	
	try
	{
	// 创建3个下载任务,共享同一个Token
	var tasks = new List<Task>
	{
	DownloadFileAsync("file1.zip", cts.Token),
	DownloadFileAsync("file2.zip", cts.Token),
	DownloadFileAsync("file3.zip", cts.Token)
	};
	
	await Task.WhenAll(tasks); // 等待所有任务完成
	Console.WriteLine("所有文件下载完成!");
	}
	catch (OperationCanceledException)
	{
	Console.WriteLine("所有下载任务已取消!");
	}
	}
	
	// 模拟文件下载(支持取消)
	private async Task DownloadFileAsync(string fileName, CancellationToken cancellationToken)
	{
	Console.WriteLine($"开始下载:{fileName}");
	try
	{
	for (int i = 0; i < 10; i++) // 模拟10步下载
	{
	// 关键节点检查取消:如果已取消,抛出异常
	cancellationToken.ThrowIfCancellationRequested();
	// 模拟下载延迟(传递Token给Delay)
	await Task.Delay(1000, cancellationToken);
	Console.WriteLine($"{fileName}:下载进度 {i*10}%");
	}
	Console.WriteLine($"完成下载:{fileName}");
	}
	finally
	{
	Console.WriteLine($"清理资源:{fileName}(比如关闭文件流)");
	}
	}

逐行讲解
场景1:用户主动取消

CancellationTokenSource(_cts):

作为取消信号的生产者,负责生成CancellationToken(Token),并触发取消操作。
必须在finally块中Dispose,因为它持有定时器等资源,避免泄漏。

BtnStartDownload_Click:

先取消之前的任务(如果存在),防止多个任务并行。
创建新的_cts,传递_cts.Token给下载方法。
捕获OperationCanceledException处理用户取消。

DownloadLargeFileAsync:

传递cancellationToken给所有支持取消的异步方法(GetAsync、ReadAsStreamAsync、WriteAsync)。
用ThrowIfCancellationRequested在关键节点检查取消:如果已取消,立即抛出异常,终止任务。
逐块读取流(8KB缓冲区),既节省内存,又能在每块后检查取消。
场景2:超时自动取消
CancelAfter:设置超时时间,CancellationTokenSource会在指定时间后自动触发取消。
API调用中传递Token,超时后GetAsync会抛出OperationCanceledException,终止任务。
场景3:多任务共享取消
共享Token:3个下载任务使用同一个cts.Token,当用户按任意键时,所有任务都会收到取消信号。
finally块清理:任务取消时,finally块会执行资源清理(比如关闭文件流),避免泄漏。
基础知识拓展

  1. 取消令牌核心概念
概念 说明
协作式取消 任务必须主动检查取消信号,不能被强制终止(避免资源泄漏)。
CancellationTokenSource 取消信号的生产者:生成Token、触发取消(Cancel()/CancelAfter())。
CancellationToken 取消信号的消费者:用于检查取消状态(IsCancellationRequested/ThrowIfCancellationRequested)。
不可变性 Token一旦生成,状态不可修改,只能传递给任务(安全线程共享)。
资源释放 CancellationTokenSource必须Dispose,否则会泄漏定时器等资源。
  1. 取消令牌的正确用法
    传递Token:所有支持取消的异步方法(如Task.Delay、HttpClient、Stream操作)必须传递Token。
    关键节点检查:在任务的耗时操作前、循环迭代中、资源操作后检查取消状态。
    异常处理:OperationCanceledException是预期异常,要捕获并处理(比如提示用户取消成功)。
    注册回调:用cancellationToken.Register(() => { /* 清理操作 */ })在取消时执行资源清理(如关闭数据库连接)。
  2. 为什么不用Thread.Abort?
    Thread.Abort会强制终止线程,可能导致:
    资源泄漏(文件流未关闭、数据库连接未释放);
    数据损坏(比如正在写入文件时被终止);
    不可预测的异常(线程在任意位置被中断)。
    而取消令牌是协作式的,任务可以自己清理资源后再终止,更安全可靠。
  3. 取消令牌与async/await的结合
    await的方法如果支持Token,一定要传递(比如await Task.Delay(1000, token));
    await会自动传播取消信号:如果Token已取消,await会立即抛出OperationCanceledException;
    异步流(IAsyncEnumerable)中,用[EnumeratorCancellation]属性传递Token(参考上一节)。
    总结
    取消令牌是C#异步编程中优雅中断任务的核心工具,关键要点:
    1.协作式取消:任务必须主动检查取消信号(ThrowIfCancellationRequested);
    2.生产者-消费者模型:CancellationTokenSource生成Token,Token用于检查取消;
    3.资源安全:取消时用finally块清理资源,避免泄漏;
    4.异常处理:捕获OperationCanceledException处理预期的取消;
    5.多任务共享:多个任务可共享同一个Token,实现全局取消。
    掌握取消令牌,你就能写出更健壮、更用户友好的异步代码——让用户随时可以中断任务,让程序在超时后自动清理,避免资源浪费!

本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49448.html


相关教程