取消异步查询时的SQLException

时间:2013-09-16 20:38:28

标签: .net async-await

使用带有CancellationToken的新的.Net 4.5 Async / Await功能,当我取消SQLException来电时,我得到ExecuteNonQueryAsync,而不是OperationCanceledException(或其他一些例外情况)特定于取消操作)。 SQLException确实在邮件末尾说Operation cancelled by user。我期望在取消操作时抛出更具体的异常。另外,我如何创建适当的Try / Catch处理程序来处理这种预期的情况?我通常会将SQLException作为更一般的故障块,但现在我必须梳理出消息的文本,看看这只是用户点击取消按钮!?我一定错过了什么。

这是一个简单的VB WinForm应用程序,它有两个按钮,一个用于执行异步调用,另一个用于取消。第一个按钮中的Try / Catch显示第二个按钮调用Cancel方法时被点击的SQLException

Dim _cts As CancellationTokenSource
Private Async Sub btnLocalTest_Click(sender As Object, e As EventArgs) Handles btnLocalTest.Click
    _cts = New CancellationTokenSource()
    Dim CancelToken As CancellationToken = _cts.Token
    Using sconn As New SqlConnection("server=(local);database=MyDB;user id=MyUser;password=MyPassword")
        sconn.Open()
        Dim SQL As String = some long running SELECT or INSERT statement
        Try
            Using scmd As New SqlCommand(SQL, sconn)
                scmd.CommandTimeout = 300
                Dim i As Integer = Await scmd.ExecuteNonQueryAsync(CancelToken)
            End Using
        Catch exCancel As OperationCanceledException
            LogEvent("Query canceled Exception.") ' This error is *not* thrown on Cancel.  
        Catch ex As SqlClient.SqlException
            LogEvent("Error with query. " & ex.Message)  ' This error *is* thrown on Cancel.  Message includes text 'Canceled by user.'
        End Try
        sconn.Close()
        _cts = Nothing
    End Using
End Sub

Private Sub btnLocalTestCancel_Click(sender As Object, e As EventArgs) Handles btnLocalTestCancel.Click
    If _cts IsNot Nothing Then
        _cts.Cancel()
    End If
End Sub

更新:我使用支持取消的HttpClient.GetAsync方法创建了不同的异步测试。取消该任务后,您可以获得我最初预期的OperationCanceledException例外情况。 所以问题仍然存在:取消异步任务时应该得到什么异常?或者这取决于每种方法及其实施?

2 个答案:

答案 0 :(得分:9)

我通过检查CancelToken.IsCancellationRequested块中的Catch ex As SqlClient.SqlException来“解决”此问题。

答案 1 :(得分:0)

并不总是像追踪SqlException一样简单。如果您在异步任务上使用Task.Wait(),则SqlException将包含在AggregateException内。

可以在MSDN上的ADO.NET部分的Asynchronous Programming文章(“取消异步操作”一节)中找到演示这一点的代码示例。

Task类的一般类似行为的文档隐藏在MSDN文章Task Cancellation(“任务并行库”文档的一部分)中,尽管AggregateException包装TaskCanceledException(源自OperationCanceledException)。

这是一段简化的C#代码,展示了我目前如何使用SqlClient处理取消请求:

class MyDataProcessor
{
    void ReadSomething(CancellationToken cancellationToken)
    {
        try
        {
            // Get the command from somewhere
            DbCommand dbCommand = [...]

            // We don't use await, we manage the Task ourselves
            Task<DbDataReader> task = dbCommand.ExecuteReaderAsync(cancellationToken)
            // If cancellation is requested this throws an AggregateException
            task.Wait();

            // Task status should probably be checked here,
            // but this is just sample code
            DbDataReader dbDataReader = task.Result;

            // If cancellation is requested, this throws a straight SqlException
            while (dbDataReader.Read())
            {
                // do something with the data

                // Be nice and check the token. ADO.NET data providers
                // other than SqlClient might not check the token.
                cancellationToken.ThrowIfCancellationRequested();
            }
        }
        catch (System.Exception exception)
        {
            // If it's a cancellation request, transform the SqlException
            // into an OperationCanceledException
            ThrowIfSqlClientCancellationRequested(
                cancellationToken, exception);

            // Re-throw if it's a genuine error
            throw;
        }
    }

    void ThrowIfSqlClientCancellationRequested(
        CancellationToken cancellationToken,
        Exception exception)
    {
        // Check the CancellationToken, as suggested by Anton S in his answer
        if (!cancellationToken.IsCancellationRequested)
            return;
        System.Data.SqlClient.SqlException sqlException =
            exception as System.Data.SqlClient.SqlException;
        if (null == sqlException)
        {
            AggregateException aggregateException = exception as AggregateException;
            if (null != aggregateException)
                sqlException = aggregateException.InnerException as System.Data.SqlClient.SqlException;
            if (null == sqlException)
                return;
        }
        // Assume that if it's a "real" problem (e.g. the query is malformed),
        // then this will be a number != 0, typically from the "sysmessages"
        // system table 
        if (sqlException.Number != 0)
            return;
        throw new OperationCanceledException();
    }
}

我对此不是很满意,它看起来很脆弱,但是缺少任何官方文档,这是我现在能想到的最好的文件。最重要的问题是:

  • 未来版本的SqlClient会改变他们的取消行为吗?
  • 上面的代码是否还有其他行为?