EF6 ToListAsync()取消长查询不起作用

时间:2014-08-19 15:42:23

标签: c# vb.net multithreading entity-framework

我正在开发一个Entity Framework WinForms应用程序,我正在尝试在与UI分离的线程上运行查询,并允许用户取消长查询。 我已经already asked one question关于如何正确实现这一点并且可能仍然不正确,但我当前的问题是如何允许用户取消长时间运行的EF6查询?

我找到了this的链接,但似乎仍然无法正常工作......再次,可能是我对原始部分编程错误(从我的第一个问题开始) ),但我的问题是如何让用户点击一个取消按钮来停止对数据库的长查询?

我的(相关)当前代码如下......

Private cts As New CancellationTokenSource

Private Sub Cancel_Click(sender As Object, e As EventArgs) Handles Cancel.Click
    cts.Cancel()
End Sub

尝试1(在任务中添加取消令牌):

Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

    Dim y As New List(Of DBData)

    Try
        Var1 = "Var1"

        Dim qry As Task(Of List(Of DBData)) = Task.Run(Function() GetDBData(Var1), cts.Token))

        Try
            y = Await qry
            cts.Token.ThrowIfCancellationRequested()
        Catch ex As OperationCanceledException
            ' ** ONLY REACH HERE AFTER QUERY PROCESSES!! **
        End Try
End Sub

尝试2(在EF ToListAsync()方法中添加取消令牌):

Private Async Function GetDBData(Var1 As String, ct As CancellationToken) As Task(Of List(Of DBData))

    Dim retval As List(Of DBData)

    Using x As New DBContext
        Try
            retval = Await (From rw In x.DBData
                             Where rw.Val1= Val1
                             Select rw).ToListAsync(ct)
            ct.ThrowIfCancellationRequested()

            Return retval

        Catch ex As Exception
            ' ** ONLY REACH HERE AFTER QUERY PROCESSES!! **
            MsgBox(ex.Message)
            Return Nothing
        End Try
    End Using

End Function

我希望我的解释/代码发布在2之间的差异是有意义的...任何帮助将不胜感激! - 即使这是在VB中,我对VB / C#解决方案也很满意。

谢谢!

2 个答案:

答案 0 :(得分:2)

这是一个从未被EF团队遗忘的错误。

事实上,这是整个EF 6 Async实现的整体弱点。

开发团队已尽最大努力通过使用内部类InternalContext等来抽象出对底层SqlCommand的任何访问。

他们未能做的是将CancellationToken的Cancel()动作连接到SqlCommand.Cancel。这是一个明显的错误,非常令人沮丧。

如果您的查询返回行,那么它们的代码可以正常工作,因为任务将在迭代后返回,但这是一项非常糟糕的工作。

internal static Task<List<T>> ToListAsync<T>(this IDbAsyncEnumerable<T> source, CancellationToken cancellationToken)
    {
      TaskCompletionSource<List<T>> tcs = new TaskCompletionSource<List<T>>();
      List<T> list = new List<T>();
      IDbAsyncEnumerableExtensions.ForEachAsync<T>(source, new Action<T>(list.Add), cancellationToken).ContinueWith((Action<Task>) (t =>
      {
        if (t.IsFaulted)
          tcs.TrySetException((IEnumerable<Exception>) t.Exception.InnerExceptions);
        else if (t.IsCanceled)
          tcs.TrySetCanceled();
        else
          tcs.TrySetResult(list);
      }), TaskContinuationOptions.ExecuteSynchronously);
      return tcs.Task;
    }

这可以通过向IDbAsyncEnumerable添加逻辑来解决,以处理取消或创建新接口IDbCancellableAsyncEnumerable,即暴露Cancel方法的类,该方法在内部访问正在执行的SqlCommand并调用Cancel。

由于他们没有对此感到困扰,我怀疑这不会很快发生,因为它可能会做很多工作。

注意:即使您尝试使用ExecuteSqlCommandAsync(),您仍然无法终止SqlCommand。

答案 1 :(得分:1)

如果要向现有任务添加CancellationToken,您只需向该任务添加一个续集,其中除了传播任务状态以外,其他任何内容都不会执行任何操作并添加新{ {1}}。

CancellationToken

当然值得注意的是,使用这样的方法实际上并没有取消相关任务执行的底层操作,它只是允许程序的执行在上继续,尽管事实如此该计划尚未完成。应该设计程序,使得如果底层操作仍然在取消任务的路径中工作,它仍将起作用。