【问题标题】:Cross-thread operation not valid: Control 'listBox1' accessed from a thread other than the thread it was created on跨线程操作无效:控件“listBox1”从创建它的线程以外的线程访问
【发布时间】:2018-10-04 15:46:16
【问题描述】:

我想将项目添加到 Modbus_request_event 中的 listBox1。我已经完成了为这个问题提供的解决方案并使用 MethodInvoker Delegate 修改了代码,但它仍然不会将项目添加到 listBox1。 这是我的代码

     public void Modbus_Request_Event(object sender, ModbusSlaveRequestEventArgs e)
        {
            //disassemble packet from master
            byte fc = e.Message.FunctionCode;
            byte[] data = e.Message.MessageFrame;
            byte[] byteStartAddress = new byte[] { data[3], data[2] };
            byte[] byteNum = new byte[] { data[5], data[4] };
            short StartAddress = BitConverter.ToInt16(byteStartAddress, 0);
            short NumOfPoint = BitConverter.ToInt16(byteNum, 0);


            string fc1 = Convert.ToString(fc);
           string StartAddress1 = Convert.ToString(StartAddress);
           string NumOfPoints1 = Convert.ToString(NumOfPoint);

           /*Adds the items to listBox1*/
            Invoke(new MethodInvoker(delegate () { listBox1.Items.Add(fc1); listBox1.Items.Add(StartAddress1); listBox1.Items.Add(NumOfPoints1); }));
//it runs infinitely not able to add to listbox//


        }

谁能帮我解决这个问题?

【问题讨论】:

  • 这不是重复的.. 仔细阅读(检查代码)... Modbus_Request_Event 是您的表单方法?
  • 当用户单击表单中的按钮以接受来自 Modbus Master 的请求时,将调用 Modbus_Request_Event 方法。在此方法中,在从 master 的请求中分解数据包后,我想添加一些这些变量到 listBox1.So 任何人都可以帮我解决这个问题吗?
  • 我添加了一个可能有帮助的答案。
  • @J.vanLangen 它重复的。仅当尝试从另一个线程修改 UI 时才会引发此类异常。不知何故,在某个地方,OP 正试图从不同的线程修改 UI。

标签: c# winforms modbus-tcp


【解决方案1】:

首先,重复的重复的。您不能从另一个线程修改 UI。在 .NET 4.5 之前,人们会在 control 上使用 InvokeBeginInvoke 将委托编组到 UI 线程并在那里运行。问题的代码虽然调用了Invoke(),但实际上是在它当前所在的线程上运行委托。

简而言之:

Invoke(new MethodInvoker(delegate () { listBox1.Items.Add(fc1); listBox1.Items.Add(StartAddress1); listBox1.Items.Add(NumOfPoints1); }));

就线程而言,本质上与此相同:

listBox1.Items.Add(fc1); listBox1.Items.Add(StartAddress1); listBox1.Items.Add(NumOfPoints1);

使用 .NET 4.5 及更高版本,您可以使用 Progress 报告任何线程或任务的进度,如 Enabling Progress and Cancellation in Async APIs 所示。鉴于最早支持的 .NET 版本是 4.5.2,您可以假设该类随处可用。

通过使用Progress<T>IProgress<T> 接口,您可以将事件与用户界面分离,这意味着您可以以任何您想要的方式处理数据,即使是在不同的表单上。您可以将 Modbus 类移动到不同的类或库中,以使其与 UI 分开。

在最简单的情况下,您可以在表单的构造函数中实例化Progress<T> 类,并通过事件处理程序的IProgress<T> 接口调用它,例如:

public class ModbusData
{
    public byte Fc {get; set;}
    public short StartAddress {get; set;}
    public short NumOfPoints {get; set;}
}

public class MyForm : ...
{

    IProgress<ModbusData> _modbusProgress;

    public MyForm()
    {
        __modbusProgress=new Progress<ModbusData>(ReportProgress);
    }

    public void ReportProgress(ModbusData data)
    {
        listBox1.Items.Add(data.fc1.ToString()); 
        listBox1.Items.Add(dta.StartAddress1.ToString()); 
        listBox1.Items.Add(data.NumOfPoints1.ToString()); 
    }

并报告事件的进度,无论它是在哪个线程上引发的:

    public void Modbus_Request_Event(object sender, ModbusSlaveRequestEventArgs e)
    {
        //disassemble packet from master
        byte fc = e.Message.FunctionCode;
        byte[] data = e.Message.MessageFrame;
        byte[] byteStartAddress = new byte[] { data[3], data[2] };
        byte[] byteNum = new byte[] { data[5], data[4] };
        short StartAddress = BitConverter.ToInt16(byteStartAddress, 0);
        short NumOfPoint = BitConverter.ToInt16(byteNum, 0);

        var modData = new ModbusData {
                 Fc = fc,
                 StartAddress = StartAddress,
                 NumOfPoints = NumOfPoint
        };
       _progress.Report(modData);
    }        

如果您决定将 Modbus 类移动到另一个类,您所要做的就是在开始使用它们之前将 IProgress&lt;ModbusData&gt; 实例传递给它们。

例如:

class MyModbusController
{
    IProgress<ModbusData> _modbusProgress;

