使用 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
中,以便它在后台继续并最终清理。
我正在尝试使用查询取消(通过取消标记)来取消长运行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
中,以便它在后台继续并最终清理。