-1

My program is suppose to perform tests on 8 electronic products of the same model simultaneously. The previous programmer has implemented some form of multi-threading in the program to accomplish this. However, when 5 slots or more are being tested, the UI becomes unresponsive and the results being written to a text file may get corrupted.

Below I will insert a pseudo-code on what's going on in the program.

private void button1_Click(object sender, EventArgs e)
{ 
  //create_thread_1 <= mainFunction 1
  //start thread 1
}

private void button2_Click(object sender, EventArgs e)
{ 
  //create_thread_2 <= mainFunction 2
  //start thread 2
}

private void button3_Click(object sender, EventArgs e)
{ 
  //create_thread_3 <= mainFunction 3
  //start thread 3
}

private void mainFunction1
{
  //perform test A
  //write test A result to textFile1 //calls writeToTextFile1
  //perform test B
  //write test B result to textFile1 //calls writeToTextFile1
  //continues on and finishes all tests
  //aborts thread1
  //end
}

private void mainFunction2
{
  //perform test A
  //write test A result to textFile2 //calls writeToTextFile2
  //perform test B
  //write test B result to textFile2 //calls writeToTextFile2
  //continues on and finishes all tests
  //aborts thread2
  //end
}

private void mainFunction3
{
  //perform test A
  //write test A result to textFile3 //calls writeToTextFile3
  //perform test B
  //write test B result to textFile3 //calls writeToTextFile3
  //continues on and finishes all tests
  //aborts thread3
  //end
}

private void writeToTextFile1
{
  //creates and saves results into textFile1
}
private void writeToTextFile2
{
  //creates and saves results into textFile2
}
private void writeToTextFile3
{
  //creates and saves results into textFile3
}

My theory is that only a single thread can open and write data into a text file at a single time, so when another thread have to write data, that thread has to wait and causes the UI to become unresponsive. Am I right here? If I'm not, any advice is greatly appreciated.

One of the solutions that I have read online is to declare the WriteToTextFile function as a new Thread so that other main threads can wait for each other without slowing down the UI. Is this the correct approach?

EDIT: added the important parts of the coding for better understanding..This code runs for one slot only but the other 9 slots basically uses the same code here

