跨线程操作无效... - VB.NET

18
我正在使用vb.net,在我的程序中,当我运行背景工作者使该文本框启用时,会出现'跨线程操作无效'错误。我的主要子程序将首先将启用设置为false,当后台工作者运行时,它将再次将其设置为true然后退出。为什么会出现错误? FYI:这里有更多的代码,但我不想让它变得更加混乱...
这是堆栈跟踪:
at System.Windows.Forms.Control.get_Handle()
   at System.Windows.Forms.Control.OnEnabledChanged(EventArgs e)
   at System.Windows.Forms.Control.set_Enabled(Boolean value)
   at Helium.Form1.BackgroundWorker1_DoWork(Object sender, DoWorkEventArgs e) in C:\Users\Kevin\documents\visual studio 2010\Projects\Helium\Helium\Form1.vb:line 167
   at System.ComponentModel.BackgroundWorker.OnDoWork(DoWorkEventArgs e)
   at System.ComponentModel.BackgroundWorker.WorkerThreadStart(Object argument)

以下是准确的错误信息:

{"Cross-thread operation not valid: Control 'mainText' accessed from a thread other than the thread it was created on."}

有人能帮我吗!

谢谢,

KEvin


1
我认为错误信息已经很明显了。 - Anon.
9个回答

28
BackgroundWorker 类的目的是在非 GUI 线程上执行工作,同时保持 GUI 的响应性。除非您将 Control.CheckForIllegalCrossThreadCalls 设置为 false(您不应该这样做),或者像其他回答中建议的那样使用 Invoke(我也不建议这样做),否则您将得到非法跨线程操作异常。

如果您想要在 BackgroundWorker 运行时发生与 GUI 相关的 "事情",我通常会建议使用 BackgroundWorker.ReportProgress 方法,并将适当的处理程序附加到 BackgroundWorker.ProgressChanged 事件上。如果您希望在 BackgroundWorker 完成后发生 GUI 上的某些操作,则只需将处理程序附加到 BackgroundWorker.RunWorkerCompleted 事件来更新 GUI。


使用 BackgroundWorker.ReportProgress 帮助我在线程运行时更新 GUI。 - Codemunkeee

15
在VB.NET中更好的方法是使用Extension,这可以为跨线程GUI控制调用提供非常漂亮的代码。只需将此代码行添加到任何模块即可。
<System.Runtime.CompilerServices.Extension()> _
Public Sub Invoke(ByVal control As Control, ByVal action As Action)
    If control.InvokeRequired Then
        control.Invoke(New MethodInvoker(Sub() action()), Nothing)
    Else
        action.Invoke()
    End If
End Sub

现在您可以编写跨线程控制代码,只需一行即可调用任何控件。

比如这样,假设您想要清除一个 ComboBox 并且它可能从线程或者非线程调用,现在您可以这样实现

cboServerList.Invoke(Sub() cboServerList.Items.Clear())

在清除后想要添加一些内容吗?

cboServerList.Invoke(Sub() cboServerList.Items.Add("Hello World"))

14

你应该在哪里设置Enabled属性?如果你在DoWork事件处理程序中这样做,那么这段代码会在与创建按钮的线程不同的线程上运行,这应该会出现你遇到的异常。为了解决这个问题,你应该使用BeginInvoke。方便起见,可以将其包装在一个方法中,例如:

Private Sub SetControlEnabled(ByVal ctl As Control, ByVal enabled As Boolean)
    If ctl.InvokeRequired Then
        ctl.BeginInvoke(New Action(Of Control, Boolean)(AddressOf SetControlEnabled), ctl, enabled)
    Else
        ctl.Enabled = enabled
    End If
End Sub

现在你可以安全地调用该方法,从任何线程启用或禁用任何控件:

SetControlEnabled(someButton, False)

14

Form1_Load子程序中键入以下代码:

System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = False

它可以解决所有被阻止的跨线程操作问题。


6
请注意,这个答案是错误的,如在调试期间避免跨线程错误只需将CheckForIllegalCrossThreadCalls设置为false是否安全?中所解释的那样。 请不要使用它,而应该修复您的代码问题(即创建适当的委托)。 - AStopher

