c# – 使用CancellationToken取消SQL Server查询

我在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().我不知道为什么会这样,但是将同步方法包装在一个任务中提供了类似的用法.

相关文章

在要实现单例模式的类当中添加如下代码:实例化的时候:frmC...
1、如果制作圆角窗体,窗体先继承DOTNETBAR的:public parti...
根据网上资料,自己很粗略的实现了一个winform搜索提示,但是...
近期在做DSOFramer这个控件,打算自己弄一个自定义控件来封装...
今天玩了一把WMI,查询了一下电脑的硬件信息,感觉很多代码都...
最近在研究WinWordControl这个控件,因为上级要求在系统里,...