Запустить задачу в форме Показано событие - PullRequest
2 голосов
/ 06 марта 2020

Я хочу связать ComboBox с сущностью EF Core из 53 тыс. Строк. Это займет некоторое время, около 10 секунд.
Я думал, что если я добавлю процесс привязки в событие Form Shown, пользовательский интерфейс останется отзывчивым. Но это был не тот случай.

Что я пробовал:

Private Sub frmCerere_Shown(sender As Object, e As EventArgs) Handles Me.Shown
    Task.Factory.StartNew(Sub() GetProducts(cmbProduse), TaskCreationOptions.LongRunning)      
End Sub
Public Shared Sub GetProducts(ctrl As ComboBox)
    Using context As EnsightContext = New EnsightContext
        context.Produse.Load()
        Dim idsap = context.Produse.Local.Select(Function(o) o.IdSap).ToList
        ctrl.DataSource = idsap
    End Using
End Sub

Безрезультатно, так как ничего не происходит. Форма показана, но ComboBox пуст.
Как я могу вернуть ComboBox обратно в основной поток?

1 Ответ

3 голосов
/ 07 марта 2020

Два метода для загрузки содержимого в ComboBox (или другие элементы управления) без замораживания формы контейнера.

Примечание: список ComboBox не поддерживает бесконечное число элементов. DropDown фактически перестанет работать после добавления в список 65534 элементов.
DropDownList и ListBox могут поддерживать больше элементов, но в некоторый момент они также начинают разрушаться (~80,000 элементов ), прокрутка и рендеринг предметов будут заметно скомпрометированы.

► Первый метод - в огне и забыть стиль. Задача запускает метод, который загружает данные из некоторого источника. Когда загрузка заканчивается, данные устанавливаются как ComboBox.DataSource.

A CancellationTokenSource используется для передачи CancellationToken в метод, чтобы сигнализировать о необходимости отмены при необходимости. Метод возвращается, если обнаруживает, что был вызван CancellationTokenSource.Cancel(), проверяя (когда это возможно) свойство CancellationToken.IsCancellationRequested.

CancellationTokenSource.Cancel() также вызывается при закрытии формы, если загрузка данных еще продолжается.
Установите CancellationTokenSource на null (Nothing) при утилизации: его IsDiposed свойство является внутренним и не может быть доступно напрямую.

BeginInvoke() используется для выполнения этой операции в потоке пользовательского интерфейса. Без него будет System.InvalidOperationException с причиной Illegal Cross-thread Operation.

Перед настройкой источника данных вызывается BeginUpdate(), чтобы предотвратить перекрашивание ComboBox, пока элементы управления загружают данные. BeginUpdate обычно вызывается, когда элементы добавляются по одному, чтобы избежать мерцания и улучшить производительность, но это также полезно в этом случае. Это более очевидно во втором методе.

Private cts As CancellationTokenSource

Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles MyBase.Shown
    cts = New CancellationTokenSource()
    Task.Run(Function() GetProducts(Me.ComboBox1, cts.Token))
    'Code here is executed right after Task.Run()
End Sub

Public Function GetProducts(ctrl As ComboBox, token As CancellationToken) As Task
    If token.IsCancellationRequested Then Return Nothing

    ' Begin loading data, synchronous or asynchrnonous
    ' The CancellationToken (token) can be passed to other procedures or
    ' methods that accept a CancellationToken
    '    (...)
    ' End loading data, synchronous or asynchrnonous

    If token.IsCancellationRequested Then Return Nothing
    ctrl.BeginInvoke(New MethodInvoker(
        Sub()
            ctrl.BeginUpdate()
            ctrl.DataSource = [The DataSource]
            ctrl.EndUpdate()
        End Sub
    ))
    Return Nothing
End Function

Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
    If cts IsNot Nothing Then
        cts.Cancel()
        cts.Dispose()
    End If
End Sub

Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click
    If cts IsNot Nothing Then
        cts.Cancel()
        cts.Dispose()
        cts = Nothing
    End If
End Sub


Form asynchronous loading 1


► Второй метод использует asyn c / шаблон ожидания

Модификатор Asyn c добавлен в обработчик Form.Shown.
Оператор Await применяется к Task.Run(), приостанавливая выполнение другого кода в методе до тех пор, пока задача не вернется, в то время как управление возвращается текущему потоку для других операций.

GetProducts() - это метод Async, который возвращает Задачу, в данном случае.

Код, следующий за вызовом Await Task.Run(), выполняется после возврата GetProducts().

Эта процедура работает иначе, чем предыдущая:
здесь предполагается, что данные загружаются в коллекцию - какой-то IEnumerable<T> - возможно, List<T>, как показано в вопрос.

Данные, если они доступны, добавляются в коллекцию ComboBox.Items кусками 120 элементов (не числом magi c, их можно настроить на любое другое значение в связи со сложностью данных) в al oop.

Await Task.Delay () вызывается в начале, чтобы соответствовать требованиям async/await. В этом нет необходимости, его можно удалить, но появится предупреждение об отсутствующем операторе Await.

Здесь нет CancellationTokenSource. Не потому, что в этом шаблоне нет необходимости, просто потому, что я думаю, что было бы неплохо попытаться добавить CancellationToken к вызову метода, как показано в предыдущем примере, к Знакомство . Поскольку в этом методе используется al oop, к запросу l oop может быть добавлена ​​проверка запроса на отмену, что делает отмену еще более эффективной.

Private Async Sub Form1_Shown(sender As Object, e As EventArgs) Handles MyBase.Shown
     Await Task.Run(Function() GetProducts(Me.ComboBox1))
    ' Code here is executed after the GetProducts() method returns
End Sub

Public Async Function GetProducts(ctrl As ComboBox) As Task
    Await Task.Delay(10)

    ' Begin loading data, synchronous or asynchrnonous
    '    (...)
    '     Generates [The List] Enumerable object
    ' End loading data, synchronous or asynchrnonous

    Dim position As Integer = 0
    For i As Integer = 0 To ([The List].Count \ 120)
        ctrl.BeginInvoke(New MethodInvoker(
            Sub()
                ctrl.BeginUpdate()
                ctrl.Items.AddRange([The List].Skip(position).Take(120).ToArray())
                ctrl.EndUpdate()
                position += 120
            End Sub
        ))
    Next
End Function

Form asynchronous loading 2

...