private void button1_Click(object sender, EventArgs e)
{
  if (this.button1.Text == "START")
    {
       this.txtSerial1.ReadOnly = false;
       this.txtSerial1.Select();
       MessageBox.Show("SLOT 1: Scan the serial number and press ENTER", "3458A 
       Heat Rack", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
    }

  else if (System.Windows.Forms.DialogResult.OK == MessageBox.Show("SLOT 1: Are 
           you sure about stopping?", "3458A Heat Rack", 
           MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation))
     {
       this.call_main1.Abort();
       this.sentry1.Close();
       this.sentry1.Dispose();
       MessageBox.Show("SLOT 1: Unit can be safely removed now", "3458A Heat 
                       Rack", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
       this.txtSerial1.Clear();
       this.txtStart1.Clear();
       this.txtStatus1.Clear();
       this.info1.Clear();
       this.button1.Text = "START";
       this.button1.BackColor = this.startColour;
       this.txtStatus1.BackColor = Control.DefaultBackColor;
      }
}


private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
 {
   int num;
   int test_num = default(int);
   double resultrelay = default(double);

   if (e.KeyChar == '\r')
     {
       if (this.txtSerial1.Text.Length == 0)
         {
           this.txtSerial1.ReadOnly = true;
         }
       else if (this.txtSerial1.Text.Length >= 10)
         {
           try
             {
               this.sentry1 = new DirectIO(string.Concat("GPIB", 
                              this.busNumber_Slot1, "::22::INSTR"));
               this.terminal1 = new DirectIO(string.Concat("GPIB0::14::INSTR"));
               num = 1;
             }
           catch (Exception exception)
             {
               num = 0;
             }

        if (num != 1)
          {
            MessageBox.Show("SLOT 1: DUT Not Present !!", "3458A Heat Rack", 
                            MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
            this.txtSerial1.Clear();
            this.txtSerial1.Select();
            this.txtSerial1.ReadOnly = true;
          }

        else
          {
            this.button1.Select();
            this.button1.Text = "RUNNING";
            this.button1.BackColor = this.runningColour;
            this.txtSerial1.Text = this.txtSerial1.Text.ToUpper();
            this.txtStart1.Text = DateTime.Now.ToString();
            this.txtSerial1.ReadOnly = true;
            string txtBox1_serial = this.txtSerial1.Text;

            this.call_main1 = new Thread(() => this.main_Program_slot1(sentry1, 
                              terminal1, txtBox1_serial, 1, test_num, 
                              resultrelay));

            this.call_main1.Start();

           }


        }

    else
       {
         MessageBox.Show("SLOT 1: Unit Serial Number Is Incorrect!!", "3458A 
                         Heat Rack", MessageBoxButtons.OK, 
                         MessageBoxIcon.Asterisk);
         this.txtSerial1.Clear();
         this.txtSerial1.Select();
       }
    }
}

public void slot1(string test) //function to update GUI
{
  if (!base.InvokeRequired)
    {
      this.info1.Text = test;
    }
  else
    {
      Form1.test1 updateTestType = new Form1.test1(this.slot1);
      object[] objArray = new object[] { test };
      base.Invoke(updateTestType, objArray);
    }
}

private void write_TestResultDetails1(string serialnumber, double resultLatest1)
{ 
  //update results into textfile
}

private void main_Program_slot1(DirectIO sentry1, DirectIO terminal1, string sernum, int slot_Number, int test_num, double resultrelay)
{
  for (int i = 1; i <= loop_Count; i++)
    {
       slot1("TEST A");
       //performs testA
       //calls write_TestResultDetails1
       slot1("TEST B");
       //performs testB
       //calls write_TestResultDetails1
    }
}

Hope this coding can help you guys to understand my problem better.. PS: seems like changing to using BackGroundWorker instead of making my own threads will be a better choice for this kind of program.

Crescendo26
  • 171
  • 6
  • 3
    `//aborts thread1` - It's broken. No - seriously, pseudo code won't get you far. We need the real thing, or even better: a [mcve] ... – Fildor Nov 07 '17 at 08:48
  • 2
    From your pseudo-code, though it seems every thread writes to its "own" file, only. So either the pseudo-code is not reflecting what the actual code does, or it is not the problem. – Fildor Nov 07 '17 at 08:52
  • 2
    When all thread has to write to a common object, I suggest to use a managed resource for this, like https://learn.microsoft.com/en-us/dotnet/standard/collections/thread-safe/blockingcollection-overview, once you have all data to thread safe collection and all threads have been completed, then write the data to file from your collection or any other c# object. – Anil Nov 07 '17 at 08:53
  • 3
    I would (tactfully) suggest to your employer that they need a professional programmer to solve this problem. – Rob Anthony Nov 07 '17 at 08:55
  • If one file per product. So if you either have to get the test of a product you don't have to look for it in a txt file, between the test of 5 other product. – Drag and Drop Nov 07 '17 at 09:00
  • Now that Rob said it: This may be suited in a different form for Workplace SE ... ;) – Fildor Nov 07 '17 at 09:00
  • Standard answer to "UI hangs" is "run blocking process in background". See marked duplicate. Your bogus pseudo-code offers zero insight as to what's actually happening with your code. You need to post _real_, compileable code, i.e. **a good [mcve] that reliably reproduces the problem**. See also [ask], and especially the articles linked at the bottom of that page for very important advice on how to present your question in a clear, answerable way. – Peter Duniho Nov 07 '17 at 17:52
  • I have updated my question with the real code. Sorry for the vagueness of the pseudocode – Crescendo26 Nov 08 '17 at 01:46

2 Answers2

0

Windows forms programming has a few gotchas and keeping a UI responsive is tough. To help you debug this issue, I recommend you name your thread in form load so you can easily find it in the debugger (double click your form in the designer to get form load then call System.Threading.Thread.CurrentThread.Name = "My UI Thread". Launch your application and then when the UI hangs, break in the debugger. You can then observe the stacktrace of the UI Thread to find out where it is working (and where you need to use a thread to keep the UI responsive.

My hunch is that you have either used a synchronisation primitive incorrectly waiting for an answer on a thread, or you have accidentally launched some work without a thread which is hanging the UI.

There is a control called a BackgroundWorker which can be used to do work on a thread easily and then report the progress back with an event safely. This cuts down on the synchronisation work you need to do which might be helpful.

Totally agree with the previous comments btw, please post your actual code, just redact the method names etc. as the most likely issue is that your psuedo code and your actual code don't match.

Spence
  • 28,526
  • 15
  • 68
  • 103
  • 1
    "BackgroundWorker" ... also consider Taskbased Async Pattern / async await and ThreadPool as an alternative to BackgroundWorker. – Fildor Nov 07 '17 at 10:17
  • This is windows forms coding. Task based async awaits will still leave you with having to invoke yourself back onto the GUI thread. BackgroundWorker is specifically designed so you don't have to think about this. If you want to use tasks you should be using WPF etc. – Spence Nov 07 '17 at 20:09
  • if I use backgroundworker, I do no longer need to invoke the GUI anymore? Because based in the my coding, the separate threads have to invoke to change the values on the GUI. Are there any more differences between manually declaring the main function as a new thread versus using backgroundworker to handle the main functions? – Crescendo26 Nov 08 '17 at 01:24
  • I have added the real coding..Hope it helps – Crescendo26 Nov 08 '17 at 01:49
  • _"leave you with having to invoke yourself back onto the GUI thread"_ - That's what `Progress`is for ... – Fildor Nov 08 '17 at 07:46
  • @Fildor said it first, but there is a progress property that you can set on a background worker that abstracts away the invoking from the background workers thread. Essentially to use it you just implement the event on the backgroundworker to do the work, modify the progress property as you do work, and watch for the cancellation request flag from the gui, all of which are safe to do. Or you roll it yourself if you are confident. – Spence Nov 08 '17 at 09:03
  • Problem is that Async Await doesn't give you guarantees about threads. If you need to do work asynchronously you need to take effort to get back to the UI thread. Good example here: https://www.thomaslevesque.com/2015/11/11/explicitly-switch-to-the-ui-thread-in-an-async-method/ – Spence Nov 08 '17 at 09:11
0

I/O intensive tasks are perfect for asynchronous actions, i.e. writeToTextFile1(), writeToTextFile2(), and writeToTextFile3() can all be executed on different threads.

in your solution, the error might be caused from the fact that you wrapped two/three I/O method calls inside one thread.

I suggest you adopt the following pattern.

  1. take writeToTextFile1() for example, I would use async/await pattern to define this method:

    private async Task writeToTextFile1Async(string resultValue)
    {
        await Task.Run(() => {
            //create and saves results into textFile1
    
        });
    }
    
  2. rewrite mainFunction1() as follows:

    private async Task mainFunction1Async()
    {
        string resultA = "***";
        await writeToTextFile1Async(resultA);
        string resultB = "***";
        await writeToTextFile1Async(resultB);
        //perform other things
    }
    
  3. Call this function inside Button1 click event handler:

    private async void button1_Click(object sender, EventArgs e)
    { 
        await mainFunction1Async();
    }
    
Joseph Wu
  • 4,786
  • 1
  • 21
  • 19