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

Canceling query with while loop hangs forever

我正在尝试使用查询取消(通过取消标记)来取消长运行ning 的复杂查询。我发现在某些情况下,取消不仅无法停止查询,而且对 CancellationToken.Cancel() 的调用也会无限期挂起。这是一个复制此行为的简单重现(在 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 中的相同查询 运行 会通过 "Cancel Executing Query" 按钮立即取消。

在无法取消紧凑的 WHILE 循环的情况下查询取消是否有一些注意事项?

我的 SqlServer 版本:

Microsoft SQL Server 2012 - 11.0.2100.60 (X64) Feb 10 2012 19:39:15 Copyright (c) Microsoft Corporation Express Edition (64-bit) on Windows NT 6.2 (Build 9200: )

我 运行宁 Windows 10,.NET 的 Environment.Version 是 4.0.30319.42000.

编辑

一些附加信息:

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

另一个线程卡在这里:

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

编辑

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

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

我要把这个作为一个答案,并说这是 ADO.NET 中的一个错误。 ADO.NET 应向 SQL 服务器发送查询取消信号。我可以从 CPU 用法中看出,SQL 服务器继续执行循环。因此,它没有收到客户的取消通知。我们也知道 SSMS 可以取消这个循环。

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

我认为该错误与循环不断发送数据以致 ADO.NET 永远没有机会发送取消有关。它仍然是一个错误,如果您报告它,它将成为一项社区服务。您可以link回答这个问题。

如果循环被限制使用...

            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 中,以便它在后台继续并最终清理。