Cat*_*nat 3 .net vb.net entity-framework task winforms
我想将 ComboBox 绑定到 53k 行的 EF Core 实体。这需要一些时间,大约 10 秒。
我认为如果我将绑定过程放在 FormShown事件中,UI 将保持响应。但事实并非如此。
我试过的:
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
Run Code Online (Sandbox Code Playgroud)
无济于事,因为什么都没有发生。显示了 Form,但 ComboBox 是空的。
如何将 ComboBox 返回到主线程?
在不冻结容器 Form 的情况下在 ComboBox(或其他控件)中加载内容的四种方法。
注意:组合框列表不支持无限数量的项目。在将65534元素添加到 List后,DropDown 实际上将停止工作。
DropDownList 和 ListBox 可以支持更多的项目,但这些也会在某个点(~80,000项目)开始崩溃,项目的滚动和呈现将明显受到损害。
在所有这些方法中(除了最后一个,请阅读此处的注释),CancellationTokenSource用于将CancellationToken传递给该方法,以发出信号(如果需要)已请求取消。
一个方法可以return在CancellationTokenSource.Cancel()被调用时检查CancellationToken.IsCancellationRequested属性,或者抛出,调用[CancellationToken].ThrowIfCancellationRequested().
接受 CancellationToken 的 .Net 方法总是抛出。我们可以尝试/捕获调用方法中的OperationCanceledException或TaskCanceledException,以便在取消请求已执行时得到通知。
CancellationTokenSource.Cancel()在表单关闭时也会调用,以防数据加载仍在运行。
设置CancellationTokenSource到null(Nothing),其设置时:其IsDiposed属性是内在的,不能被直接访问。
? 第一种方法,使用在 UI 线程中创建的IProgress委托,用于在从工作线程调用时更新 UI 控件。
Private cts As CancellationTokenSource
Private progress As Progress(Of String())
Private Async Sub Form1_Shown(sender As Object, e As EventArgs) Handles MyBase.Shown
cts = New CancellationTokenSource()
progress = New Progress(Of String())(Sub(data) OnProgress(data))
Try
Await GetProductsProgressAsync(progress, cts.Token)
Catch ex As OperationCanceledException
' This exception is raised if cts.Cancel() is called.
' It can be ignored, logged, the User can be notified etc.
Console.WriteLine("GetProductsProgressAsync canceled")
End Try
'Code here is executed right after GetProductsProgressAsync() returns
End Sub
Private Sub OnProgress(data As String())
ComboBox1.BeginUpdate()
ComboBox1.Items.AddRange(data)
ComboBox1.EndUpdate()
End Sub
Private Async Function GetProductsProgressAsync(progress As IProgress(Of String()), token As CancellationToken) As Task
token.ThrowIfCancellationRequested()
' Begin loading data, asynchronous only
' The CancellationToken (token) can be passed to other procedures or
' methods that accept a CancellationToken
' (...)
' If the methods used allow to partition the data, report progress here
' progress.Report([array of strings])
' End loading data
' Otherwise, generate an IEnumerable collection that can be converted to an array of strings
' (or any other collection compatible with the Control that receives it)
progress.Report([array of strings])
End Function
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles MyBase.FormClosing
CancelTask()
End Sub
Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click
CancelTask()
End Sub
Private Sub CancelTask()
If cts IsNot Nothing Then
cts.Cancel()
cts.Dispose()
cts = Nothing
End If
End Sub
Run Code Online (Sandbox Code Playgroud)
注意:Form 的FormClosing事件仅在此处订阅,当然其他所有方法也是如此
Progress<T>使用方法委托,OnProgress(data As String()).
它可以用一个 Lambda 代替:
' [...]
' Progress<T> can be declared in place
Dim progress = New Progress(Of String())(
Sub(data)
ComboBox1.BeginUpdate()
ComboBox1.Items.AddRange(data)
ComboBox1.EndUpdate()
End Sub)
Await GetProductsProgressAsync(progress, cts.Token)
' [...]
Run Code Online (Sandbox Code Playgroud)
? 使用 OleDb 异步方法查询数据库的第二种方法。
所有方法都接受一个 CancellationToken,可用于在任何阶段取消操作。某些操作可能需要一些时间才能取消生效。无论如何,这一切都是异步发生的。
我们可以像以前一样捕获OperationCanceledException通知或记录(或任何适合特定上下文的内容)取消。
Private cts As CancellationTokenSource
Private Async Sub Form1_Shown(sender As Object, e As EventArgs) Handles MyBase.Shown
cts = New CancellationTokenSource() ' <= Can be used to set a Timeout
Dim connString As String = "<Some connection string>"
Dim sql As String = "<Some Query>"
Try
ComboBox1.DisplayMember = "[A Column Name]"
ComboBox1.ValueMember = "[A Column Name]" ' Optional
ComboBox1.DataSource = Await GetProductsDataAsync(connString, sql, cts.Token)
Catch ocEx As OperationCanceledException
Console.WriteLine("GetProductsDataAsync canceled")
Catch ex As Exception
' Catch exceptions related to data access
Console.WriteLine(ex.ToString())
End Try
'Code here is executed right after GetProductsDataAsync() returns
cts.Dispose()
End Sub
Public Async Function GetProductsDataAsync(connectionString As String, query As String, token As CancellationToken) As Task(Of DataTable)
token.ThrowIfCancellationRequested()
Dim dt As DataTable = New DataTable
Using conn As New OleDbConnection(connectionString),
cmd As New OleDbCommand(query, conn)
Await conn.OpenAsync(token)
dt.Load(Await cmd.ExecuteReaderAsync(token))
End Using
Return dt
End Function
Run Code Online (Sandbox Code Playgroud)
当您需要将一个或多个将来更新的控件传递给异步过程时,可以使用另外两种方法。
您需要确保这些控件在任务执行时可用并且它们的句柄已经创建。
Visible = False具有从未显示过的 TabContol 的 TabPage 的子控件或它们的子控件不创建句柄。? 第三种方法,即发即忘风格。任务运行从某个源加载数据的方法。加载完成后,数据被设置为 ComboBox.DataSource。
BeginInvoke()用于在 UI 线程中执行此操作。没有它,将引发带有原因的 System.InvalidOperationExceptionIllegal Cross-thread Operation。
在设置 DataSource 之前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
Private 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
' Async methods will throw is the Task is canceled
' (...)
' End loading data, synchronous or asynchrnonous
' Synchronous methods don't accept a CancellationToken
' In this case, check again now if we've been canceled in the meanwhile
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
Run Code Online (Sandbox Code Playgroud)
? 第四种方法使用async/await 模式
该异步调节剂添加到Form.Shown事件处理程序。
的等待操作员施加于Task.Run(),悬浮剂的其他代码的方法中,直到任务返回执行,而控制返回到当前线程用于其它操作。
GetProducts()是一个Async返回任务的方法,在这种情况下。
Await Task.Run()调用之后的代码在GetProducts()返回后执行。
此过程的工作方式与前一个过程不同:
这里假设数据加载到一个集合中 -IEnumerable<T>某种类型 - 可能List<T>如问题所示。
数据在可用时以元素ComboBox.Items块120(不是幻数,它可以调整为与数据复杂性相关的任何其他值)的循环中添加到集合中。
最后调用Await Task.Yield()以符合async/await要求。它将恢复到Await到达时捕获的 SynchronizationContext 。
这里没有CancellationTokenSource。不是因为不需要使用这种模式,只是因为我认为尝试将 a 添加CancellationToken到方法调用中可能是一个很好的练习,如前面的示例所示,以熟悉。由于此方法使用循环,因此可以在循环中添加取消请求检查,使取消更加有效。
如果数据加载程序使用async方法,Await Task.Yield()可以删除。
Private Async Sub Form1_Shown(sender As Object, e As EventArgs) Handles MyBase.Shown
Await Task.Run(Function() GetProductsAsync(Me.ComboBox1))
' Code here is executed after the GetProducts() method returns
End Sub
Private Async Function GetProductsAsync(ctrl As ComboBox) As Task
' 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)
' BeginInvoke() will post to the same Thread here.
' It's used to update the Control in a non-synchronous way
ctrl.BeginInvoke(New MethodInvoker(
Sub()
ctrl.BeginUpdate()
ctrl.Items.AddRange([The List].Skip(position).Take(120).ToArray())
ctrl.EndUpdate()
position += 120
End Sub
))
Next
Await Task.Yield()
End Function
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
297 次 |
| 最近记录: |