c#无响应的第二个UI线程

本文关键字:UI 线程 第二个 响应 | 更新日期: 2023-09-27 18:19:03

我知道这是我之前问过的一个问题的转发…

c#需要多个UI线程,但却得到交叉引用错误

…但是我的后续问题没有得到回答,所以我再次发帖寻求新问题的帮助。我在这里重复一下介绍。谢谢你的纵容。

在c#、线程和表单方面我还是个新手。我写了一个小的数据采集程序。它有两个线程:一个传感器轮询/日志线程和一个可以绘制传感器数据的主UI线程。当用户单击"开始记录"按钮时,它会不断地轮询传感器(通过虚拟COM端口),将响应写入文件,并使用一些基本轮询统计数据(每秒轮询多少次)更新主表单。如果用户单击"monitor"按钮,它将打开一个图表表单,轮询线程开始调用一个方法,该方法将传感器值添加到图表中。该程序运行良好,但我发现,如果我有多个图表打开(以便我可以实时查看多个传感器),图表更新变得零星或停止,只有焦点的窗口更新顺利。(通信端口只有56k波特,所以它不像轮询被数据淹没。)

所以我有了一个"聪明"的想法来制作图表线程,认为这将提供多个UI循环(所以我可以与每个图表交互),并将在多个图表形式上生成漂亮的平滑图表。下面是简化的代码;例如,在这里,图表线程是由轮询线程启动的,而不是当用户点击"monitor"按钮时。

问题是委托永远不会被执行。主表单上的统计数据正在很好地更新,显示了图表表单,但没有响应,当我将鼠标移到它时,我得到了"等待"光标。建议不胜感激。谢谢。

namespace WindowsFormsApplication2
{
    public partial class Main_Form : Form
    {
        delegate void UpdateUIStatsDelegate(string update);
        UpdateUIStatsDelegate update_stats_delegate;
        static BackgroundWorker polling_thread = new BackgroundWorker();
        static BackgroundWorker charting_thread = new BackgroundWorker();
        public static Chart_Form chart_form;
        public Main_Form()
        {
            Thread.CurrentThread.Name = "main";
            update_stats_delegate = new UpdateUIStatsDelegate(update_stats);
            polling_thread.DoWork += polling_thread_DoWork;
            charting_thread.DoWork += charting_thread_start;
        }
        private void start_polling_Click(object sender, EventArgs e)
        {
            // start polling thread
            polling_thread.RunWorkerAsync();
            // start charting plotting thread
            charting_thread.RunWorkerAsync();
        }
        private void polling_thread_DoWork(object sender, DoWorkEventArgs e)
        {
            string sensor_values;
            Thread.CurrentThread.Name = "polling";
           while (true)
            {
                sensor_values = poll_the_sensors_and_collect_the_responses();
                chart_form.BeginInvoke(chart_form.update_chart_delegate, new object[] { sensor_values });
                pps = compute_polling_performance();
                BeginInvoke(update_stats_delegate, new object[] { pps.ToString("00") });
            }
        }
        private string poll_the_sensors_and_collect_the_responses()
        {
            send_command_to_sensor(sensor_id, command_to_return_current_readings);
            return read_sensor_response(sensor_id);
        }
        private void update_stats(string stat)
        {
            pollings_per_second.Text = stat;
        }
        private void charting_thread_start(object sender, DoWorkEventArgs e)
        {
            Thread.CurrentThread.Name = "charting";
            chart_form = new Chart_Form();
            chart_form.Show();
            while (charting_is_active) { }
        }
    }
    public partial class Chart_Form : Form
    {
        public delegate void UpdateChartDelegate(string sensor_values);
        public UpdateChartDelegate update_chart_delegate;
        public Chart_Form()
        {
            update_chart_delegate = new UpdateChartDelegate(update_chart);
            this.Text = "a realtime plot of sensor values";
        }
        private void update_chart(string sensor_values)
        {
            int x = extract_x_value(sensor_values);
            int y = extract_y_value(sensor_values);
            chart1.Series[X_AXIS].Points.AddY(x);
            chart1.Series[Y_AXIS].Points.AddY(y);
        }
    }
}

c#无响应的第二个UI线程

问题出在你的第二个UI线程中。你不能在UI线程中设置一个无限循环并期望它工作:

    while (charting_is_active) { }
UI线程需要运行windows消息队列。我的建议是只在初始UI线程中创建这两个表单。但是,如果您仍然想使用两个线程的方法,我认为您应该这样做:
private void charting_thread_start(object sender, DoWorkEventArgs e)
{
    Thread.CurrentThread.Name = "charting";
    Chart_Form chart_form = new Chart_Form();
    Application.Run(chart_form); 
}

要跟踪dotTrace数据:请仔细查看这些数字。在8秒内调用OnPaint 138次(58毫秒绘制图表)。还要注意,你已经调用了2630次BeginInvoke !update_logging_stats被处理了超过2000次-你的轮询线程似乎运行得太快了。它将工作馈送到UI线程的速度比你的眼睛看到或显示器甚至渲染的速度都快。

因为你每次更新图表时调用update_logging_stats一次,这意味着你的Windows消息队列已经积累了大量的绘画消息积压,无法跟上它们(这导致你的UI线程阻塞)。你只是给了它太多的工作要做(远远超过必要的)。当它忙于绘制图表时,又有20条消息进来重新绘制图表。最终,它试图服务队列并锁定。

你可以尝试在图表上添加秒表并测量你的需求-每隔200毫秒左右发送一次更新:

private void polling_thread_DoWork(object sender, DoWorkEventArgs e)
{
    string sensor_values;
    Thread.CurrentThread.Name = "polling";
    Stopwatch spw = new Stopwatch();
    spw.Restart();
    while (true)
    {
        sensor_values = poll_the_sensors_and_collect_the_responses();
        if (spw.ElapsedMilliseconds > 200)
        {
            chart_form.BeginInvoke(chart_form.update_chart_delegate,
                                                 new object[] { sensor_values });
            spw.Restart();
        }
        pps = compute_polling_performance();
        BeginInvoke(update_stats_delegate, new object[] {pps.ToString("00")});
    }
}

你仍然可以保留所有的数据,当然,如果你真的需要它与这样的分辨率-做一些其他的sensor_values当你不添加到图表(将它们保存到数组,文件等)。您甚至可以考虑在200毫秒左右的时间跨度内收集一些数据点,然后立即发送一组点进行绘图(而不是试图每秒重新绘制整个集合100次)—如果您确实以那种速度积累数据的话。