EF6 ToListAsync () Отмена длинного запроса не работает

1

Я работаю над приложением Entity Framework WinForms и пытаюсь запустить запросы в потоке отдельно от пользовательского интерфейса и разрешить пользователю отменять длинные запросы. Я уже задал один вопрос о том, как правильно это реализовать и, возможно, все еще не так прав, но мой текущий вопрос заключается в том, как разрешить пользователю отменить долговременный запрос EF6?

Я нашел ссылки по строкам этого, но все еще не может показаться, что это хорошо работает... Опять же, возможно, я неправильно запрограммировал исходную часть (как из моего первого вопроса), но мой вопрос: как мне разрешить пользователю нажать кнопку отмены, которая остановит длинный запрос в БД?

Мой (актуальный) текущий код выглядит следующим образом...

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 (добавить маркер отмены в ToListAsync() 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/С#.

Благодарю!!

Теги:
multithreading
entity-framework

2 ответа

2

Это ошибка, которую команда EF никогда не исправляла.

На самом деле это общая слабость во всей реализации EF 6 Async.

Команда разработчиков сделала все возможное, чтобы отвлечь любой доступ к базовому SqlCommand, используя внутренний класс InternalContext и т.д.

То, что они не смогли сделать, - это связать действие Cancel() вашего CancellationToken с 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, то есть класса, который предоставляет метод отмены, который внутренне обращается к исполняющему SqlCommand и вызывает Отмена.

Поскольку они не беспокоили этого, я подозреваю, что это не произойдет в ближайшее время, так как это, вероятно, будет очень большой работой.

Примечание. Даже если вы пытаетесь использовать ExecuteSqlCommandAsync(), у вас все еще нет возможности убить SqlCommand.

1

Слишком добавить CancellationToken в существующую задачу, вы можете просто добавить продолжение к этой задаче, в которой продолжение не делает ничего, кроме распространения статуса задачи и добавления нового CancellationToken.

public static Task<T> WithToken<T>(
    this Task<T> task,
    CancellationToken token)
{
    return task.ContinueWith(async t => await t, token)
        .Unwrap();
}
public static Task WithToken(
    this Task task,
    CancellationToken token)
{
    return task.ContinueWith(async t => await t, token)
        .Unwrap();
}

Разумеется, стоит отметить, что использование такого метода никоим образом не отменяет основную операцию, выполняемую данной задачей, она просто позволяет продолжить выполнение программы, несмотря на то, что программа еще не завершена. Программа должна быть сконструирована таким образом, чтобы она все еще функционировала, если эта базовая операция все еще выполняет работу на пути, где задача отменяется.

  • 0
    Хорошо, понял о программе, которая все еще работает ... Что мне удалось сделать ... Мой вопрос заключается в том, как не дать EF продолжать делать свое дело / использовать ресурсы, если пользователь нажимает кнопку отмены ... По сути, убить эту ветку, может быть ??
  • 1
    @JohnBustos Вы не можете, и нет потока, который является основным смыслом использования EF для выполнения асинхронных операций в первую очередь. После того как вы отправили сетевой запрос в базу данных, вы не можете «получить его обратно». Это ушло, и это не остановить.
Показать ещё 5 комментариев

Ещё вопросы

Сообщество Overcoder
Наверх
Меню