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 伪造异步执行,并且该方法同步运行。甚至取消都是伪造的。 Cancelnew 连接上执行 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 取消机制的主要内容,如果未能解决你的问题,请参考以下文章

如何取消 CancellationToken

C#:使用 CancellationToken 取消 MySqlCommand,给出 NULLReferenceException

取消 HttpClient 请求 - 为啥 TaskCanceledException.CancellationToken.IsCancellationRequested 为假?

C#源码生成器的cancellationtoken啥时候取消?

使用 CancellationToken 取消任务而不在任务中明确检查?

NamedPipeServerStream.ReadAsync() 在 CancellationToken 请求取消时不退出