CancellationToken 取消机制
Posted wskxy
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了CancellationToken 取消机制相关的知识,希望对你有一定的参考价值。
1、背景
在我们访问一个十分耗时的请求的时候(这里以web请求举例)
如果请求执行到一半的时候,用户把页面关掉了,那后台还在执行请求就会造成资源的浪费,所以需要引入取消请求机制
2、举例
代码很简单,直接居个小例子
[HttpGet("GetFile")] public async Task<string> GetFile(string file) CancellationTokenSource cts = new CancellationTokenSource();//通过CancellationTokenSource产生Token cts.CancelAfter(3000);//超时时间3s cts.Token.Register(() => Console.WriteLine($"超时取消"));//注册一个取消请求后的执行事件 return await DownFile(file, cts.Token); private async Task<string> DownFile(string file, CancellationToken cancellationToken) for (int i = 1; i <= 10; i++) Console.WriteLine($"执行进度i"); if (cancellationToken.IsCancellationRequested) return "超时"; await Task.Delay(1000); return file + ":下载完成";
执行结果:请求在3s的时候取消掉了
3、那怎么监听页面情况呢?
在action注入CancellationToken(net core),框架会自己监听页面情况,如:
[HttpGet("GetFile2")] public async Task<string> GetFile(string file, CancellationToken cancellationToke) cancellationToke.Register(() => Console.WriteLine($"超时取消"));//注册一个取消请求后的执行事件 return await DownFile(file, cancellationToke);
关闭跳转Web/Postman取消等,都是断开了http请求建立的链接,断开后即触发CancellationToken
C#:使用 CancellationToken 取消 MySqlCommand,给出 NULLReferenceException
【中文标题】C#:使用 CancellationToken 取消 MySqlCommand,给出 NULLReferenceException【英文标题】:C# : Cancelling MySqlCommand using CancellationToken giving NULLReferenceException 【发布时间】:2020-04-11 17:29:40 【问题描述】:我试图使用CancellationToken
取消MySqlCommand
。未请求取消时,查询成功执行。
public async Task<int> ExecuteNonQueryAsync(string connectionString, string query,
CancellationToken cancellationToken)
int affectedRowsCount = 0;
await Task.Run(() =>
using (MySqlConnection connection = new MySqlConnection(connectionString))
using (MySqlCommand command = new MySqlCommand())
connection.Open();
command.Connection = connection;
cancellationToken.Register(() => command.Cancel());
command.CommandText = query;
command.CommandTimeout = 0;
affectedRowsCount = command.ExecuteNonQuery();
connection.Close();
);
return affectedRowsCount;
但是当请求取消时,它会产生 NullReferenceException。 不知道什么是NULL。
我正在调用上述方法
deletedRowsInLastIteration = await
mySqlHelperService.ExecuteNonQueryAsync(
connectionString,
query,
cancellationToken);
如果我尝试
cancellationToken.ThrowIfCancellationRequested();
在调用ExecuteNonQueryAsync()
方法之前,它可以工作。但是 MySqlCommand 的取消不起作用。
这是堆栈跟踪
System.NullReferenceException HResult=0x80004003 消息=对象 引用未设置为对象的实例。来源=MySql.Data 堆栈跟踪:在 MySql.Data.MySqlClient.MySqlConnection.CancelQuery(Int32 超时) 在 MySql.Data.MySqlClient.MySqlCommand.Cancel() 在 ProjectName.Common.MySqlHelperService.c__DisplayClass1_1.b__1() 在 C:\Users\username\source\repos\ProjectName\Applications\ProjectName.Common\MySqlHelperService.cs:line 55 在 System.Threading.CancellationToken.ActionToActionObjShunt(对象 obj) 在 System.Threading.CancellationCallbackInfo.ExecutionContextCallback(对象 obj) 在 System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext、ContextCallback 回调、对象状态、布尔值 preserveSyncCtx) 在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext、ContextCallback 回调、对象状态、布尔值 preserveSyncCtx) 在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext、ContextCallback 回调、对象状态)在 System.Threading.CancellationCallbackInfo.ExecuteCallback() 在 System.Threading.CancellationTokenSource.CancellationCallbackCoreWork(CancellationCallbackCoreWorkArguments 参数)在 System.Threading.CancellationTokenSource.ExecuteCallbackHandlers(布尔 throwOnFirstException)
【问题讨论】:
检查cancellationToken
是否已经初始化。
是的 CancellationToken 已初始化
你是如何以及在哪里初始化它的?
我将它传递给这个方法。我会更新代码
发布完整的异常文本,而不仅仅是消息的屏幕截图。单击Copy Details
并将文本粘贴到问题本身中。该文本包含显示 NRE 在哪里被抛出的堆栈跟踪以及涉及的方法。可能是在取消发生时该命令已被处理。或者可能是 Connector/Net 的 MySqlCommand 实现中还有另一个错误
【参考方案1】:
您不应该使用Task.Run
将同步方法转换为异步方法。充其量,这会浪费一个线程等待某些 IO 操作完成。
MySqlCommand
有一个接受取消令牌的ExecuteNonQueryAsync
方法。 MySqlConnection
本身有一个 OpenAsync
方法。您应该将代码更改为:
public async Task<int> ExecuteNonQueryAsync(string connectionString, string query,
CancellationToken cancellationToken)
using (MySqlConnection connection = new MySqlConnection(connectionString))
using (MySqlCommand command = new MySqlCommand(query,connection))
await connection.OpenAsync();
command.CommandTimeout = 0;
var affectedRowsCount = await command.ExecuteNonQuery(cancellationToken);
return affectedRowsCount;
【讨论】:
是的。我会试试这个并让你知道 +1 如果您使用 MySQL Connector/NET(又名 MySql.Data),这不会取消查询,因为它不支持异步方法:bugs.mysql.com/bug.php?id=70111 改用 nuget.org/packages/MySqlConnector,这具有完整的异步 I/O 支持。 @BradleyGrainger 查看代码 - 如果可能的话,它比我记忆中的还要糟糕。 BeginXXX 最终启动了一个新线程但是 Async 方法 fake 呢?他们甚至不尝试使用 BeginXXX?并且 CancelQuery 不会取消查询。它尝试在另一个连接上发出KILL QUERY
@jophab 看起来它毕竟不起作用。 MySQL 伪造异步执行,并且该方法同步运行。甚至取消都是伪造的。 Cancel
在 new 连接上执行 KILL QUERY
。我一直忘记 Connector/NET 有多糟糕。如果可能的话,你不应该使用它。如果您想要真正的异步执行,请使用 MySqlConnector 包。顺便说一句,其中没有 sync 方法,并通过阻塞异步方法来伪造它们
是的,我会试试的【参考方案2】:
你是如何创建你的取消令牌的,他的价值是什么?
这里还有一个解决方案,如何使用取消令牌取消 sql 命令
private CancellationTokenSource cts;
private async void TestSqlServerCancelSprocExecution()
cts = new CancellationTokenSource();
try
await Task.Run(() =>
using (SqlConnection conn = new SqlConnection("connStr"))
conn.InfoMessage += conn_InfoMessage;
conn.FireInfoMessageEventOnUserErrors = true;
conn.Open();
var cmd = conn.CreateCommand();
cts.Token.Register(() => cmd.Cancel());
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "dbo.[CancelSprocTest]";
cmd.ExecuteNonQuery();
);
catch (SqlException)
// sproc was cancelled
The code above is from this question, which had kinda the same problem, that the cancellation token won't cancel the sql command.
【讨论】:
取消在 SqlCommand 上运行良好。我的问题是 MySqlCommand以上是关于CancellationToken 取消机制的主要内容,如果未能解决你的问题,请参考以下文章
C#:使用 CancellationToken 取消 MySqlCommand,给出 NULLReferenceException
取消 HttpClient 请求 - 为啥 TaskCanceledException.CancellationToken.IsCancellationRequested 为假?
C#源码生成器的cancellationtoken啥时候取消?
使用 CancellationToken 取消任务而不在任务中明确检查?
NamedPipeServerStream.ReadAsync() 在 CancellationToken 请求取消时不退出