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

Posted

技术标签:

【中文标题】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

以上是关于C#:使用 CancellationToken 取消 MySqlCommand,给出 NULLReferenceException的主要内容,如果未能解决你的问题,请参考以下文章

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

我应该何时调用 CancellationToken.ThrowIfCancellationRequested?

我应该如何使用 DataflowBlockOptions.CancellationToken?

如何使用 CancellationToken 属性?

CancellationToken 不能使用零超时

如何取消 CancellationToken