Windows Main Form slows down processing if I do not click on it

chwaitang

Member
Joined
Apr 4, 2021
Messages
8
Programming Experience
5-10
Hi All.

I have encountered a weird problem as follows:
I am running C# Win Form app build with Visual Studio Community 2019.

The main form will be constantly receiving CAN messages via USB-CAN adaptor using DLL file provided by CAN manufacturer.
I clicked a button to select which particular CAN-ID message I want to process and display. Then a separate sub form opens up
and decodes the parameters in CAN message and then plots in a line chart the received and decoded variables.

This chart plotting in sub form slows down. I found out this happens when I DO NOT CLICK and HOLD on the main form window continuously.
When I do click and hold on the main form window, I can see CAN-ID message count with its timestamps and also raw hex data of CAN packet is
shown on a display log in the main form and is updating and running continuous.

When I let go and do not click on the main form window, the update in main form display log slows down and is only updated once every 20 - 30 plus seconds.
But I can see that the message count is correct as there is some difference between CAN message count between each update shown in display log. This means
the CAN message is still being received in between consecutive updates shown in display log window ion main form.

But in the above case, the chart plot in sub form slows down and there are many missing data points not plotted as sub form is not receiving those data points in between updates.
The timestamp of each data point send to sub form from the main is shown on sub form and it also freezes between updates. But when I click on main form window, both the
display log window in the main and received data interval time stamps in the sub form are running smoothly and continuously.

I have tried what means I know and also googled on this problem with no result.
Please advise.

Best regards
Tang
 
It sounds like the system is assigning less priority to the UI thread at times. This makes a certain amount of sense as, if you're not focused on a particular form, allocating resources to it when there are other threads that would suffer is logical. The part about having to click and hold the form sounds interesting though. That the form has to be active to get top priority makes sense but it also sounds like it has lower priority even when focused if you're not directly interacting with it. This may be something that you can't do anything about - it may just be a Windows thing - but it's hard to test that easily.

As for the missing data, that would almost certainly have something to do with your code specifically but we can't really say much more without seeing the relevant code (and ONLY the relevant code). It may be that you are passing the data on the UI thread when it should be done on a secondary thread but, as I said, it's hard to know for sure without specific details. Unfortunately, the fact that you're interfacing with specific hardware makes it hard to provide something that we can test for ourselves.
 
Sorry, To add on,
The slow down occurs WHEN NONE of the forms are clicked and held on. Clicking and holding onto ANY ONE FORM (either MAIN FOR or ANY OF THE SUB FORM DOING THE CHART PLOT) will cause things to go back to normal (smooth continuous update of message count in the main form and received data timestamps on the sub forms),

Please advise.

Tang
 
Hi,

