跨线程操作无效 - 需要使用和更新来自异步线程的 UI 线程上的控件

Cross-thread operation not valid - Need to use and update controls on UI thread from async thread

我有一个构建报告文档的程序,我想将 在后台工作人员的“DoWork”处理程序下构建报告的例程。报告的初始部分已启动,但是,一旦我在组合框中引用选定的项目,它就会停止执行?

这是我的代码:

 Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    ProgressBar1.Visible = True
    Application.EnableVisualStyles()
    ProgressBar1.Style = ProgressBarStyle.Marquee
    ProgressBar1.MarqueeAnimationSpeed = 10

    Dim x As New Thread(AddressOf buildReport)
    x.Start()
    MessageBox.Show("Build Complete")
    ProgressBar1.Visible = False
    GC.Collect()
    GC.WaitForPendingFinalizers()
    GC.Collect()
    GC.WaitForPendingFinalizers()
End Sub

'builds the report
Public Sub buildReport()
    Dim app As word.Application = New word.Application
    Dim document As word.Document
    Dim today As String()
    app.Visible = True
    document = app.Documents.Add("K:\ETL Test Files\" & mycallerPreview.previewInst.txtYear.Text & "\" & mycallerPreview.previewInst.txtVendor.Text & "\" & mycallerPreview.previewInst.txtReport.Text & "\Test Report\Report Data\ReportTemplate.doc") 'open up template

    'document.Styles.Add("Contents1")
    'document.Styles.Add("Contents2")
    'document.Styles.Add("Contents3")
    'add info to pre-made bookmarks
    today = Date.Today.ToString.Split(" ")
    document.Bookmarks("Date").Range.Text = today(0).ToString
    document.Bookmarks("Date1").Range.Text = today(0).ToString
    document.Bookmarks("Date2").Range.Text = today(0).ToString
    document.Bookmarks("Date3").Range.Text = today(0).ToString
    document.Bookmarks("Approver").Range.Text = mycallerPreview.mycallerSelect2.txtChecked.Text.ToString
    document.Bookmarks("Number2").Range.Text = mycallerPreview.mycallerSelect2.txtReportNumber.Text.ToString
    document.Bookmarks("Number1").Range.Text = mycallerPreview.mycallerSelect2.txtReportNumber.Text.ToString
    document.Bookmarks("Vendor").Range.Text = mycallerPreview.previewInst.txtVendor.Text.ToString
    document.Bookmarks("Test1").Range.Text = mycallerPreview.mycallerSelect2.txtReportTitle.Text.ToString
    document.Bookmarks("TestTitle").Range.Text = mycallerPreview.mycallerSelect2.txtReportTitle.Text.ToString
    If mycallerPreview.mycallerSelect2.cmbName.SelectedIndex <> -1 Then
        document.Bookmarks("Writer").Range.Text = mycallerPreview.mycallerSelect2.cmbName.SelectedItem.ToString
        document.Bookmarks("Reviewer").Range.Text = mycallerPreview.mycallerSelect2.cmbName.SelectedItem.ToString
    End If
    If mycallerPreview.mycallerSelect2.cmbQuote.SelectedIndex <> -1 Then
        document.Bookmarks("Quote").Range.Text = mycallerPreview.mycallerSelect2.cmbQuote.SelectedItem.ToString
    End If

我的 word 文档中的所有书签都已填写,直到它到达位于“DoWork”处理程序底部的组合框引用。有什么建议吗?

更新:

按照建议,我尝试了线程同步...

    Dim x As New Thread(AddressOf buildReport)
    x.Start()

这并没有解决我的问题,但出现了以下异常:

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

修订:

 'garbage collects and initializes progress bar to default values
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    'create list of objects to pass through ThreadStart Method
    Dim list As New List(Of Object)
    If mycallerPreview.mycallerSelect2.cmbName.SelectedIndex <> -1 Then
        list.Add(mycallerPreview.mycallerSelect2.cmbName.SelectedItem.ToString)
    End If
    If mycallerPreview.mycallerSelect2.cmbQuote.SelectedIndex <> -1 Then
        list.Add(mycallerPreview.mycallerSelect2.cmbQuote.SelectedItem.ToString)
    End If
    list.Add(ProgressBar1)

    Dim x As New Thread(AddressOf buildReport)
    x.Start(list)
    MessageBox.Show("Build Complete")
    GC.Collect()
    GC.WaitForPendingFinalizers()
    GC.Collect()
    GC.WaitForPendingFinalizers()
End Sub

'builds the report
Public Sub buildReport(list_temp As Object)
    Dim progress As New ProgressBar
    progress = list_temp(2)
    progress.Visible = True
    Application.EnableVisualStyles()
    progress.Style = ProgressBarStyle.Marquee
    progress.MarqueeAnimationSpeed = 10
    Dim list As List(Of Object) = list_temp
    Dim app As word.Application = New word.Application
    Dim document As word.Document
    Dim today As String()
    app.Visible = True
    document = app.Documents.Add("K:\ETL Test Files\" & mycallerPreview.previewInst.txtYear.Text & "\" & mycallerPreview.previewInst.txtVendor.Text & "\" & mycallerPreview.previewInst.txtReport.Text & "\Test Report\Report Data\ReportTemplate.doc") 'open up template

    'add info to pre-made bookmarks
    today = Date.Today.ToString.Split(" ")
    document.Bookmarks("Date").Range.Text = today(0).ToString
    document.Bookmarks("Date1").Range.Text = today(0).ToString
    document.Bookmarks("Date2").Range.Text = today(0).ToString
    document.Bookmarks("Date3").Range.Text = today(0).ToString
    document.Bookmarks("Approver").Range.Text = mycallerPreview.mycallerSelect2.txtChecked.Text.ToString
    document.Bookmarks("Number2").Range.Text = mycallerPreview.mycallerSelect2.txtReportNumber.Text.ToString
    document.Bookmarks("Number1").Range.Text = mycallerPreview.mycallerSelect2.txtReportNumber.Text.ToString
    document.Bookmarks("Vendor").Range.Text = mycallerPreview.previewInst.txtVendor.Text.ToString
    document.Bookmarks("Test1").Range.Text = mycallerPreview.mycallerSelect2.txtReportTitle.Text.ToString
    document.Bookmarks("TestTitle").Range.Text = mycallerPreview.mycallerSelect2.txtReportTitle.Text.ToString
    document.Bookmarks("Writer").Range.Text = list(0).ToString
    document.Bookmarks("Reviewer").Range.Text = list(0).ToString
    document.Bookmarks("Quote").Range.Text = list(1).ToString

澄清您遇到的问题:
您需要从另一个线程访问 ComboBoxProgressBar。您最初使用的 BackgroundWorker 显然吞没了您的跨线程错误,或者您在 Try-Catch 中吞下了它。无论哪种方式,您都将其更改为 Thread 并使 Cross Thread Operation Not Valid 可见。

当您尝试从非自身线程访问用户控件时,会出现此错误 "Cross-Thread Operation Not Valid"。能够修改这些控件很重要,那么我们该怎么做呢?

首先,您修改要异步的方法以接受参数。这应该是一个对象,以便您可以将任务所需的尽可能多的信息传递到异步中。

这是您修改后的方法,将对象作为参数包含在内。

Public Sub buildReport(list_temp As Object)

在您的代码中,您传入了 ComboxBox 文本而不是对 ComboBox 的引用。这就是该部分起作用的原因。然后您传递对 ProgressBar 的引用。当您从异步方法访问进度条时,您没有调用委托。这意味着您必须在 UI 线程上创建一个方法来更新您的控件。然后声明一个将从异步方法调用的委托。

下面是一个按钮示例,该按钮启动更新 TextBox 文本的线程。对于此示例,您需要一个 TextBox 和一个 Button

首先,您需要声明一个委托和该委托的一个实例。您还需要创建修改所需控件的方法,因为您需要将该方法名称传递到委托实例声明中。

Public Delegate Sub SetTextBoxDelegate(Text As String)
    Public SetTextbox_UI_Thread As SetTextBoxDelegate = New SetTextBoxDelegate(AddressOf SetTextBox)

Public Sub SetTextBox(Text As String)
    TextBox1.Text = Text
End Sub

现在这是启动线程的按钮点击:

Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
    Dim t As System.Threading.Thread = New Threading.Thread(AddressOf DoStuff)
    t.Start()
End Sub

如您所见,它正在使用方法 DoStuff() 启动一个线程。这是调用我们的委托(如果需要)来更新文本框的方法。

Public Sub DoStuff()
    System.Threading.Thread.Sleep(3000)
    If TextBox1.InvokeRequired Then
        TextBox1.Invoke(SetTextbox_UI_Thread, "Hello")
    Else
        TextBox1.Text = "Hello"
    End If
End Sub

请注意,我首先检查了 InvokeRequired = True,因为您可以从 UI 线程调用此方法,这样您就可以像往常一样访问控件。