我做了一个有点大的应用程序运行良好,除了它的UI(winforms)冻结时使用webclient从web检索数据(链接是嗯...不是最快),或数据连接从数据库中检索查询(存储在远,慢的服务器中 - 无法避免)。
所以我想利用异步方法,以便用户可以移动,最小化并单击窗口而不会让操作系统感到紧张并将其标记为“没有响应”。
我不希望我的代码在这些长度操作之间不做任何其他操作,只是保持UI响应(我知道我应该禁用控件以防止用户在第一次操作完成之前询问其他内容或同样的事情)
但我对异步方法没有任何经验,我的第一次尝试是这样的:
Public Function GetDatatableUIblocked() As DataTable
Dim retTable As New DataTable
Dim connection As New OleDb.OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=\\lanserver\storage\DB.accdb;Persist Security Info=False;")
Dim command = connection.CreateCommand()
command.CommandText = "SELECT * FROM bdPROC;"
connection.Open()
Dim reader = command.ExecuteReader
retTable.Load(reader)
connection.Close()
Return retTable
End Function
Public Function GetDatatableUIfree() As DataTable
Dim retTable As New DataTable
Dim connection As New OleDb.OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=\\lanserver\storage\DB.accdb;Persist Security Info=False;")
Dim command = connection.CreateCommand()
command.CommandText = "SELECT * FROM bdPROC;"
connection.Open()
Dim readerTask = command.ExecuteReaderAsync()
readerTask.Start() '<=========================== EXCEPTION HAPPENS HERE
Do
Application.DoEvents()
Loop Until readerTask.IsCompleted OrElse readerTask.IsFaulted
Dim reader = readerTask.Result
retTable.Load(reader)
connection.Close()
Return retTable
End Function
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
ProgressBar1.Visible = True
Dim dt = GetDatatableUIblocked()
ProgressBar1.Visible = False
DataGridView1.DataSource = dt
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
ProgressBar1.Visible = True
Dim dt = GetDatatableUIfree()
ProgressBar1.Visible = False
DataGridView1.DataSource = dt
End Sub
然而,我在运行readerTask.Start()
时遇到异常,它说“在已完成的任务中无法调用启动”(我正在翻译,我的VS不是英文)。
我在这里检查了一些线索,但说实话,我无法掌握这个概念并将其应用到我的问题中,所以我谦卑地寻求帮助。非常感谢你!
答案 0 :(得分:1)
让UI免费的一种方法是使用Task
。建议的解决方案类似于阻止BackGroundWorker
带有无操作循环的点,等待它完成。下面的代码进行了一些其他值得注意的更改:
' note the Async modifier
Private Async Sub btnDoIt_Click(...
Dim sql = "SELECT * FROM RandomData"
dtSample = Await Task(Of DataTable).Run(Function() LoadDataTable(sql))
dgv2.DataSource = dtSample
End Sub
Private Function LoadDataTable(sql As String) As DataTable
' NO UI/Control references allowed
Dim dt = New DataTable
Using dbcon As New OleDbConnection(ACEConnStr)
Using cmd As New OleDbCommand(sql, dbcon)
dbcon.Open()
dt.Load(cmd.ExecuteReader())
End Using
End Using
Return dt
End Function
我没有OP中提到的确切条件,但我确实有一个500k行的Access表。这对OleDB征税足够,可能需要6-10秒才能加载,这足以说明用户界面是否仍然保持响应。确实如此。
Asynch
使用等待Task
完成的任何方法。LoadTable
方法设置为从有效查询加载任何表,因此其他东西可以使用它。如果/当查询发生变化时,只需更改调用它的内容。Using
块为我们执行此操作。DoEvents
循环,也不需要完成通知的事件。 Await
代码将等待加载方法完成,以便之后可以执行的任何其他操作。 Task
通常比BackGroundWorker
更容易使用。
<强>资源强>
答案 1 :(得分:0)
Public Function GetDatatableUIfree() As DataTable
Dim connection As New OleDb.OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=\\lanserver\storage\DB.accdb;Persist Security Info=False;")
Dim command = connection.CreateCommand()
command.CommandText = "SELECT * FROM bdPROC;"
connection.Open()
Dim retTable As New DataTable, bgw As New BackgroundWorker, bgw_complete As Boolean
AddHandler bgw.DoWork,
Sub(sender As Object, e As DoWorkEventArgs) retTable.Load(command.ExecuteReader)
AddHandler bgw.RunWorkerCompleted,
Sub(sender As Object, e As RunWorkerCompletedEventArgs) bgw_complete = True
bgw.RunWorkerAsync()
Do
Application.DoEvents()
Loop Until bgw_complete
connection.Close()
Return retTable
End Function
非常感谢你的帮助。