14

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

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

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我最初预期的异常。 所以问题仍然存在:取消 Async 任务时应该得到什么异常?还是取决于每种方法及其实现?

4

2 回答 2

11

CancelToken.IsCancellationRequested我通过签入Catch ex As SqlClient.SqlException块“解决了”这个问题。

于 2014-02-13T21:28:40.257 回答
0

它并不总是像捕获SqlException. 如果您Task.Wait()在异步任务上使用,那么SqlException将被包裹在一个AggregateException.

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

TaskMSDN 文章Task Cancellation(“Task Parallel Library”文档的一部分)中包含该类通用的类似行为的文档,尽管此处AggregateException包含 a 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 的未来版本会改变它们的取消行为吗?
  • 上面的代码是否包含其他行为?
于 2015-03-09T15:48:04.080 回答