    public MyModbusController(IProgress<ModbusData> progress)
    {
        _modbusProgress=progress;
    }

    public void Modbus_Request_Event(...)
}

【讨论】:

  • 这意味着如果 modbus 每秒引发 1000 个事件,您的 ui 线程每秒将收到 1000 个回调。 Progress&lt;T&gt; 只是调用同步上下文。 GF ui 响应。
  • @J.vanLangen 这既不是问题也不是真正的问题。有很多方法可以批量更新。事实上,如果使用数据绑定而不是直接修改 UI,那么更新 UI 会简单得多——在引发 NotifyChanged 事件之前稍等片刻
【解决方案2】:

这并不能解决您的问题..,因为您的代码看起来没有错,但这可能有助于制定更好的线程和 gui 解决方案。

您正在使用Invoke 来同步 modbus 线程和 gui 线程。这意味着当接收到 modbus 的事件时,您将项目添加到列表框中。当它调用 gui 线程时,您的 modbus 线程被阻塞。因此,您的 modbus 通信速度取决于 gui 组件的速度。尽量避免这种讨厌的块。如果您每秒收到许多 modbus 事件,您的列表框将被重绘淹没,您的应用程序将被限制。

如何解决这个问题?:

您应该将项目添加到列表(concurrentcollection)并使用计时器来更新您的 gui。通过这种方式,您可以确定何时将接收到的事件(批量)添加到列表框中。一个100ms间隔的定时器就够了。

伪示例代码:

public class Data
{
    public string Fc1 {get; set;}
    public string StartAddress1 {get; set;}
    public string NumOfPoints1 {get; set;}
}

ConcurrentQueue<Data> _modbusEvents = new ConcurrentQueue<Data>();


public void Modbus_Request_Event(object sender, ModbusSlaveRequestEventArgs e)
{
    //disassemble packet from master
    byte fc = e.Message.FunctionCode;
    byte[] data = e.Message.MessageFrame;
    byte[] byteStartAddress = new byte[] { data[3], data[2] };
    byte[] byteNum = new byte[] { data[5], data[4] };
    short StartAddress = BitConverter.ToInt16(byteStartAddress, 0);
    short NumOfPoint = BitConverter.ToInt16(byteNum, 0);


    string fc1 = Convert.ToString(fc);
    string StartAddress1 = Convert.ToString(StartAddress);
    string NumOfPoints1 = Convert.ToString(NumOfPoint);

    // add to the concurrentqueue
    _modbusEvents.Add(new Data { Fc1 = fc1, StartAddress1 = StartAddress1, NumOfPoints1 = NumOfPoints1 });

   /*Adds the items to listBox1*/
    //Invoke(new MethodInvoker(delegate () { listBox1.Items.Add(fc1); listBox1.Items.Add(StartAddress1); listBox1.Items.Add(NumOfPoints1); }));
//it runs infinitely not able to add to listbox//

}

private void Timer_Tick(object sender, EventArgs e)
{
    if(_modbusEvents.Count > 0)
    {
        listBox1.BeginUpdate();

        while(_modbusEvents.TryDequeue(out var result))
        {
             listBox1.Items.Add(result.Fc1);
             listBox1.Items.Add(result.StartAddress1);
             listBox1.Items.Add(result.NumOfPoints1);
        }

        listBox1.EndUpdate();
    }
}

【讨论】:

  • 您不需要并发包来更新 UI 线程。实际上,在这种情况下,ConcurrentBag 具有很高的跨线程编组成本。 .NET 本身使用Progress&lt;T&gt; 类实现跨线程进度报告
  • 从未听说过 Progress,我会调查一下。我通常会为它使用一个简单的锁。
  • 锁不报告进度,也不向其他线程发送数据
  • 您为什么认为锁会发送任何数据?这是荒谬的。我会结合上面的解决方案使用锁
  • 您比较了 IProgress 和锁。它们根本不一样。至于这个解决方案,它不应该需要锁。它应该使用 ConcurrentQueue,而不是 ConcurrentBag,因为 ConcurrentBag 使用线程本地存储。它也不应该需要计时器——这里需要计时器,因为并发集合没有可等待的操作或通知,例如 ObservableCollection。如果你想运行一个动作来响应一个新的项目,有 ActionBlock.
【解决方案3】:

尝试使用以下代码:

Invoke((MethodInvoker)delegate 
{
listBox1.Items.Add(fc1);
listBox1.Items.Add(StartAddress1);
listBox1.Items.Add(NumOfPoints1);
});

【讨论】:

    猜你喜欢
    • 2011-01-15
    • 2016-05-21
    • 2016-06-11
    • 2015-05-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多