2

I asked in a previous question how to "Threading 2 forms to use simultaneously C#".
I realize now that I was not explicit enough and was asking the wrong question.

Here is my scenario:
I have some data, that I receive from a local server, that I need to write to a file. This data is being sent at a constant time rate that I cant control.
What I would like to do is to have one winform for the initial setup of the tcp stream and then click on a button to start reading the tcp stream and write it to a file, and at the same time launch another winform with multiple check-boxes that I need to check the checked state and add that info simultaneously to the same file.
This processing is to be stopped when a different button is pressed, closing the stream, the file and the second winform. (this button location is not specifically mandatory to any of the winforms).

Because of this cancel button (and before I tried to implement the 2nd form) I used a background worker to be able to asynchronously cancel the do while loop used to read the stream and write the file.

    private void bRecord_Click(object sender, EventArgs e)
    {
        System.IO.StreamWriter file = new System.IO.StreamWriter(AppDomain.CurrentDomain.BaseDirectory + DateTime.Now.ToString("yyyy-dd-M--HH-mm-ss") + ".xml", true);

        data_feed = client.GetStream();
        data_write = new StreamWriter(data_feed);
        data_write.Write("<SEND_DATA/>\r\n");
        data_write.Flush();

        exit_state = false;
        string behavior = null;

        //code to launch form2 with the checkboxes
        //...

        worker = new BackgroundWorker();
        worker.WorkerSupportsCancellation = true;

        worker.DoWork += new DoWorkEventHandler((state, args) =>
        {
            do
            {
                int var = data_feed.ReadByte();
                if (var != -1)
                {
                    data_in += (char)var;

                    if (data_in.IndexOf("\r\n") != -1)
                    {
                        //code to check the checkboxes state in form2
                        //if (form2.checkBox1.Checked) behavior = form2.checkBox1.Text;
                        //if (form2.checkBoxn.Checked) behavior = form2.checkBoxn.Text;  

                        file.WriteLine(data_in + behavior);
                        data_in = "";
                    }
                }
            }
            while (exit_state == false);
        });

        worker.RunWorkerAsync();
    }
    private void bStop_Click(object sender, EventArgs e)
    {
        exit_state = true;
        worker.CancelAsync();
    }

I hope I've been clearer now. I not experienced in event programming and just started in C# so please try to provide some simple examples in the answers if possible.

Community
  • 1
  • 1
wmw301
  • 49
  • 1
  • 8

1 Answers1

1

At first would it be enough to use one Winform? Disable all checkboxes, click a button which enables the checkboxes and start reading the tcpstream? If you need two Forms for other reasons let me know, but i think this isn't needed from what i can see in your question.

Then i would suggest you to use the Task Library from .Net. This is the "modern" way to handle multithreading. BackgroundWorker is kind of old school. If you just able to run on .Net 2.0 you have to use BackgroundWorker, but don't seem to be the case (example follows).

Further if you want to cancel a BackgroundWorker operation this isn't only call CancelAsync();. You also need to handle the e.Cancelled flag.

backgroundWorker.WorkerSupportsCancellation = true;

private void CancelBW()
{
   backgroundWorker.CancelAsync();
}

private void backgroundWorker_DoWork += ((sender, args)
{
    //Handle the cancellation (in your case do this in your loop for sure)
    if (e.Cancelled) //Flag is true if someone call backgroundWorker.CancelAsync();
       return;

    //Do your stuff.
});

There is no common way to directly cancel the backgroundWorker operation. You always need to handle this.

Now let's change your code to the modern TAP-Pattern and make some stuff you want to have.

private void MyForm : Form
{
   private CancellationTokenSource ct;

   public MyForm()
   {
      InitializeComponent();

      checkbox1.Enable = false;
      //Disable all checkboxes here.

      ct = new CancellationTokenSource();
   }

   //Event if someone click your start button
   private void buttonStart_Click(object sender, EventArgs e)
   {
      //Enable all checkboxes here

      //This will be called if we get some progress from tcp
      var progress = new Progress<string>(value =>
      {
          //check the behaviour of the checkboxes and write to file
          file.WriteLine(value + behavior);           
      });

      Task.Factory.StartNew(() => ListenToTcp(ct, progress as IProgress<string)); //starts the tcp listening async
   }

   //Event if someone click your stop button
   private void buttonStop_Click(object sender, EventArgs e)
   {
      ct.Cancel();
      //Disable all checkboxes (better make a method for this :D)
   }

   private void ListenToTcp(CancellationToken ct, IProgess<string> progress)
   {
       do
       {
          if (ct.IsCancellationRequested)
            return;

          int temp = data_feed.ReadByte(); //replaced var => temp because var is keyword
          if (temp != -1)
          {
            data_in += (char)temp;

            if (data_in.IndexOf("\r\n") != -1)
            {
               if (progress != null)
                 progress.Report(data_in); //Report the tcp-data to form thread
               data_in = string.empty;                 
            }
        }
        while (exit_state == false);
   }
}

This snippet should do the trick. I don't test it so some syntax error maybe occur :P, but the principle will work.

The most important part is that you are not allowed to access gui components in another thread then gui thread. You tried to access the checkboxes within your BackgroundWorker DoWork which is no possible and throw an exception.

So I use a Progress-Object to reuse the data we get in the Tcp-Stream, back to the Main-Thread. There we can access the checkboxes, build our string and write it to the file. More about BackgroundWorker vs. Task and the Progress behaviour you can find here.

Let me know if you have any further questions.

Sebi
  • 3,879
  • 2
  • 35
  • 62
  • By `private CancellationToken ct = new CancellationTokenSource();` you meant `private CancellationTokenSource ct = new CancellationTokenSource();`, right? (It was the only syntax that worked) – wmw301 Nov 15 '16 at 19:22
  • I tried your code but the report `incoming_data` is not passing the entire stream to write to the file, I think. In the file the lines are incomplete in a random way (some, very few, are complete). Any idea why this is happening? – wmw301 Nov 15 '16 at 19:28
  • @wmw301 1) Yes. 2) Umpf... sound like a problem with thread synchronization. Please make sure your progress.Report Method is called when the data_in variable is ready to use. If you pass right string into Report(); this should work. Not sure if your code in while loop is exactly correct, because i don't know your data etc. Further it could happen that Report(); is executed second time when first time isn't printed to your file. Maybe because it's very big chunck and needs some time. But i don't think this is the case. Please try to debug and test if data_in is correct first. – Sebi Nov 15 '16 at 19:57
  • The string data_in is correct in the loop, but it gets trimmed in here: `var progress = new Progress(value => { file.WriteLine(data_in); });` Sometimes it comes blank, others with very few chars, others only some of the last chars are gone, and 1 or 2 times the complete string is passed. – wmw301 Nov 15 '16 at 23:41
  • Never mind, It was just the dumb copy code. I missed `file.WriteLine(value)`, that's why. (so embarrassed right now) – wmw301 Nov 15 '16 at 23:48