微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

原文地址:https://www.jb51.cc/csharp/94339.html

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