【问题标题】:C# SerialPort datareceived and WPF UI freezingC# SerialPort datareceived 和 WPF UI 冻结
【发布时间】:2018-12-24 22:24:25
【问题描述】:

我的目标是监视自定义系统上的 gsm 模块 RX/TX 通信。 为此,我在我的计算机上使用 2 个串行端口和一个 WPF c# 应用程序。 在特定命令下,gsm 模块与系统之间的通信可以更改为 9600bds、57600bds 或 125000bds。 用 9600 和 57600 没问题。 但是当 com 速度为 125000 时,我的 UI 冻结了。 我已经阅读了很多关于使用 Action、Delegate、调度程序冻结 UI 的帖子,但它不起作用,我不明白如何解决我的问题。

这里,我现在在做什么:

  1. 我有 2 个来自 System.IO.Ports 的串行端口;
  2. 2个串口使用相同的SerialDataReceivedEventHandler
  3. 在SerialDataReceivedEventHandler 中,我使用serialPort 名称、接收的字节数和事件发生的时间构建了一个“QueueElement”的自定义类实例。此 QueueElement 是从
  4. 推送到队列中的
  5. 每 100 毫秒,一个计时器滴答事件读取队列以将其中的所有项目显示到 UI 列表框。

这里有一些代码来解释我是如何进行的:

// queue to store data read on serial port before display
Queue<QueueElement> queueList;

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    initPorts();
    queueList = new Queue<QueueElement>();
    QueueConsumerProcessRunning = false;
    queueConsumer();
}

private void initPorts()
{                  
    mySerialPort1.DataReceived += new SerialDataReceivedEventHandler(SerialPort_DataReceived);         
    mySerialPort2.DataReceived += new SerialDataReceivedEventHandler(SerialPort_DataReceived);
    // ...
}

// data on serial port
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    Monitor.Enter(queueList);
    try
    {
        // create a queue element and add it to the queue
        // ...
        queueList.Enqueue(queueSingleElement);
    }
    finally
    {
        Monitor.Exit(queueList);
    }
}


private void queueConsumer()
{
    Thread backgroundThread = new Thread(
        new ThreadStart(() =>
        {
            while (true) // always alive
            {
                if ((QueueConsumerProcessRunning == false) && (queueList.Count > 0))
                {

                    Dispatcher.Invoke(() => { QueueCount.Text = queueList.Count.ToString(); });

                    QueueConsumerProcessRunning = true;
                    QueueElement Qelem;// = new QueueElement();

                    Monitor.Enter(queueList);
                    try
                    {
                        // get alodest element
                        Qelem = queueList.Peek();

                        // remove oldest element
                        queueList.Dequeue();
                    }
                    finally
                    {
                        Monitor.Exit(queueList);
                    }

                    // call method to display data    
                    AddDataMethod(buildSerialElement(Qelem.SerialPortName, Qelem.ReadBytes, Qelem.EventHour));

                    QueueConsumerProcessRunning = false;
                }
                Thread.Sleep(100);
            }
        }
    )); // backgroundThread
    backgroundThread.IsBackground = true;
    backgroundThread.Start();            
}


// display received data
// when incoming data is from the same serial port of last received data: add data on last written row except if it's a carriage return
private void AddDataMethod(SerialElement elem)
{
    if (!Dispatcher.CheckAccess()) // CheckAccess returns true if you're on the dispatcher thread       this.ListBoxSpy.Disp...
   {
        Dispatcher.Invoke(new AddDataDelegate(AddDataMethod), elem);
        return;
    }

    // update List<SerialElement>
    // ...
    // and call the function to update item displayed on UI
    refreshDisplay();
}

// refresh display
private void refreshDisplay()
{
    // create a list of ListBoxRowItem
    // ...
    // and display the list on ListBox UI

    ListBoxSpy.ItemsSource = myListBoxRows;   
}

ListBox 的 xaml 部分:

<ListBox Name="ListBoxSpy" HorizontalAlignment="Stretch" Margin="0,100,0,0" VerticalAlignment="Stretch" FontFamily="Courier New" >
    <ListBox.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding Message}" Foreground="{Binding MessageColor}" VirtualizingPanel.IsVirtualizing="True" VirtualizingPanel.VirtualizationMode="Recycling" />                
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

我使用 Visual Studio Community 2017。

【问题讨论】:

  • 尝试使用任务在不同的线程中执行耗时的操作。
  • @Babbillumpa:根据你的建议,我尝试了 Thread t = new Thread(new threadStart(() =&gt; { /*consumer here*/ })); t.Start();Task t = Task.Run(() =&gt; { /*consumer here*/}); t.Wait();,但我的 UI 再次冻结。
  • 请不要发布所有您拥有的代码。为了帮助您解决问题,我们需要一个minimal reproducible example,强调minimal

标签: c# wpf user-interface serial-port freeze


【解决方案1】:

我认为您的队列存在竞争条件。我假设 QueueConsumerTimedEvent 正在 UI 线程上运行。 while 循环可能会永远运行,因为它正在检查 queueList.Count > 0 但在处理顶部项目时,DataReceived 线程正在将更多项目添加到队列中。所以它永远不会达到零,因此会阻塞 UI 线程。

您至少应该移动 Monitor.Enter 以适应,但您会冒丢失或延迟传入数据的风险。可能应该先出队,以便您可以快速释放监视器,然后处理出队的项目。

【讨论】:

  • 根据您的评论,我已经更新了代码。我的队列使用者现在在后台线程中(我希望不在 UI 线程中......)并且我已经删除了 queueList.Count &gt; 0 条件并移动了 Monitor.Enter。可悲的是,没有改变。我尝试在Dispatcher.Invoke 中添加DispatcherPriority.SystemIdle。这样,UI 保持响应,但显示的数据延迟很大,并且在此过程中队列列表中填充了大约 2700 个项目。
猜你喜欢
  • 1970-01-01
  • 2015-01-16
  • 1970-01-01
  • 2016-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多