如何最好地捕获“用户取消操作”异常?

16
我有以下的代码片段:
IAsyncResult beginExecuteReader = command.BeginExecuteNonQuery();

while (!beginExecuteReader.IsCompleted)
{
    if (controllerTask.CancellationTokenSource.IsCancellationRequested)
    {
        command.Cancel();
    }

    Thread.Sleep(100);
}

try
{
    result = command.EndExecuteNonQuery(beginExecuteReader);
}
catch (SqlException exception)
{
    if (exception.ErrorCode == OperationCanceled)
    {
        throw new OperationCanceledException();
    }
    
    throw;
}

怎样才能确定捕获的异常是由操作取消引起的呢?
在这种情况下,ExecuteNonQuery会抛出一个错误代码为0x80131904的异常,但这是一个非常普遍的异常,可能由许多原因引起。错误信息看起来像这样:“当前命令发生了严重错误。如果有任何结果,应该被丢弃。\r\n操作被用户取消。”
除了解析错误信息之外,我没有看到其他选项... 有什么想法吗?
谢谢
附注:是的,我知道在异步操作中使用Cancel命令可能不是最好的主意,因为在.NET 2.0的MSDN文档中有一个警告。但在.NET 4.0的文档中,这个警告被移除了。 我也不喜欢其他在另一个线程中调用取消方法的实现,因为对我来说这会使代码更加复杂。

不,这只是一个类库,它通过 ADO.NET 类(.NET 4.0)与数据库交互。所以我不能使用 BackgroundWorker :) - and85
好的,你会在WinForms中使用你的库吗?还是其他地方? - Likurg
它可以在任何地方使用。目前它被Windows服务、WCF服务、WPF应用程序和控制台应用程序使用。 - and85
活锁可以被替换为 myCancelToken.Register(() => cmd.Cancel()); 然后你可以直接调用阻塞的 ExecuteNonQuery 而不是 BeginExecuteNonQuery。当令牌被取消时,ExecuteNonQuery 将抛出一个 sqlException。 - Stephan Møller
3个回答

9

似乎没有一种不受本地化影响的机制来捕获这个错误。HResult 0x80131904只是一个COR_E_SqlException。错误是在TdsParser.cs:2332处引发的,没有任何独特的属性。它几乎与:2759 - 未知错误:3850 - 意外排序的代码完全相同。

以下是我想到的不好的解决方案:

选项1:违反“不要使逻辑具有本地化敏感性”的好建议

using (var con = new SqlConnection("Server=(local);Integrated Security=True;"))
{
    con.Open();

    try
    {
        var sqc = new SqlCommand("WAITFOR DELAY '1:00:00'", con);
        var readThread = Task.Run(() => sqc.ExecuteNonQuery());

        // cancel after 5 seconds
        Thread.Sleep(5000);
        sqc.Cancel();

        // this should throw
        await readThread;

        // unreachable
        Console.WriteLine("Succeeded");
    }
    catch (SqlException ex) when (ex.Number == 0 && ex.State == 0 && ex.Class == 11 
        && ex.Message.Contains("Operation cancelled by user."))
    {
        Console.WriteLine("Cancelled");
    }
    catch (Exception ex)
    {
        Console.WriteLine("Error");
    }
}
选项2:假设在发出取消请求后,没有其他严重的本地生成错误会影响结果。
using (var con = new SqlConnection("Server=(local);Integrated Security=True;"))
{
    con.Open();

    bool isCancelled = false;
    try
    {
        var sqc = new SqlCommand("WAITFOR DELAY '1:00:00'", con);
        var readThread = Task.Run(() => sqc.ExecuteNonQuery());

        // cancel after 5 seconds
        Thread.Sleep(5000);
        isCancelled = true;
        sqc.Cancel();

        // this should throw
        await readThread;

        // unreachable
        Console.WriteLine("Succeeded");
    }
    catch (SqlException ex) when (isCancelled && ex.Number == 0 && ex.State == 0 && ex.Class == 11)
    {
        Console.WriteLine("Cancelled");
    }
    catch (Exception ex)
    {
        Console.WriteLine("Error");
    }
}

1
我喜欢选项2! - Stephan Møller

-3

所以,在我看来,你应该接下来这样做:

  1. 创建一个线程,在其中使用ado(参考线程示例
  2. 删除Thread.Sleep(100); //我认为从不使用它
  3. 在你的类中添加静态bool muststop=false;
  4. 添加到你的类中公共静态函数来更改“muststop”
  5. 更改你的线程函数以在muststop==true时停止它

希望这可以帮助你。


-4
您可以在 catch 块中检查异常消息,找出是哪个操作被用户取消了:
try
{
   //your code
}
catch (SqlException ex)
{
  if (ex.Message.Contain("Operation cancelled by user"))
   {
       //Do something here
   }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接