【问题标题】:Update Text Box Properly when Cross-threading in Visual Basic (VS 2012 V11)在 Visual Basic 中跨线程时正确更新文本框(V​​S 2012 V11)
【发布时间】:2013-05-13 18:51:42
【问题描述】:
  • 简而言之,这是我的问题:如何使用 BackGroundWorker(或 InvokeRequired 方法)进行线程安全调用以将文本附加到文本框?

  • 这是我的问题,其中包含很多细节和背景:我一直在开发一个程序,该程序将文件从一个位置复制到另一个位置以进行备份。我设置了一个选项,当使用 FileSysteWatcher 修改文件时将保存文件。代码如下:

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Me.Load
    
        Dim directoryPath As String = Path.GetDirectoryName(TextBox1.Text)
        Dim varFileSystemWatcher As New FileSystemWatcher()
        varFileSystemWatcher.Path = directoryPath
    
        varFileSystemWatcher.NotifyFilter = (NotifyFilters.LastWrite)
        varFileSystemWatcher.Filter = Path.GetFileName(TextBox1.Text)
        AddHandler varFileSystemWatcher.Changed, AddressOf OnChanged
        varFileSystemWatcher.EnableRaisingEvents = True
    
    End Sub
    
    Private Sub OnChanged(source As Object, ByVal e As FileSystemEventArgs)
    
        My.Computer.FileSystem.CopyFile(e.FullPath, TextBox2.Text & "\" & e.Name, True)
        TextBox3.ApendText("[New Text]") ' This line causes an error
    End Sub
    

除了更新 textbox3 中的文本外,该代码工作正常。当指定的文件被修改时,我需要更新文本框。这一点很重要,这样用户就可以知道程序正在运行并获得程序操作的完整日志。

导致的错误是:

跨线程操作无效:控件“TextBox3”从 线程不是创建它的线程。

我假设“AddHandler varFileSystemWatcher.Changed, AddressOf OnChanged”创建了一个新线程。此外,在“OnChange Sub”中更新“textbox3”(以我的方式)不是线程安全的方式。所以我做了一些研究,发现了这篇文章:

How to: Make Thread-Safe Calls to Windows Forms Controls

文章解释说,可以使用 InvokeRequired 或 BackgroundWorker 进行线程安全调用。我想使用 BackgroundWorker 进行线程安全调用(如果 InvokeRequired 更有效,那么我将使用它),但是所提供示例的这一部分让我感到困惑:

' Do I need these imports to use the BackgroundWorker or InvokeRequired?
Imports System
Imports System.ComponentModel
Imports System.Threading
Imports System.Windows.Forms

Public Class Form1
   Inherits Form ' Do I need this for what I am trying to do?

   ' This delegate enables asynchronous calls for setting
   ' the text property on a TextBox control.
   Delegate Sub SetTextCallback([text] As String)

   ' This thread is used to demonstrate both thread-safe and
   ' unsafe ways to call a Windows Forms control.
   Private demoThread As Thread = Nothing

   ' This BackgroundWorker is used to demonstrate the 
   ' preferred way of performing asynchronous operations.
   Private WithEvents backgroundWorker1 As BackgroundWorker

   Private textBox1 As TextBox
   Private WithEvents setTextUnsafeBtn As Button
   Private WithEvents setTextSafeBtn As Button
   Private WithEvents setTextBackgroundWorkerBtn As Button

   ' What is this part of the code for and do I need it?
   Private components As System.ComponentModel.IContainer = Nothing

   ' Again, What is this part of the code for and do I need it?
   Public Sub New()
      InitializeComponent()
    End Sub

   ' And again, What is this part of the code for and do I need it?
   Protected Overrides Sub Dispose(disposing As Boolean)
      If disposing AndAlso (components IsNot Nothing) Then
         components.Dispose()
      End If
      MyBase.Dispose(disposing)
    End Sub

上述代码的许多部分让我感到困惑。它有什么作用?我需要这段代码的哪些部分来使用 BackgroundWorker? InvokeRequired 方法的哪些部分?同样,如何使用 BackGroundWorker(或 InvokeRequired 方法)进行线程安全调用以将文本附加到文本框?(很高兴能够解释上面的代码,但我真正需要的是如何以线程安全的方式更新文本框的文本的一个示例。)

【问题讨论】:

  • 对于一个简单的修复只需添加varFileSystemWatcher.SynchronizingObject = Me

标签: vb.net multithreading textbox backgroundworker


【解决方案1】:

变化:

Private Sub OnChanged(source As Object, ByVal e As FileSystemEventArgs)
    My.Computer.FileSystem.CopyFile(e.FullPath, TextBox2.Text & "\" & e.Name, True)
    TextBox3.ApendText("[New Text]") ' This line causes an error
End Sub

收件人:

Private Sub OnChanged(source As Object, ByVal e As FileSystemEventArgs)
    My.Computer.FileSystem.CopyFile(e.FullPath, TextBox2.Text & "\" & e.Name, True)
    AppendTextBox(TextBox3, "[New Text]")
End Sub

Private Delegate Sub AppendTextBoxDelegate(ByVal TB As TextBox, ByVal txt As String)

Private Sub AppendTextBox(ByVal TB As TextBox, ByVal txt As String)
    If TB.InvokeRequired Then
        TB.Invoke(New AppendTextBoxDelegate(AddressOf AppendTextBox), New Object() {TB, txt})
    Else
        TB.AppendText(txt)
    End If
End Sub

这是一个使用匿名委托的更新简化版本:

Private Sub OnChanged(source As Object, ByVal e As FileSystemEventArgs)
    Dim filename As String = System.IO.Path.Combine(TextBox2.Text, e.Name)
    Try
        My.Computer.FileSystem.CopyFile(e.FullPath, filename, True)

        TextBox3.Invoke(Sub()
                            TextBox3.AppendText("[New Text]")
                        End Sub)

    Catch ex As Exception
        Dim msg As String = "Source: " & e.FullPath & Environment.NewLine &
            "Destination: " & filename & Environment.NewLine & Environment.NewLine &
            "Exception: " & ex.ToString()
        MessageBox.Show(msg, "Error Copying File")
    End Try
End Sub

【讨论】:

  • *您确实应该将TextBox2.Text & "\" & e.Name 存储在类级别变量中,而不是直接访问它。要么,要么也使用 Invoke()/delegate 模型检索它。
  • 上述方法有效,但文本框将文本翻倍。文本框没有添加“[New Text]”,而是以“[New Text][New Text]”结尾。代码是否触发了两次 OnChanged sub?
  • 这很可能是 FileSystemWatcher 的症状(功能?)。众所周知,您可以为单个文件操作触发多个事件。
  • 我已经使用了上面的我也得到它'追加'而不是替换文本。有什么想法吗?我正在使用 TimeElapsed 事件。
  • 使用委托子写入主线程控件的最佳示例。谢谢你像冠军一样工作!
【解决方案2】:

您也可以使用 SynchronizationContext。小心从构造函数中获取对它的引用。 CodeProject.com 上有一篇很棒的文章:http://www.codeproject.com/Articles/14265/The-NET-Framework-s-New-SynchronizationContext-Cla

Imports System.IO
Imports System.Threading

Public Class Form1
    Private m_SyncContext As System.Threading.SynchronizationContext
    Private m_DestinationPath As String

    Public Sub New()

        ' This call is required by the designer.
        InitializeComponent()

        ' Add any initialization after the InitializeComponent() call.
        Me.m_SyncContext = System.Threading.SynchronizationContext.Current
    End Sub

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Me.m_DestinationPath = Me.TextBox2.Text
        Dim directoryPath As String = Path.GetDirectoryName(TextBox1.Text)
        Dim varFileSystemWatcher As New FileSystemWatcher()
        varFileSystemWatcher.Path = directoryPath

        varFileSystemWatcher.NotifyFilter = (NotifyFilters.LastWrite)
        varFileSystemWatcher.Filter = Path.GetFileName(TextBox1.Text)
        AddHandler varFileSystemWatcher.Changed, AddressOf OnChanged
        varFileSystemWatcher.EnableRaisingEvents = True
    End Sub

    Private Sub OnChanged(source As Object, ByVal e As FileSystemEventArgs)
        My.Computer.FileSystem.CopyFile(e.FullPath, Me.m_DestinationPath & "\" & e.Name, True)
        Me.m_SyncContext.Post(AddressOf UpdateTextBox, "[New Text]")
    End Sub

    Private Sub UpdateTextBox(param As Object)
        If (TypeOf (param) Is String) Then
            Me.TextBox3.AppendText(CStr(param))
        End If
    End Sub
End Class

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-05-02
    • 1970-01-01
    • 1970-01-01
    • 2013-05-16
    • 1970-01-01
    • 2013-05-01
    • 1970-01-01
    相关资源
    最近更新 更多