我有一个主要形式,预计将执行一些长期操作。与此同时,我试图显示已执行操作的百分比。
所以我创建了第二个这样的形式:
Private Delegate Sub DoubleFunction(ByVal D as Double)
Private Delegate Sub EmptyFunction()
Public Class LoaderClass
Inherits Form
'Some properties here
Public Sub DisplayPercentage(Value as Double)
If Me.InvokeRequired then
dim TempFunction as New DoubleFunction(addressof DisplayPercentage)
Me.Invoke(TempFunction, Value)
Else
Me.PercentageLabel.text = Value
End if
End sub
Public Sub CloseForm()
If Me.InvokeRequired Then
Dim CloseFunction As New EmptyFunction(AddressOf CloseForm)
Me.Invoke(CloseFunction)
Else
Me.Close()
End If
FormClosed = True
End Sub
End class
我的主要子,预期执行长操作的那个子的另一种形式如下:
Private Sub InitApplication
Dim Loader as new LoaderClass
Dim LoaderThread as new thread(Sub()
Loader.ShowDialog()
End sub)
LoaderThread.start()
Loader.DisplayPercentage(1/10)
LoadLocalConfiguration()
Loader.DisplayPercentage(2/10)
ConnectToDataBase()
Loader.DisplayPercentage(3/10)
LoadInterfaceObjects()
Loader.DisplayPercentage(4/10)
LoadClients()
...
Loader.CloseForm()
End sub
代码的工作时间几乎 95%但有时我会在sub DisplayPercentage 中的某处获得线程异常。我什么都没改,我只是再次点击开始按钮,调试器继续执行没有任何问题。
我得到的异常是:跨线程操作无效:控制'LoaderClass'从一个线程以外的线程访问事件虽然我正在使用:如果InvokeRequired
有人知道该代码有什么问题吗?
谢谢。
答案 0 :(得分:2)
这是一个标准的线程错误,称为“竞争条件”。您的代码的基本问题是,在创建对话框的本机窗口之后,InvokeRequired属性只能是准确的。问题是你不要等待。您启动的线程需要时间来创建对话框。当InvokeRequired仍然返回 false 时它会爆炸但是稍后会创建一个窗口,而Invoke()现在会大声反对在工作线程上调用。
这需要互锁,您必须使用AutoResetEvent。在对话框的Load事件处理程序中调用其Set()方法。在InitApplication()中调用它的WaitOne()方法。
这是不此代码的唯一问题。您的对话框与应用程序中的其余窗口也没有Z顺序关系。非零赔率,它会在后面显示另一个窗口。
由SystemEvents类引起的一种特别讨厌的问题。哪个需要在UI线程上触发事件。它不知道UI线程是什么线程,它猜测订阅事件的第一个是UI线程。如果这是你的对话,当它使用ProgressBar时,结果非常糟糕。它使用SystemEvents知道何时重绘自己。当现在在错误的线程上引发其中一个SystemEvents时,关闭对话框后,程序将崩溃并长时间刻录。
吓到你了吗?不要这样做。仅在UI线程上显示UI,仅在工作线程上执行缓慢的非UI代码。
答案 1 :(得分:1)
感谢您提出的建议。请问怎么做?我应该在哪里 添加Invoke?
假设您已选择在主UI线程中保留主窗体的“加载”代码(可能从Load()事件中调用), AND 您已设置LoaderClass()作为Project中的“启动画面” - >属性...
这是LoaderClass()的样子:
Public Class LoaderClass
Private Delegate Sub DoubleFunction(ByVal D As Double)
Public Sub DisplayPercentage(Value As Double)
If Me.InvokeRequired Then
Dim TempFunction As New DoubleFunction(AddressOf DisplayPercentage)
Me.Invoke(TempFunction, Value)
Else
Me.PercentageLabel.text = Value
End If
End Sub
End Class
*这与你所拥有的相同,但我将代表移到了班级。
*请注意,您不需要CloseForm()方法,因为一旦主窗体完全加载,框架将自动关闭启动屏幕。
现在,在主窗体中,您可以使用My.Application.SplashScreen
获取闪屏的显示实例,并将其强制转换为LoaderClass()。然后在适当的时间使用适当的值调用DisplayPercentage()方法:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
InitApplication()
End Sub
Private Sub InitApplication()
Dim Loader As LoaderClass = DirectCast(My.Application.SplashScreen, LoaderClass)
Loader.DisplayPercentage(1 / 10)
LoadLocalConfiguration()
Loader.DisplayPercentage(2 / 10)
ConnectToDataBase()
Loader.DisplayPercentage(3 / 10)
LoadInterfaceObjects()
Loader.DisplayPercentage(4 / 10)
LoadClients()
' Loader.CloseForm() <-- This is no longer needed..."Loader" will be closed automatically!
End Sub
Private Sub LoadLocalConfiguration()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
Private Sub ConnectToDataBase()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
Private Sub LoadInterfaceObjects()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
Private Sub LoadClients()
System.Threading.Thread.Sleep(1000) ' simulated "work"
End Sub
End Class
如果一切顺利,您的初始屏幕应自动显示,随进度更新,然后在主表单加载完成并自行显示后自动关闭。
答案 2 :(得分:0)
Me.Invoke(TempFunction, Value)
应该是:
Me.Invoke(TempFunction, new Object(){Value})
因为带参数的重载需要一系列参数。
值在当前线程中的函数堆栈上。您需要在GC堆上分配内存并将值复制到该内存,以便即使在本地堆栈被销毁后,其他线程也可以使用它。