【发布时间】:2014-07-14 14:05:53
【问题描述】:
我在 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() 后,我可以验证底层存储过程是否立即停止执行。鉴于我在我的应用程序中大量使用了 async/await 模式,我希望 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,0,1) WITH NOWAIT;
INSERT INTO foo VALUES (@loop);
WAITFOR DELAY '00:00:01.01';
SET @loop = @loop+1;
END;
【问题讨论】:
-
您在取消任务时没有收到 TaskCancellation 异常吗?
-
可以先取消任务后尝试运行TestSqlServerCancelSprocExecution()吗?
-
@Dan - 这可能是因为当前的上下文 - 我在这里猜测。请尝试 cmd.ExecuteNonQueryAsync(cts.Token).ConfigureAwait(false);
-
@JohnSaunders 我没有在任何地方读到过。我假设(显然是错误的)因为 TPL 中的 CancellationTokens 是明确合作的,所以当 SqlCommand 通过 CancellationToken 收到取消请求时,它会在异步方法中使用相同的取消机制,就像它通过同步方法中的取消方法。
-
对于其他遇到此问题的人,在异步取消中存在一个已知错误,请参阅github.com/dotnet/SqlClient/issues/44。
标签: c# sql-server async-await cancellationtokensource