【问题标题】:Canceling query with while loop hangs forever使用 while 循环取消查询将永远挂起
【发布时间】:2018-01-26 12:40:15
【问题描述】:

我正在尝试使用查询取消(通过取消令牌)来取消长时间运行的复杂查询。我发现在某些情况下,取消不仅无法停止查询,而且对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 中运行的相同查询会通过“取消执行查询”按钮立即取消。

在无法取消紧凑的 WHILE 循环的情况下查询取消是否有一些警告?

我的 SqlServer 版本:

Microsoft SQL Server 2012 - 11.0.2100.60 (X64) 2012 年 2 月 10 日 19:39:15 版权所有 (c) 微软公司 Windows NT 6.2(内部版本 9200:)上的 Express 版(64 位)

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

编辑

一些附加信息:

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

另一个线程卡在这里:

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

编辑

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

【问题讨论】:

  • 我会更担心我的 SQL 中的 WHILE LOOP,它为什么挂起是一个较小的问题。
  • SqlServer Management Studio 很可能不使用SqlConnectionSqlCommand,因此很有可能它可以通过其他方式立即取消查询
  • 拥有一个不返回任何内容的无限循环有多现实?您可以随时尝试添加命令超时
  • @M.Ali 我在这里展示了一个玩具箱,试图找出技术限制或错误。我实际上认为查询取消挂起是一个很大的问题。想象一下,如果有人试图在 ADO.NET 之上实现类似 SSMS 的工具?这将是一个大问题。
  • @MickyD 命令超时将导致查询退出,但仅在超时到期后。在我的用例中,超时相当长是合适的,因此如果最终不需要操作,我将依靠取消来在该点之前中止查询。同样,在真实情况下,循环并不是真正的无限循环,而是在某些情况下需要足够长的时间才能终止,因此这个问题很明显。

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


【解决方案1】:

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

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

当循环运行时,我可以看到控制台应用程序正在使用一个 CPU 内核的 50%,并以 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 中,以便它在后台继续运行并最终清理干净。

【讨论】:

猜你喜欢
  • 2014-05-30
  • 2021-11-18
  • 2017-06-11
  • 1970-01-01
  • 2019-04-05
  • 1970-01-01
  • 2015-12-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多