C# 使用while循环取消查询将永远挂起
我试图使用查询取消(通过取消令牌)来取消长时间运行的复杂查询。我发现,在某些情况下,取消操作不仅无法停止查询,而且对C# 使用while循环取消查询将永远挂起,c#,.net,sql-server,async-await,cancellation,C#,.net,Sql Server,Async Await,Cancellation,我试图使用查询取消(通过取消令牌)来取消长时间运行的复杂查询。我发现,在某些情况下,取消操作不仅无法停止查询,而且对CancellationToken.Cancel()的调用也会无限期挂起。下面是一个复制此行为的简单复制程序(可以在LinqPad中运行): 有趣的是,在SQLServerManagementStudio中运行的同一查询会通过“CancelExecutingQuery”按钮立即取消 如果查询取消不能取消,是否有一些警告 我的SqlServer版本: Microsoft SQL Se
CancellationToken.Cancel()
的调用也会无限期挂起。下面是一个复制此行为的简单复制程序(可以在LinqPad中运行):
有趣的是,在SQLServerManagementStudio中运行的同一查询会通过“CancelExecutingQuery”按钮立即取消
如果查询取消不能取消,是否有一些警告
我的SqlServer版本:
Microsoft SQL Server 2012-11.0.2100.60(X64)
2012年2月10日19:39:15
版权所有(c)微软公司
Windows NT 6.2(内部版本9200:)上的快速版(64位)
我在Windows 10上运行,.NET的环境。版本为4.0.30319.42000
编辑
一些补充资料:
以下是当cancellationToken.Cancel()挂起时从Visual Studio中提取的堆栈跟踪:
另一条线卡在这里:
此外,我尝试更新到SQLServerExpress 2017,我看到了相同的行为
编辑
我将此作为一个bug提交给corefx:我可以在控制台应用程序中重现此问题。(问题中的代码是来自LINQPad的代码。)
我将回答这个问题,并说这是ADO.NET中的一个bug。ADO.NET应向SQL Server发送查询取消信号。我可以从CPU使用情况看出,SQL Server继续执行循环。因此,它没有收到客户的取消通知。我们还知道SSMS能够取消此循环
当循环运行时,我可以看到控制台应用程序使用了一个CPU核心的50%,并以70MB/秒的速度从SQL Server接收数据。我不知道这是什么数据。可能是行数信息或其他相关信息
我认为这个错误与循环不断发送数据有关,因此ADO.NET从来没有机会发送取消。它仍然是一个bug,如果你报告它,它将是一个社区服务。你可以链接到这个问题
如果循环使用
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
中,这样它就可以在后台继续并最终清理干净。我更担心SQL中的WHILE循环
,它挂起的原因是一个较小的问题。最有可能的是SqlServer Management Studio不使用SqlConnection
和SqlCommand
,因此它很有可能通过其他方式立即取消查询。是否有一个无论如何都不会返回任何内容的无限循环?您可以尝试添加命令timeout@M.Ali我在这里展示一个玩具盒,试图找出一个技术限制或缺陷。实际上,我认为查询取消挂起是一个相当大的问题。想象一下,如果有人试图在ADO.NET上实现一个类似SSMS的工具?这将是一个大问题。@MickyD命令超时将导致查询退出,但只有在超时过期之后。在我的用例中,超时很长是合适的,因此如果操作最终不需要,我依靠取消在该点之前中止查询。同样,在实际情况中,循环并不是真正无限的,但在某些情况下需要足够长的时间才能终止,这是正确的。我已经在这里向MSFT提交了错误:
var queryTask = ...;
var cancellationToken = ...;
await Task.WhenAll(queryTask, cancellationToken);