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

Joh*_*tos 4 c# vb.net multithreading entity-framework

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

我找到了与此类似的链接,但似乎仍然无法正常工作...同样,可能是我对原始部分的编程错误(从我的第一个问题开始),但我的问题是我该如何做允许用户单击取消按钮来停止数据库上的长查询?

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

Private cts As New CancellationTokenSource

Private Sub Cancel_Click(sender As Object, e As EventArgs) Handles Cancel.Click
    cts.Cancel()
End Sub
Run Code Online (Sandbox Code Playgroud)

尝试 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
Run Code Online (Sandbox Code Playgroud)

尝试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
Run Code Online (Sandbox Code Playgroud)

我希望我发布的解释/代码能够理解两者之间的差异...任何帮助将不胜感激!- 尽管这是用 VB 编写的,但我同样对 VB / C# 解决方案感到满意。

谢谢!!

Tom*_*ord 7

遗憾的是,这是一个 EF 团队从未修复过的错误。

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

开发团队已尽最大努力通过使用内部类 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;
    }
Run Code Online (Sandbox Code Playgroud)

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

由于他们没有为此烦恼,我怀疑这不会很快发生,因为这可能需要大量工作。

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