سؤال

i know this is a repost of a previous question i asked...

c# wanting multiple ui threads but getting cross-reference errors instead

...but my followup question wasn't answered so i'm posting again for help on the new problem. i'll repeat the intro here. thanks for you indulgence.

i'm still very new at c#, threads and forms. i've written a small data acquistion program. it has two threads: a sensor polling/logging thread and a main UI thread which can chart the sensor data. when the user clicks the "start-logging" button, it continuously polls the sensors (over a virtual COM port), writes the response to a file, updates the main form with some basic polling stats (how many pollings per second). if the user has clicked a "monitor" button, it opens a charting form and the polling thread begininvokes a method that adds the sensors values to the chart. the program works well but i found that if i have multiple charts open (so that i can view multiple sensors in realtime), the chart updates become sporadic or stop and only the window with the focus updates smoothly. (the comm port is only 56kbaud so it's not like the polling is being swamped with data.)

so i got the "bright" idea to make charting threads, thinking this would provide multiple UI loops (so i could interact with each chart) and would produce nice smooth charting on multiple chart forms. below is simplified code; e.g. here, the charting thread is started with the polling thread instead of when the user clicks the "monitor" button.

the problem is that the delegate is never performed. the stats on the main form is being updated nicely, the charting form is displayed, but is unresponsive and i get the "wait" cursor when i mouse it. advice greatly appreciated. thanks.

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);
        }
    }
}
هل كانت مفيدة؟

المحلول 2

To follow up on your dotTrace data : take a close look at those numbers. 138 calls to OnPaint over ~8 seconds (58ms to draw the chart). Also note that you've called BeginInvoke 2630 times! update_logging_stats was handled over 2000 times - your polling thread seems to be running way too fast. It's feeding work to the UI thread faster than your eyes can see or the display can even render.

Since you call update_logging_stats once for every time you've updated the chart, this means that your Windows message queue has accumulated an enormous backlog of paint messages and cannot keep up with them all (this is causing your UI thread to choke). You're simply giving it too much work to do (way more than is necessary). While it is busy drawing the chart, twenty more messages have come in to paint it again. Eventually it ends up trying to service the queue and locks up.

What you may try is something like adding a stopwatch and metering your demands on the chart - only send it an update every 200ms or so :

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")});
    }
}

You can still keep all of the data, of course, if you really need it with such resolution - do something else with sensor_values when you are not adding them to the chart (save them to an array, file, etc). You might even consider collecting a number of data points over a span of 200ms or so and then sending a cluster of points to plot at once (rather than trying to replot the whole set a hundred times per second) - again if you are really accumulating data at that speed.

نصائح أخرى

The problem is in your second UI thread. You can not put a infinite loop in a UI thread an expect it to work:

    while (charting_is_active) { }

The UI thread needs to run the windows message queue. My advice is that you create both forms only in the initial UI thread. But if you still want to go with the two threads approach, I think you should do something like:

private void charting_thread_start(object sender, DoWorkEventArgs e)
{
    Thread.CurrentThread.Name = "charting";
    Chart_Form chart_form = new Chart_Form();
    Application.Run(chart_form); 
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top