如何在VB.NET窗体中处理长时间运行的任务?
我目前正在开发一个VB.NET表单,该表单根据Excel文件和表单要求的一些额外数据(项目名称、客户名称、使用SQL等)自动创建Word文档 此程序工作正常,大约需要1或2分钟完成。 问题是我的所有脚本都在如何在VB.NET窗体中处理长时间运行的任务?,vb.net,multithreading,winforms,Vb.net,Multithreading,Winforms,我目前正在开发一个VB.NET表单,该表单根据Excel文件和表单要求的一些额外数据(项目名称、客户名称、使用SQL等)自动创建Word文档 此程序工作正常,大约需要1或2分钟完成。 问题是我的所有脚本都在按钮生成中。请单击处理程序。因此,当按下“生成”按钮时,窗体窗口被阻塞,无法取消 它不应该在单击处理程序中。为这项漫长的任务打开一条新的线索似乎更好。但是我对多线程不是很熟悉。 我试着用 ThreadPool.QueueUserWorkItem(... 但是我的任务是在主窗体中生成子集标签并
按钮生成中。请单击处理程序。因此,当按下“生成”按钮时,窗体窗口被阻塞,无法取消
它不应该在单击处理程序中。为这项漫长的任务打开一条新的线索似乎更好。但是我对多线程不是很熟悉。
我试着用
ThreadPool.QueueUserWorkItem(...
但是我的任务是在主窗体中生成子集标签并更新进度条,因此除非使用
Me.Invoke(New MethodInvoker(Sub()
label.Text = "..."
ProgressBar.Value = 10
' ...
End Sub)
每次我需要更新表单上的某些内容时,我甚至无法用它检索任何新的按钮(取消按钮就好了)
这基本上是我的代码:
Public Class TestFichesAutomation
Private Sub BtnGenerate_Click(sender As Object, e As EventArgs) Handles BtnGenerate.Click
System.Threading.ThreadPool.QueueUserWorkItem(Sub() Generate())
End Sub
Public Sub Generate()
' Check user options, retrieve Excel Data, SQL, Fill in custom classes, create Word docs (~ 1 minute)
End Sub
那么你将如何处理这个脚本呢?线程是一个很好的解决方案吗?您应该考虑使用
如果您需要做的工作受CPU限制,我喜欢使用Task.Run()
doc
通过使事件处理程序异步并让它等待工作,可以防止UI锁定,并在大多数情况下避免使用Invoke
例:
对于使用Async/Await
进行进度报告,您可能会感兴趣非常感谢您的帮助^^^和有用的文档
我的应用程序现在打开一个新线程,并使用2个自定义类充当缓冲区:
Private Async Sub Btn_Click(sender As Object, e As EventArgs) Handles Btn.Click
myProgress = New Progress
' a custom class just for the UI with the current task, current SQL connection status and progress value in %
_Options.ProjectName = TextBoxProjectName.Text
_Options.CustomerName = TextBoxCustomerName.Text
...
' Fill in a custom "_Options" private class to act as a buffer between the 2 thread (the user choices)
Loading = New Loading()
Me.Visible = False
Loading.Show() ' Show the Loading window (a ProgressBar and a label : inputLine)
Task.Run(Function() Generate(Progress, _Options))
Me.Visible = True
End Sub
Public Async Function Generate(ByVal myProgress As Progress, ByVal Options As Options) As Task(Of Boolean)
' DO THE LONG JOB and sometimes update the UI :
myProgress.LoadingValue = 50 ' %
myProgress.CurrentTask= "SQL query : " & ...
Me.Invoke(New MethodInvoker(Sub() UpdateLoading()))
' Check if the task has been cancelled ("Cancelled" is changed by a passvalue from the Loading window):
If myProgress.Cancelled = True Then ...
' Continue ...
End Function
Public Shared Sub UpdateLoading()
MyForm.Loading.ProgressBar.Value = myProgress.LoadingValue
MyForm.Loading.inputLine.Text = myProgress.CurrentTask
' ...
End Sub
这可能会有帮助:虽然我通常倾向于async
和wait
,但可能会重复,因为问题是要更新UI,只要工作在另一个线程上完成,你就不能像在任务中一样挥手挥动调用
。运行
——我想这是值得这么做的足够的CPU约束。我想有办法可以解决,但你可能是对的,只使用调用
。然而,由于在这两种情况下(Async/Await
或更直接地使用线程池),您都需要调用invoke
,因此我还是选择Async/Await
,因为在我看来,这会带来更干净的代码。我当然更喜欢Async/Await
,我不是有意提出其他建议。然后我误解了您,重新阅读您的评论,我确实可以看出这不是您所说的。同意,与Async/Await
问题分开:我认为如果您使用原子操作来增加进度变量,如PercentComplete
,假设您提前计算所需的总工作量,则可以防止使用invoke
。然后UI线程可以定期检查并更新progressbar。
Private Async Sub Btn_Click(sender As Object, e As EventArgs) Handles Btn.Click
myProgress = New Progress
' a custom class just for the UI with the current task, current SQL connection status and progress value in %
_Options.ProjectName = TextBoxProjectName.Text
_Options.CustomerName = TextBoxCustomerName.Text
...
' Fill in a custom "_Options" private class to act as a buffer between the 2 thread (the user choices)
Loading = New Loading()
Me.Visible = False
Loading.Show() ' Show the Loading window (a ProgressBar and a label : inputLine)
Task.Run(Function() Generate(Progress, _Options))
Me.Visible = True
End Sub
Public Async Function Generate(ByVal myProgress As Progress, ByVal Options As Options) As Task(Of Boolean)
' DO THE LONG JOB and sometimes update the UI :
myProgress.LoadingValue = 50 ' %
myProgress.CurrentTask= "SQL query : " & ...
Me.Invoke(New MethodInvoker(Sub() UpdateLoading()))
' Check if the task has been cancelled ("Cancelled" is changed by a passvalue from the Loading window):
If myProgress.Cancelled = True Then ...
' Continue ...
End Function
Public Shared Sub UpdateLoading()
MyForm.Loading.ProgressBar.Value = myProgress.LoadingValue
MyForm.Loading.inputLine.Text = myProgress.CurrentTask
' ...
End Sub