Here I post the relevant code below:
C#:
private void btn_HS1_Click(object sender, EventArgs e)    // button click to start monitor HS1 CAN-ID msg (on main form)
{
    if (!HS1_diagnose)
    {
        this.btn_HS1.Text = "Stop HS1 log";
        HS1_diagnose = true;

        hs1Data = new Form_HS1mcuData();                        // Form_HS1mcuData class is in Form_HS1mcuData.cs
        //  hs1Data.FormClosing += new FormClosingEventHandler(Form_HS1_Closing);
        hs1Data.FormClosed += new FormClosedEventHandler(Form_HS1_Close);
        hs1Data.Show();                                                         // Initialisation of hs1Data is done in main form
    }
Here is form load event for Form_HS1mcuData:
C#:
public void Form_HS1mcuData_Load(object sender, EventArgs e)    (on HS1 form)
{
    if (!OpenHS1form)
    {
        OpenHS1form = true;
        Close_HS1Form = false;      

        thread_HS1 = new Thread(new ThreadStart(hs1_Diagnostic));    // child thread for HS1 form
        thread_HS1.Name = "HS1 Diagnostic";
        thread_HS1.Priority = ThreadPriority.Highest;
        thread_HS1.Start();
    }
Here is hs1_Diagnostic thread function
C#:
public void hs1_Diagnostic()      // (on HS1 form)
{
    string filePath, resultString;
    string oldChar = "\\";
    string newChar = "\\\\";

    this.BeginInvoke((System.Windows.Forms.MethodInvoker)delegate () {      // Timer to call chart plotting function periodically
        ChartTimer = new System.Windows.Forms.Timer();
        ChartTimer.Tick += new System.EventHandler(ChartTimer_Tick);
        ChartTimer.Interval = 10;
        ChartTimer.Enabled = true;
    });

    while (!Close_HS1Form)    // (on HS1 form)
    {
        this.Invoke((Action)this.ProcessMessage);   // This is main function to decode and parse received CAN msg
    }

    if (Close_HS1Form)
    {
        //while (WIP1) { };
        this.Invoke(new MethodInvoker(() => this.Close()));
        //this.Close();
    }
Here is processing and decoding
C#:
private void ProcessMessage()     // (on HS1 form)
{
    if (HS1_canRead)
    {
        tempByte = HS1_data[1];
        unsignedINT16 = HS1_data[0];
        unsignedINT16 |= (ushort)(tempByte << 8);
        tempDouble = Convert.ToDouble(unsignedINT16);
        Torque_Feedback = tempDouble * 0.0625;
        HS1_Torque.Enqueue(Torque_Feedback);

        tempByte = HS1_data[5];
        signedINT16 = HS1_data[4];
        signedINT16 |= (short)(tempByte << 8);
        dc_Current = signedINT16;
        HS1_CurrentDC.Enqueue(dc_Current);

        tempByte = HS1_data[3];
        signedINT16 = HS1_data[2];
        signedINT16 |= (short)(tempByte << 8);
        motor_Speed = signedINT16;
        HS1_RPM.Enqueue(motor_Speed);

        HS1_Time.Enqueue(HS1_TimeStamp);
        this.Invoke((Action)this.addRecordHS1);    // Here is to save data to file
        HS1_PlotOK = true;
        HS1_canRead = false;
    }
}
Here is Timer Tick to plot chart // (on HS1 form)
C#:
private void ChartTimer_Tick(object sender, System.EventArgs e)   // Timer method
{
    if (!Close_HS1Form)
    {
        if (HS1_PlotOK)
        {
            if(HS1_Time.Count != 0)
            {
                qTorque_Feedback = Convert.ToDouble(HS1_Torque.Dequeue());
                qmotor_Speed = Convert.ToInt16(HS1_RPM.Dequeue());
                qdc_Current = Convert.ToInt16(HS1_CurrentDC.Dequeue());
                qHS1_TimeStamp = Convert.ToString(HS1_Time.Dequeue());
                this.Invoke((Action)this.update_chart1);    // update of individual chart
                this.Invoke((Action)this.update_chart2);
                this.Invoke((Action)this.update_chart3);

                this.Invoke(new MethodInvoker(() => textBox1.Text = qHS1_TimeStamp));
            }       

            HS1_PlotOK = false;           
        }
    }
}
Here is button click to close HS1 form // (on HS1 form)
C#:
private void btn_CloseHS1_Click(object sender, EventArgs e)
{
    Close_HS1Form = true;
}
Here is methods used to access CAN class structure in HS1 form (- used to access the same data on both HS1 form and main form)
C#:
public class MCU_Message             // (on HS1 form)
{
    /// 11/29-bit message identifier
    public string ID { get; set; }
    /// Type of the message
    public Peak.Can.Basic.TPCANMessageType MSGTYPE { get; set; }
    public string MCU_TimeStamp;
    public bool msg_avail;
    public bool readyforPlot;
    public byte LEN { get; set; }
    public byte[] data { get; set; }

    public MCU_Message(string id, bool msg, bool ready, TPCANMessageType id_type, byte len, string Period)
    {
        ID = id;
        MSGTYPE = id_type;
        MCU_TimeStamp = Period;
        LEN = len;
        msg_avail = msg;
        readyforPlot = ready;
        data = new byte[len];
    }
}

public byte[] HS1_data      // (on HS1 form)
{
    get { return HS1_Message.data; }
    set { HS1_Message.data = value; }
}

public bool HS1_canRead
{
    get { return HS1_Message.msg_avail; }
    set { HS1_Message.msg_avail = value; }
}
Here is how I know the previous data passed to sub form has been processed and read in new data and then passed to sub form
C#:
foreach (MessageStatus msg in m_LastMsgsList)    // this is in main form
{
    if ((msg.CANMsg.ID == theMsg.ID) && (msg.CANMsg.MSGTYPE == theMsg.MSGTYPE))
    {
        msg.Update(theMsg, itsTimeStamp);   // update display log in main form

        //if (HS1_diagnose && !hs1Data.HS1_canRead)   // if i use this if condition check, the program is not stable and sometimes give error of object instance not initialised
        if (HS1_diagnose && hs1Data != null)                 // which is why i switch to hs1Data != null
        {
            decodeMessageMCU(msg, itsTimeStamp, hs1Data.HS1_ID, TPCANMessageType.PCAN_MESSAGE_EXTENDED);
            hs1Data.HS1_canRead = true;                  
        }
        return;
    }
}
Here is the decodeMessageMCU , also in main form
C#:
// We filter the received message looking for waht we want
if ((msg.CANMsg.ID == Convert.ToUInt32(id_searched, 16)) && (msg.CANMsg.MSGTYPE == id_type_searched) && (msg.MarkedAsUpdated))
{
    switch (msg.CANMsg.ID)
    {
        case 421068657:     // Torque, Speed
            hs1Data.HS1_TimeStamp = msg.TimeString;
            for (int i = 0; i < hs1Data.HS1_len; i++)
                hs1Data.HS1_data[I] = msg.CANMsg.DATA[I];
            msg.MarkedAsDecoded = true;
            break;
    }
}
Previously, my approach is messy as I put all data on main form, decode them in main form and then pass variables to sub form to plot chart. I did not encounter this problem then.
Now, the main form just receive message and pass the raw data to sub form which do the decode and plotting.

Sorry if I posted so much BUT i really have NO IDEA where the above problem occur to me and how to solve this.

Any advice is appreciated.
Thanks
Tang
 
Last edited by a moderator:
Unfortunately there's just too much code there. There's no way that all of that actually is relevant and it will take us, who have no access to or experience with the specific hardware, too much time and effort to work out what's going on. For yourself and for us, you should have created a new test project that is as simple as possible but still demonstrates the issue. That will often provide you with some insight into what's actually happening and thus enable you to at least partially solve your own issue, but it definitely means that we can identify what's happening and what's going wrong much more efficiently.
 
Ok.. Is the following method to exchange data between a thread running on sub form and main form ok?
C#:
public byte[] HS1_data      // (on HS1 form)
{
    get { return HS1_Message.data; }
    set { HS1_Message.data = value; }
}

public bool HS1_canRead
{
    get { return HS1_Message.msg_avail; }
    set { HS1_Message.msg_avail = value; }
}
the data is residing on sub form. I supect is passing of data between sub form and main intermittent
 
Last edited by a moderator:
Please don't post unformatted code snippets as they are too hard to read, especially on mobile devices. I spent some time formatting post #4 and notified you of the change and you did the same in post #6. Ensuring that you post only the minimal code required to demonstrate the issue and posting code that is as readable as possible is all part and parcel of doing what you can to help us help you.
 
As for the question, you've just posted code for a couple of properties so there is no exchange of data occurring there.

In general, forms are just objects so passing data between forms or between forms and other objects is no different than passing any data between any two objects. The sticky part is that anything that relates directly to the UI, i.e. anything that makes use of the window handle of a form or control, must be done on the UI thread. Just passing data between forms doesn't involve the window handle so it doesn't matter what thread to do it on. Setting the Text of a TextBox, for instance, does involve the window handle because it involves modifying what you see in the UI, so that does need to be done on the UI thread.

If everything you do doesn't take long then it's OK to do it all on the UI thread. As soon as you start using multi-threading to keep the UI responsive, you should generally do as much as you can on a secondary thread, only marshalling to the UI thread when you actually need to update the UI. If you need to perform multiple operations on the UI, you should marshal a single method call and perform all those operations together. That's because going back and forth across the thread boundary is expensive. For instance, here:
C#:
this.Invoke((Action)this.update_chart1);    // update of individual chart
this.Invoke((Action)this.update_chart2);
this.Invoke((Action)this.update_chart3);

this.Invoke(new MethodInvoker(() => textBox1.Text = qHS1_TimeStamp));
you make four separate trips to the UI thread when you should just make one and then perform all four operations on that one trip.
 
You're using lots of invoke when already on UI thread, which indicate you haven't considered which thread does what. It basically looks like you do everything on UI thread except for a tight While loop in hs1_Diagnostic.

hs1_Diagnostic invokes ProcessMessage to UI thread.

Forms Timer interval 10ms is lower than possible accuracy: Timer Class (System.Windows.Forms)
 
yes. that is because ProcessMessage does the decoding and parsing of raw data.
Invoke calls via Timer is for chart plotting.

Is there any way to better this?

Please advise .
Chee Wai
 
In hs1_Diagnotic() you are using a Windows Forms timer in a non-UI thread. The WinForms timer assumes that it is running on a UI thread, and that there is a message pump. You probably should be using one of the other timer variants for use on that non-UI thread. I suggest the System.Threading.Timer or the System.Timers.Timer.


As an aside, I suspect what is happening is that when you are holding the mouse down on one of the forms, the message pump is being driven faster because it has to send all those mouse down windows messages. When you don't hold down the mouse, not as many messages get queued and the message pump doesn't have to be driven as fast. I suspect that you'll also see a similar speed up if you just simply wiggle the mouse pointer over one of the two forms as mouse move messages will have to be fired.
 
In hs1_Diagnotic() you are using a Windows Forms timer in a non-UI thread
It is only configured there, through Control.Invoke, so that is UI thread too.
 
Which thread you instantiate the Timer matters. Yes, the Invoke() call eventually drives the UI updates to happen on the UI thread, but controls have thread affinities.
 
Back
Top Bottom