我在sql Server中有一个长时间运行的存储过程,我的用户需要能够取消.我写了一个小测试应用程序如下,表明sqlCommand.Cancel()方法工作相当不错:
private sqlCommand cmd; private void TestsqlServerCancelSprocExecution() { TaskFactory f = new TaskFactory(); f.StartNew(() => { using (sqlConnection conn = new sqlConnection("connStr")) { conn.InfoMessage += conn_InfoMessage; conn.FireInfoMessageEventOnUserErrors = true; conn.Open(); cmd = conn.CreateCommand(); cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "dbo.[CancelSprocTest]"; cmd.ExecuteNonQuery(); } }); } private void cancelButton_Click(object sender,EventArgs e) { if (cmd != null) { cmd.Cancel(); } }
调用cmd.Cancel()后,我可以验证底层存储过程是否基本上立即停止执行.鉴于我在应用程序中使用了异步/等待模式,我希望sqlCommand上使用CancellationToken参数的异步方法工作同样顺利.不幸的是,我发现在CancellationToken上调用Cancel()使InfoMessage事件处理程序不再被调用,但是底层存储过程继续执行.我的异步版本的测试代码如下:
private sqlCommand cmd; private CancellationTokenSource cts; private async void TestsqlServerCancelSprocExecution() { cts = new CancellationTokenSource(); using (sqlConnection conn = new sqlConnection("connStr")) { conn.InfoMessage += conn_InfoMessage; conn.FireInfoMessageEventOnUserErrors = true; conn.Open(); cmd = conn.CreateCommand(); cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "dbo.[CancelSprocTest]"; await cmd.ExecuteNonQueryAsync(cts.Token); } } private void cancelButton_Click(object sender,EventArgs e) { cts.Cancel(); }
我错过了CancellationToken应该如何工作吗?我在.NET 4.5.1和sql Server 2012,如果重要.
编辑:我重新编写测试应用程序作为控制台应用程序,以防同步上下文是一个因素,我看到相同的行为 – 调用CancellationTokenSource.Cancel()不会停止执行底层的存储过程.
编辑:这是我所要求的存储过程的正文.它以一秒的间隔插入记录并打印结果,以便轻松查看取消尝试是否立即生效.
WHILE (@loop <= 40) BEGIN DECLARE @msg AS VARCHAR(80) = 'Iteration ' + CONVERT(VARCHAR(15),@loop); RAISERROR (@msg,1) WITH NOWAIT; INSERT INTO foo VALUES (@loop); WAITFOR DELAY '00:00:01.01'; SET @loop = @loop+1; END;
解决方法
在查看您的存储过程正在做什么后,似乎阻止了取消.
如果你改变
RAISERROR (@msg,1) WITH NOWAIT;
要删除WITH NOWAIT子句,则取消按预期工作.但是,这样可以防止InfoMessage事件实时触发.
您可以以其他方式跟踪长时间运行的存储过程的进度,或注册令牌取消,并调用cmd.Cancel(),因为您知道这是有效的.
另外需要注意的是,使用.NET 4.5,您可以使用Task.Run而不是实例化一个TaskFactory.
所以这里是一个工作的解决方案:
private CancellationTokenSource cts; private async void TestsqlServerCancelSprocExecution() { cts = new CancellationTokenSource(); try { await Task.Run(() => { using (sqlConnection conn = new sqlConnection("connStr")) { conn.InfoMessage += conn_InfoMessage; conn.FireInfoMessageEventOnUserErrors = true; conn.Open(); var cmd = conn.CreateCommand(); cts.Token.Register(() => cmd.Cancel()); cmd.CommandType = CommandType.StoredProcedure; cmd.CommandText = "dbo.[CancelSprocTest]"; cmd.ExecuteNonQuery(); } }); } catch (sqlException) { // sproc was cancelled } } private void cancelButton_Click(object sender,EventArgs e) { cts.Cancel(); }
在我的测试中,我不得不将ExecuteNonQuery包装在一个任务中,以使cmd.Cancel()工作.如果我使用ExecuteNonQueryAsync,即使没有传递一个令牌,那么系统会阻塞cmd.Cancel().我不知道为什么会这样,但是将同步方法包装在一个任务中提供了类似的用法.