2

如果您想从另一个线程直接设置 UI 线程上的控件属性是不行的。但这是可以实现的,下面是 MSDN 上的一个示例。

Private Sub SetText(ByVal [text] As String)

    ' InvokeRequired required compares the thread ID of the'
    ' calling thread to the thread ID of the creating thread.'
    ' If these threads are different, it returns true.'
    If Me.textBox1.InvokeRequired Then
        Dim d As New SetTextCallback(AddressOf SetText)
        Me.Invoke(d, New Object() {[text]})
    Else
        Me.textBox1.Text = [text]
    End If
End Sub

那么我应该把textbox1.enabled = true这部分放在哪里呢? - lab12

0
请在您的Form_Load()事件下编写以下代码部分。这样可以解决您所有的问题。
'## crossed-thread parts will not be controlled by this option...

System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = False

0

建议使用AutomationPeer。

例如,下面的代码在我按下F5键时调用Execute按钮。类似地,如果您想让一个线程或后台工作者调用一个事件或函数,您可以使用Automation Peer。在您的情况下,您可以使用文本框及其适当的属性来调用,而不是按钮(我在此处使用的)。

'Button name=btnExecute

'Imports System.Windows.Automation.Peers

'Imports System.Windows.Automation.Provider

If e.Key = Key.F5 Then

   Dim peer As New ButtonAutomationPeer(btnExecute)

   Dim invokeProv As IInvokeProvider = TryCast(peer.GetPattern(PatternInterface.Invoke), IInvokeProvider)

   invokeProv.Invoke()

End If

敬礼 RV


0
这是一个巧妙的方法,让你既能拥有蛋糕,又能吃掉它!换句话说,这是一种让你在BackgroundWorker中编写UI交互代码而不会遇到跨线程操作的方法。我有一天为了一个项目临时凑合出来的,买家自行决定是否使用!
' Init background worker
Dim _BGWorker As BackgroundWorker

' Your Windows Form's _Load event handler:
Private Sub Form_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    ' Just for this example, we're newing up the _BGWorker here
    _BGWorker = New BackgroundWorker()
    _BGWorker.WorkerReportsProgress = True
End Sub

' Magical UI Action Handling ProgressChanged Event Handler Thing (v24.4.550-alpha9) ™ ©
Private Sub _BGWorker_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs) Handles _BGWorker.ProgressChanged
    ' Take the UserState object and cast to an Action delegate type
    Dim uiAction As Action = CType(e.UserState, Action)

    ' Check if an action was passed
    If uiAction IsNot Nothing Then
        ' Run it if so!
        uiAction()
    End If
End Sub

' Standard DoWork handler for BackroundWorker
Private Sub _BGWorker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles _BGWorker.DoWork
    '...your background worker code...

    ' EXAMPLE:
    '
    ' Me.Text = "Background worker is (trying to) change form title! :-O"
    '
    ' This line would normally fail with an exception when written directly in the DoWork
    ' handler of a BackgroundWorker. Exception you would see is:
    '   System.InvalidOperationException: Cross-thread operation not valid: Control 'FormName'
    '   accessed from a thread other than the thread it was created on.'

    ' BUT...

    ' If we write something like this:

    _BGWorker.ReportProgress(-1, New Action(
    Sub()
        '...and put that line inside this action:
        Me.Text = "Background worker is changing form title! :-O"

        ' Then NO PROBLEM! UI-interactive code MAGIC!

        ' Well, it's not magic... This works because this code is not executed here immediately.
        ' It is an Action Delegate: https://learn.microsoft.com/en-us/dotnet/api/system.action-1?view=net-7.0
        ' You can write ANY UI-interactive code inside blocks like this in your BackgroundWorkers
        ' and they will all execute on the main thread, because the Action delegate is being 
        ' shipped to the ProgressChanged event handler on the main Form thread.

        'TODO: Add your other UI code...

    End Sub))

    ' Now simply repeat the above section every time you wish write UI-interactive
    ' code in your BG workers! SHAZAM!

    '...your background worker code...
End Sub

-1
CheckForIllegalCrossThreadCalls = False

请添加一些额外的解释。 - rghome

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接