CancellationToken
通常在异步场景下,我们需要提前终止任务。如:请求超时提前终止任务,防止一直占用资源、用户主动取消操作等。这里就可以使用 CancellationToken 参数,用于获得提前终止执行的信号。很多异步方法都有CancellationToken参数,用于获得提前终止执行的信号。
转到定义可以看到 CancellationToken 是一个结构体,可用于终止线程的成员有:
None:空
bool IsCancellationRequested: 是否取消
(*)Register(Action callback): 注册取消监听
ThrowIfCancellationRequested(): 如果任务被取消,执行到这句话就抛异常。
下面来对比下各种情况下的不同执行效果:
设计实现“下载一个网址N次”的方法。
分别用GetStringAsync + IsCancellationRequested、 GetStringAsync + ThrowIfCancellationRequested()、带CancellationToken的GetAsync()分别实现。取消分别用超时、用户敲按键(不能await)实现。
一.无CancellationToken
案例:下载对应网站内容100次
使用案例:
#region 不用CancellationToken
await DownloadAsync("http://www.baidu.com", 100);
/// <summary>
/// 下载 n 次指定 url 的内容
/// </summary>
/// <param name="url"></param>
/// <param name="n"></param>
/// <returns></returns>
static async Task DownloadAsync(string url, int n)
{
using(var client = new HttpClient())
{
for (int i = 0; i < n; i++)
{
string html = await client.GetStringAsync(url);
Console.WriteLine($"{DateTime.Now}:{html}");
}
}
}
#endregion
此案例会一直下载对应网站的内容100次,不会因为长时间下载而停止。
运行结果:
二.IsCancellationRequested
IsCancellationRequested 属性。该属性用来判断 CancellationToken 是否发出取消任务的请求。
1.案例:下载对应网站内容100次,超过5秒提前结束下载
使用案例:
#region 用CancellationToken——IsCancellationRequested
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(5000);
CancellationToken cToken = cts.Token;
await DownloadAsyncIsCancellationRequested("http://www.baidu.com", 100, cToken);
/// <summary>
/// 下载 n 次指定 url 的内容
/// </summary>
/// <param name="url"></param>
/// <param name="n"></param>
/// <param name="cancellationToken">取消操作的通知</param>
/// <returns></returns>
static async Task DownloadAsyncIsCancellationRequested(string url, int n,CancellationToken cancellationToken)
{
using (var client = new HttpClient())
{
for (int i = 0; i < n; i++)
{
string html = await client.GetStringAsync(url);
Console.WriteLine($"{DateTime.Now}:{html}");
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("请求被取消");
break;
}
}
}
}
#endregion
注意:要通过 CancellationTokenSource 类的实例创建 CancellationToken调用。
CancellationTokenSource类包含方法:
- CancelAfter():超时后发出取消信号
- Cancel() :发出取消信号
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(5000);
CancellationToken cToken = cts.Token;
await DownloadAsyncIsCancellationRequested("http://www.baidu.com", 100, cToken);
此案例下载对应网站的内容超过5秒,就会提前结束下载。
运行结果:
2.案例:下载对应网站内容100次,按q键提前取消下载。
使用案例:
#region 用CancellationToken——IsCancellationRequested,按q键取消请求。
CancellationTokenSource ctsQ = new CancellationTokenSource();
CancellationToken cTokenQ = ctsQ.Token;
//这里不能用await,否则请求结束前,无法执行下面的代码。
DownloadAsyncIsCancellationRequestedQ("http://www.baidu.com", 100, cTokenQ);
while (Console.ReadLine() != "q")
{
}
ctsQ.Cancel();
Console.ReadLine();
/// <summary>
/// 下载 n 次指定 url 的内容
/// </summary>
/// <param name="url"></param>
/// <param name="n"></param>
/// <param name="cancellationToken">取消操作的通知</param>
/// <returns></returns>
static async Task DownloadAsyncIsCancellationRequestedQ(string url, int n, CancellationToken cancellationToken)
{
using (var client = new HttpClient())
{
for (int i = 0; i < n; i++)
{
string html = await client.GetStringAsync(url);
Console.WriteLine($"{DateTime.Now}:{html}");
if (cancellationToken.IsCancellationRequested)
{
Console.WriteLine("请求被取消");
break;
}
}
}
}
#endregion
此案例会一直下载对应网站的内容100次,如果按下“q”键,则会提前取消下载。
运行结果:
三.ThrowIfCancellationRequested
ThrowIfCancellationRequested(): 如果任务被取消,执行到这句话就抛异常。
案例:下载对应网站内容100次,超过5秒提前结束下载
使用案例:
#region 用CancellationToken——ThrowIfCancellationRequested
CancellationTokenSource cts1 = new CancellationTokenSource();
cts1.CancelAfter(5000);
CancellationToken cToken1 = cts1.Token;
await DownloadAsyncThrowIfCancellationRequested("http://www.baidu.com", 100, cToken1);
/// <summary>
/// 下载 n 次指定 url 的内容
/// </summary>
/// <param name="url"></param>
/// <param name="n"></param>
/// <param name="cancellationToken">取消操作的通知</param>
/// <returns></returns>
static async Task DownloadAsyncThrowIfCancellationRequested(string url, int n, CancellationToken cancellationToken)
{
using (var client = new HttpClient())
{
for (int i = 0; i < n; i++)
{
string html = await client.GetStringAsync(url);
Console.WriteLine($"{DateTime.Now}:{html}");
cancellationToken.ThrowIfCancellationRequested();
}
}
}
#endregion
此案例下载对应网站的内容超过5秒,就会抛出异常:System.OperationCanceledException:“The operation was canceled.”,提前结束下载。
运行结果:
我们反编译看看 ThrowIfCancellationRequested 内部的原理,发现方法内部同样是判断了IsCancellationRequested 属性。
建议使用 IsCancellationRequested,因为思路更加清晰,并且可以进行其他额外的精确控制。而通过方法抛出异常,需要先 catch 异常在进行处理,没有直接判断属性来的直接高效。
四.带CancellationToken的方法
直接调用带CancellationToken参数的方法。
1.案例:调用带CancellationToken的GetAsync(),下载对应网站内容100次,超过5秒提前结束下载
使用案例:
#region 用CancellationToken——带CancellationToken的GetAsync()
CancellationTokenSource cts2 = new CancellationTokenSource();
cts2.CancelAfter(5000);
CancellationToken cToken2 = cts2.Token;
await DownloadAsyncGetAsync("http://www.baidu.com", 100, cToken2);
/// <summary>
/// 下载 n 次指定 url 的内容
/// </summary>
/// <param name="url"></param>
/// <param name="n"></param>
/// <param name="cancellationToken">取消操作的通知</param>
/// <returns></returns>
static async Task DownloadAsyncGetAsync(string url, int n, CancellationToken cancellationToken)
{
using (var client = new HttpClient())
{
for (int i = 0; i < n; i++)
{
var resp = await client.GetAsync(url,cancellationToken);
string html = await resp.Content.ReadAsStringAsync();
Console.WriteLine($"{DateTime.Now}:{html}");
}
}
}
#endregion
此案例下载对应网站的内容超过5秒,就会抛出异常,提前结束下载。
运行结果:
2.案例:调用带CancellationToken的GetStringAsync,下载对应网站内容100次,超过5秒提前结束下载
使用案例:
#region 用CancellationToken——带CancellationToken的GetStringAsync
CancellationTokenSource cts3 = new CancellationTokenSource();
cts3.CancelAfter(5000);
CancellationToken cToken3 = cts3.Token;
await DownloadAsyncGetStringAsync("http://www.baidu.com", 100, cToken3);
/// <summary>
/// 下载 n 次指定 url 的内容
/// </summary>
/// <param name="url"></param>
/// <param name="n"></param>
/// <param name="cancellationToken">取消操作的通知</param>
/// <returns></returns>
static async Task DownloadAsyncGetStringAsync(string url, int n, CancellationToken cancellationToken)
{
using (var client = new HttpClient())
{
for (int i = 0; i < n; i++)
{
string html = await client.GetStringAsync(url,cancellationToken);
Console.WriteLine($"{DateTime.Now}:{html}");
}
}
}
#endregion
此案例下载对应网站的内容超过5秒,就会抛出异常,提前结束下载。
运行结果:
五.ASP.NET Core中CancellationToken使用
ASP.NET Core开发中,一般不需要自己处理CancellationToken、CancellationTokenSource这些,只要做到“能转发CancellationToken就转发”即可。ASP.NET Core会对于用户请求中断进行处理。
演示一下ASP.NET Core中的使用:写一个方法,下载对应网站内容1000次,用Debug.WriteLine()输出,访问中间跳到放到其他网站。
两个案例对比:
1.不传CancellationToken参数
#region 不传CancellationToken参数
public async Task<IActionResult> IndexAsync()
{
await DownloadAsyncGetStringAsync("http://www.baidu.com", 1000);
return View();
}
static async Task DownloadAsyncGetStringAsync(string url, int n)
{
using (var client = new HttpClient())
{
for (int i = 0; i < n; i++)
{
string html = await client.GetStringAsync(url);
Debug.WriteLine($"{DateTime.Now}:{html}");
}
}
}
#endregion
访问中间跳到放到其他网站,后台代码依然下载,直到下载1000次,结束运行。
2.传CancellationToken参数
#region 传CancellationToken参数
public async Task<IActionResult> IndexAsync(CancellationToken cancellationToken)
{
await DownloadAsyncGetStringAsync("http://www.baidu.com", 10000, cancellationToken);
return View();
}
static async Task DownloadAsyncGetStringAsync(string url, int n, CancellationToken cancellationToken)
{
using (var client = new HttpClient())
{
for (int i = 0; i < n; i++)
{
string html = await client.GetStringAsync(url, cancellationToken);
Debug.WriteLine($"{DateTime.Now}:{html}");
}
}
}
#endregion
访问中间跳到放到其他网站,后台代码,结束运行。不会傻傻地还在一直运行。