我有我的表单类,第二个模块有一些特殊功能, 当我单击我的表单上的一个按钮时,我在一个单独的线程中运行第二个模块的公共函数(后来运行第二个模块中的其他公共函数),我设置了SetApartmentState(ApartmentState.STA),我尝试使用deletage sub和CheckForIllegalCrossThreadCalls = False,但问题保持不变,我的线程函数(在第二个模块上)无法访问我的表单控件,但是当我将函数移动到表单类时,一切都再次工作,你建议解决什么这个问题?
Public Class Form1
Dim T0 As Thread
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
CheckForIllegalCrossThreadCalls = False
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
T0 = New Thread(AddressOf sub1)
T0.SetApartmentState(ApartmentState.STA)
T0.start()
End Sub
End Class
Module Module1
Public Sub Sub1()
msgbox(form1.textbox1.text) 'even if the textbox contains content it returns ""
Function2()
End Sub
Public Function Function1()
'SomeInstructions
msgbox(form1.textbox1.text) 'same problem here
End Function
End module
PS:它在调试时没有给出任何错误或停止代码,我试图将sub1放在表单类和模块中的其他函数中,但是,只有他sub1才能访问控件,我试过代表,但我不知道我做得对,请有人提出任何建议
答案 0 :(得分:2)
这里有两个问题。
其中之一是由于默认表单在VB.NET中的工作方式。请参阅,在C#中,您需要一个类型为Form1
的具体实例才能访问其非静态成员,而在VB.NET中Form1
看起来像一样的表单实例允许你写Form1.TextBox1.Text
之类的东西。当您从UI线程访问Form1
的成员时,这可以正常工作,但是当您尝试从后台线程获取它时,会创建Form1
的 new 实例,并且该线程看到的Form1.TextBox1
实际上指向TextBox
的完全不同的实例。
另一种说法是VB.NET中的默认表单实例是线程静态。
解决这个问题的方法是保留对Form1
的具体引用,以便您可以传递它。
当你去
Dim myForm As New Form1
......或
Dim myForm As Form1 = Me
...指向特定Form1
实例的'myForm'变量可以像任何其他引用一样在线程之间传递,并且不会改变其含义。
但是,这给我们带来了问题#2:
您不应该访问UI控件(这意味着来自 Control
的任何类型,包括 Form
,来自创建它的线程以外的线程。
如果您必须这样做,则必须将访问Control
的调用封送到创建它的线程,例如使用Invoke
/ BeginInvoke
(还有其他方式也是如此。
这是对代码的修改以实现您想要的,以及演示多线程“切换”的更复杂的示例:在UI线程上收集有趣的状态,在后台线程上执行它的工作,然后在UI线程。
Imports System.Threading
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' We don't need this anymore.
' We'll do things right and access
' the UI on the UI thread only.
' CheckForIllegalCrossThreadCalls = False
End Sub
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
' Note that we're passing a reference
' to THIS instance of Form1 to Sub1 and Sub2.
Dim form As Form1 = Me
' Let's spin up some threads.
Dim T0 As New Thread(Sub() Module1.Sub1(form))
T0.Start()
Dim T1 As New Thread(Sub() Module1.Sub2(form))
T1.Start()
End Sub
End Class
Module Module1
' Note that this sub now accepts
' a reference to an instance of Form1.
Public Sub Sub1(form As Form1)
' This is what we want to do:
Dim action As New Action(Sub() MsgBox(form.TextBox1.Text))
' See if we're on the right thread.
If form.InvokeRequired Then
' Invoke on the thread which created this Form1 instance.
form.Invoke(action)
Else
' Invoke on the current thread.
action.Invoke()
End If
End Sub
' This is a more complex example.
Public Sub Sub2(form As Form1)
' This function will get the text from TextBox1 when invoked.
' It still needs to be invoked on the UI thread though.
Dim getText As New Func(Of String)(Function() form.TextBox1.Text)
Dim text As String
If form.InvokeRequired Then
text = CStr(form.Invoke(getText))
Else
text = getText() ' Shorthand syntax.
End If
' Now that we have the text, let's do some
' intensive work with it while we're on
' the background thread.
For i = 0 To 5
text &= i
Thread.Sleep(100)
Next
' Now we want to show the message box - again, on the UI thread.
Dim showMessageBox As New Action(Sub() MsgBox(text))
If form.InvokeRequired Then
form.Invoke(showMessageBox)
Else
showMessageBox()
End If
End Sub
End Module