Dan*_*ann 14 c# sql-server async-await cancellationtokensource
我在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();
}
}
Run Code Online (Sandbox Code Playgroud)
在调用时cmd.Cancel(),我可以验证基础存储过程基本上立即停止执行.鉴于我在我的应用程序中使用了异步/等待模式,我希望SqlCommand采用CancellationToken参数的异步方法同样可以正常工作.不幸的是,我发现,呼吁Cancel()对CancellationToken造成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();
}
Run Code Online (Sandbox Code Playgroud)
我错过了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;
Run Code Online (Sandbox Code Playgroud)
Cod*_*nis 13
在查看存储过程正在执行的操作之后,它似乎以某种方式阻止了取消.
如果你改变了
RAISERROR (@msg,0,1) WITH NOWAIT;
Run Code Online (Sandbox Code Playgroud)
删除该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();
}
Run Code Online (Sandbox Code Playgroud)
在我的这个测试,我不得不换ExecuteNonQuery一个Task为了cmd.Cancel()工作.如果我使用了ExecuteNonQueryAsync,即使没有传递令牌,系统也会阻止cmd.Cancel().我不确定为什么会这样,但在同步方法中包装同步方法提供了类似的用法.