使用while循环取消查询将永久挂起

时间:2018-01-26 12:40:16

标签: c# .net sql-server async-await cancellation

我正在尝试使用查询取消(通过取消令牌)取消长时间运行的复杂查询。我发现在某些情况下,取消不仅无法停止查询,而且对CancellationToken.Cancel()的调用也无限期挂起。这是一个复制此行为的简单repro(可以在LinqPad中运行):

void Main()
{   
    var cancellationTokenSource = new CancellationTokenSource();
    var blocked = RunSqlAsync(cancellationTokenSource.Token);
    blocked.Wait(TimeSpan.FromSeconds(1)).Dump(); // false (blocked in SQL as expected)
    cancellationTokenSource.Cancel(); // hangs forever?!
    Console.WriteLine("Finished calling Cancel()");
    blocked.Wait();
}

public async Task RunSqlAsync(CancellationToken cancellationToken)
{
    var connectionString = new SqlConnectionStringBuilder { DataSource = @".\sqlexpress", IntegratedSecurity = true, Pooling = false }.ConnectionString;
    using (var connection = new SqlConnection(connectionString))
    {
        await connection.OpenAsync().ConfigureAwait(false);

        using (var command = connection.CreateCommand())
        {
            command.CommandText = @"
                WHILE 1 = 1
                BEGIN
                    DECLARE @x INT = 1
                END
            ";
            command.CommandTimeout = 0;
            Console.WriteLine("Running query");
            await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
        }
    }
}

有趣的是,在SqlServer Management Studio中运行的相同查询会立即通过“取消执行查询”按钮取消。

对于无法取消紧密WHILE循环的查询取消,是否有一些警告?

我的SqlServer版本:

  

Microsoft SQL Server 2012 - 11.0.2100.60(X64)       2012年2月10日19:39:15       版权所有(c)Microsoft Corporation       Windows NT 6.2上的Express Edition(64位)(Build 9200:)

我在Windows 10上运行,而.NET的Environment.Version是4.0.30319.42000。

修改

其他一些信息:

以下是cancellationToken.Cancel()挂起时从Visual Studio中提取的堆栈跟踪:

Cancel() stack trace

另一个线程被困在这里:

ExecuteNonQueryAsync() stack trace

此外,我尝试更新到SqlServer Express 2017,我看到了相同的行为。

修改

我已将此归为corefx的错误:https://github.com/dotnet/corefx/issues/26623

1 个答案:

答案 0 :(得分:1)

我可以在控制台应用程序中重现该问题。 (问题中的代码是来自LINQPad的代码。)

我要回答这个问题并说这是ADO.NET中的一个错误。 ADO.NET应该向SQL Server发送查询取消信号。我可以从CPU使用情况看,SQL Server继续执行循环。因此,它没有收到客户的取消。我们也知道SSMS能够取消这个循环。

当循环运行时,我可以看到控制台应用程序正在使用50%的一个CPU核心,并以70MB /秒的速度从SQL Server接收数据。我不知道这是什么数据。它可能是ROWCOUNT信息或相关的东西。

我认为该错误与循环不断发送数据这一事实有关,因此ADO.NET永远不会有机会发送取消。它仍然是一个错误,如果您报告它将是一个社区服务。你可以链接到这个问题。

如果使用...

限制循环
            WHILE 1 = 1
            BEGIN
                DECLARE @x INT = 1
                WAITFOR DELAY '00:00:01' --new
            END

...然后取消很快。

此外,您通常不能依赖快速取消。如果网络丢失,可能需要30秒才能让客户端注意到这一点并抛出。

因此,您需要对程序进行编码,以便它继续执行而不是等待查询完成。它看起来像这样:

var queryTask = ...;
var cancellationToken = ...;

await Task.WhenAll(queryTask, cancellationToken);

这样取消总是看起来瞬间。确保资源仍然处理完毕。所有SQL交互都应封装在queryTask中,以便它只在后台继续并最终清理。

相关问题