【问题标题】:vb.NET You are not allowed to perform an operation across different threadsvb.NET 不允许跨不同线程执行操作
【发布时间】:2021-04-01 13:27:08
【问题描述】:

我正在运行代码以从 pdf 文件中读取文本,然后创建一个 WordCloud。为了通知用户该过程正在进行中,我将BackgroundWorker 添加到我的表单中,显示image 表示正在加载。跨不同线程操作时出现错误。

Private Sub ButtonCreate_Click(sender As Object, e As EventArgs) Handles ButtonCreate.Click

        bTextEmpty = False
        ListView1.Items.Clear()
        ResultPictureBox.Image = Nothing

        If ListBox1.SelectedIndex > -1 Then
            For Each Item As Object In ListBox1.SelectedItems
                Dim ItemSelected = CType(Item("Path"), String)
                Dim myTempFile As Boolean = File.Exists(ItemSelected + "\Words.txt")
                If myTempFile = False Then
                    'when we load the form we first call the code to count words in all files in a directory
                    'lets check if the folder exists
                    If (Not System.IO.Directory.Exists(ItemSelected)) Then
                        Dim unused = MsgBox("The archive " + ItemSelected.Substring(ItemSelected.Length - 5, Length) + " was not found",, Title)
                        Exit Sub
                    Else
                        Call CreateWordList(ItemSelected)
                    End If
                End If
                'if the words file is empty we cant create a cloud so exit the sub
                If bTextEmpty = True Then Exit Sub
                'then we fill the wordcloud and Listview from the created textfile
                Call CreateMyCloud(ItemSelected + "\Words.txt")
            Next
        Else
            Dim unused = MsgBox("You have to choose an Archive to create the Word Cloud",, Title)
        End If
        Size = New Drawing.Size(1400, 800)

    End Sub

我把上面的代码放在一个 Private Sub 中,并从我的BackgroundWorker 中调用它。错误发生在这一行:If ListBox1.SelectedIndex > -1 Then

在尝试了建议的代码后,我再次遇到同样的错误:

Public Sub CreateMyCloud(ByVal sourcePDF As String)

        Dim WordsFreqList As New List(Of WordsFrequencies)
        For Each line As String In File.ReadLines(sourcePDF)
            Dim splitText As String() = line.Split(","c)
            If splitText IsNot Nothing AndAlso splitText.Length = 2 Then
                Dim wordFrq As New WordsFrequencies
                Dim freq As Integer
                wordFrq.Word = splitText(0)
                wordFrq.Frequency = If(Integer.TryParse(splitText(1), freq), freq, 0)
                WordsFreqList.Add(wordFrq)
            End If
        Next
        If WordsFreqList.Count > 0 Then
            ' Order the list based on the Frequency
            WordsFreqList = WordsFreqList.OrderByDescending(Function(w) w.Frequency).ToList
            ' Add the sorted items to the listview
            WordsFreqList.ForEach(Sub(wf)
                            error ->  ListView1.Items.Add(New ListViewItem(New String() {wf.Word, wf.Frequency.ToString}, 0))
                                  End Sub)
        End If

        Dim wc As WordCloudGen = New WordCloudGen(600, 400)
        Dim i As Image = wc.Draw(WordsFreqList.Select(Function(wf) wf.Word).ToList, WordsFreqList.Select(Function(wf) wf.Frequency).ToList)
        ResultPictureBox.Image = i


    End Sub

【问题讨论】:

  • 将代码主体移动到另一个方法中。如果没有选择任何项目,那么您根本不启动工作人员。
  • 一般来说,从 UI 线程(按钮单击处理程序)中,您应该将要处理的项目从 ListBox 收集到 List(Of String),然后将其传递给 RunWorkerAsync()。这可以通过DoWorkEventArgs 参数在DoWork() 处理程序中检索,如e.Argument
  • 当你想更新 UI 时,使用ReportProgress() 方法,它会触发ProgressChanged() 事件。从该事件中更新 UI 是安全的。同样,当 BackgroundWorker 完成后,RunWorkerCompleted() 事件将触发,您可以在其中对 UI 进行其他更新。
  • @HansPassant 你能告诉我那会是什么样子吗?

标签: vb.net backgroundworker word-cloud


【解决方案1】:

应该看起来更像:

Private Sub ButtonCreate_Click(sender As Object, e As EventArgs) Handles ButtonCreate.Click
    If ListBox1.SelectedItems.Count > 0 Then
        Dim data As New List(Of Object)
        For Each Item As Object In ListBox1.SelectedItems
            data.Add(Item)
        Next

        bTextEmpty = False
        ListView1.Items.Clear()
        ResultPictureBox.Image = Nothing

        BackgroundWorker1.RunWorkerAsync(data)
    Else
        MessageBox.Show("You have to choose an Archive to create the Word Cloud",, Title)
    End If
End Sub

Private Sub BackgroundWorker1_DoWork(sender As Object, e As System.ComponentModel.DoWorkEventArgs) Handles BackgroundWorker1.DoWork
    Dim data As List(Of Object) = DirectCast(e.Argument, List(Of Object))
    For Each Item As Object In data
        Dim ItemSelected = CType(Item("Path"), String)
        Dim myTempFile As Boolean = File.Exists(ItemSelected + "\Words.txt")
        If myTempFile = False Then
            'when we load the form we first call the code to count words in all files in a directory
            'lets check if the folder exists
            If (Not System.IO.Directory.Exists(ItemSelected)) Then
                MessageBox.Show("The archive " + ItemSelected.Substring(ItemSelected.Length - 5, Length) + " was not found",, Title)
                Exit Sub
            Else
                Call CreateWordList(ItemSelected)
            End If
        End If
        'if the words file is empty we cant create a cloud so exit the sub
        If bTextEmpty = True Then Exit Sub
        'then we fill the wordcloud and Listview from the created textfile
        Call CreateMyCloud(ItemSelected + "\Words.txt")
    Next
End Sub

Private Sub BackgroundWorker1_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BackgroundWorker1.RunWorkerCompleted
    Size = New Drawing.Size(1400, 800)
End Sub

【讨论】:

  • 所以这基本上是因为我调用 ListBox1 来查看选择了哪些项目?
  • 正确。在DoWork() 中,您不应直接访问任何 UI 控件。可以这样做,但您必须使用 Invoke() 才能使调用线程安全。
  • 我读到 Invoke() 并不是一个真正安全的解决方案。
  • Invoke() 正确使用是绝对安全的。您将看到的大多数抱怨仅仅是因为它是一种较旧的做事方式,并且它“不应该”与其他方法(例如 BackgroundWorker/Task)混合使用。不过,从技术上讲,这是可以做到的。
  • 我很乐意用 BackgroundWorker 尝试您的解决方案
【解决方案2】:

要修复您的第二个已编辑帖子中的错误:

WordsFreqList.ForEach(Sub(wf)
                          ListView1.Invoke(Sub()
                                               ListView1.Items.Add(New ListViewItem(New String() {wf.Word, wf.Frequency.ToString}, 0))
                                           End Sub)
                      End Sub)

【讨论】:

    【解决方案3】:

    您不能简单地访问当前线程之外的控件。您首先需要在目标控件上调用Invoke 方法,然后传递一个委托,其中包含旨在修改当前线程之外的控件的指令。有关如何执行此操作,请参阅 MSDN 上的这篇文章:https://docs.microsoft.com/en-us/dotnet/desktop/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls?view=netframeworkdesktop-4.8

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-03
      • 1970-01-01
      • 2018-09-07
      • 1970-01-01
      相关资源
      最近更新 更多