使用带有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
例外情况。 所以问题仍然存在:取消异步任务时应该得到什么异常?或者这取决于每种方法及其实施?
答案 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();
}
}
我对此不是很满意,它看起来很脆弱,但是缺少任何官方文档,这是我现在能想到的最好的文件。最重要的问题是: