首页 > 解决方案 > 从创建它的线程以外的线程访问的控件

问题描述

我想根据网络更改事件更新我的表单(VB.NET)上的一些标签。在一个事件中它工作得很好,但另一个引发了这个错误:

跨线程操作无效:控件“lblHostnameT”从创建它的线程以外的线程访问。

我正在编写一个用于网络诊断的小型应用程序。为了通知用户网络更改,我正在使用 NetworkChange 类。

在表单加载时,会创建事件处理程序:

AddHandler NetworkChange.NetworkAvailabilityChanged, AddressOf NetworkAvailabilityChanged
AddHandler NetworkChange.NetworkAddressChanged, AddressOf NetworkAddressChanged

这些是事件处理程序:

Private Sub NetworkAvailabilityChanged(sender As Object, e As NetworkAvailabilityEventArgs)
    ResetStats()
    niTray.ShowBalloonTip(5000)
End Sub

Private Sub NetworkAddressChanged(sender As Object, e As EventArgs)
    ResetStats()
    niTray.ShowBalloonTip(5000)
End Sub

这是 ResetStats:

Private Sub ResetStats()
    lblHostnameT.Text = "[na]"
    lblIPT.Text = "[na]"
    lblGWT.Text = "[na]"
    lblDNST.Text = "[na]"
    lblGWPingT.Text = "[na]"
    lblDNSTestT.Text = "[na]"
    lblDCPingT.Text = "[na]"
    lblExtPingT.Text = "[na]"
    GWPingSent = 0
    GWPingReceived = 0
    GWAverage = 0
    DNSTestDone = 0
    DNSTestOK = 0
    DCPingSent = 0
    DCPingReceived = 0
    DCAverage = 0
    ExtPingSent = 0
    ExtPingReceived = 0
    ExtAverage = 0
    DCList.Clear()
    DCList.Add("192.168.19.113")
    DCList.Add("172.31.0.2")
    DCList.Add("172.31.0.3")
    DCList.Add("172.31.0.4")
    ExtList.Clear()
    ExtList.Add("www.google.com")
    ExtList.Add("www.msn.com")
    ExtList.Add("www.yahoo.com")
    DNSTestList.Clear()
    DNSTestList.Add("www.google.com")
    DNSTestList.Add("www.msn.com")
    DNSTestList.Add("www.yahoo.com")
    DNSTaskList.Clear()
    IPList.Clear()
    GWList.Clear()
    DNSList.Clear()
    lblHostnameT.Text = My.Computer.Name

    Dim nics() As NetworkInterface = NetworkInterface.GetAllNetworkInterfaces
    Dim ipi As IPInterfaceProperties

    For Each nic As NetworkInterface In nics
        If (nic.OperationalStatus = OperationalStatus.Up) AndAlso (nic.NetworkInterfaceType <> NetworkInterfaceType.Loopback) Then
            ipi = nic.GetIPProperties()
            If (ipi IsNot Nothing) AndAlso (ipi.UnicastAddresses IsNot Nothing) AndAlso (ipi.UnicastAddresses.Count > 0) Then
                For Each iip As IPAddressInformation In ipi.UnicastAddresses
                    If iip.Address.AddressFamily = Net.Sockets.AddressFamily.InterNetwork Then
                        IPList.Add(iip.Address.ToString & " (" & nic.Name & ")")
                    End If
                Next
                If (ipi.GatewayAddresses IsNot Nothing) AndAlso (ipi.GatewayAddresses.Count > 0) Then
                    For Each iip As GatewayIPAddressInformation In ipi.GatewayAddresses
                        If iip.Address.AddressFamily = Net.Sockets.AddressFamily.InterNetwork Then GWList.Add(iip.Address.ToString)
                    Next
                End If
                If (ipi.DnsAddresses IsNot Nothing) AndAlso (ipi.DnsAddresses.Count > 0) Then
                    For Each iip As Net.IPAddress In ipi.DnsAddresses
                        If iip.AddressFamily = Net.Sockets.AddressFamily.InterNetwork Then DNSList.Add(iip.ToString)
                    Next
                End If
            End If
        End If
    Next
    lblIPT.Text = String.Join(", ", IPList.ToArray)
    niTray.Text = "NetInfo" & vbCrLf & lblHostnameT.Text & vbCrLf & lblIPT.Text
    lblGWT.Text = String.Join(", ", GWList.ToArray)
    If GWList.Count > 1 Then
        lblGWT.ForeColor = Color.Red
    Else
        lblGWT.ForeColor = Drawing.SystemColors.ControlText
    End If
    lblDNST.Text = String.Join(", ", DNSList.ToArray)
End Sub

当我更改一个 NIC 的连接(例如通过切换 WiFi SSID)时,NetworkAvailabilityChanged 被触发并且 UI 更新正常。但是,当我进行更改导致 NetworkAddressChanged 调用(例如,通过将以太网电缆插入笔记本电脑)时,应用程序在 ResetStats 的第一行因错误而崩溃。这里有什么问题?

标签: .netvb.netmultithreading

解决方案


UI 控件具有线程亲和性;您只能从 UI 线程中触摸它们。您的网络事件并非来自 UI 线程。解决方案是将 UI 工作反弹到 UI 线程。在 C# 中,这将类似于:

// not shown prep work to figure out what to do
// string newLabel = ...
this.Invoke((MethodInvoker) delegate {
    // now we're on the UI thread
    someControl.Text = newLabel;
    // etc
});

抱歉,我的 VB 不够强大,无法翻译。

如果有疑问,您可以将整个事情反弹到 UI 线程,但在工作线程上进行预处理通常是有意义的。但:

private void NetworkAvailabilityChanged(object sender, NetworkAvailabilityEventArgs args)
{
    if (InvokeRequired)
    {
        Invoke((MethodInvoker)delegate { // jump to UI thread
            NetworkAvailabilityChanged(sender, args);
        });
    }
    else
    {
        // the real code
    }
}

推荐阅读