Robert Robert - 25 days ago 8
C# Question

C# blocked form when i receive data

i have arduino uno and im using it like A/D converter. To my arduino goes signal 1-5V, i convert it to digital and send it to my port. I made an application in C# WFA. Here is the code of the application:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;
using System.Threading;

namespace na_posla
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
button1.Enabled = true;
button2.Enabled = false;
button3.Enabled = false;
getAvailablePorts();
}

void getAvailablePorts()
{
String[] ports = SerialPort.GetPortNames();
comboBox1.Items.AddRange(ports);
}

private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{

}

private void button1_Click(object sender, EventArgs e)
{
try
{
if (comboBox1.Text == "" || comboBox2.Text == "")
{
MessageBox.Show("Please select port settings");
}
else
{
serialPort1.PortName = comboBox1.Text;
serialPort1.BaudRate = Convert.ToInt32(comboBox2.Text);
serialPort1.Open();
progressBar1.Value = 100;
button1.Enabled = false;
button2.Enabled = true;
button3.Enabled = true;
}
}

catch (UnauthorizedAccessException)
{
textBox1.Text = "Unauthorized Access";
}
}

private void button2_Click(object sender, EventArgs e)
{
serialPort1.Close();
progressBar1.Value = 0;
textBox1.Text = "";

button1.Enabled = true;
button2.Enabled = false;
button3.Enabled = false;
}

private void button3_Click(object sender, EventArgs e)
{

double y = 0;
double doubleval;
string stringVal;
stringVal = serialPort1.ReadLine();
if (stringVal.Contains("."))
stringVal = stringVal.Replace(".", ",");
doubleval = System.Convert.ToDouble(stringVal);
y = (doubleval / 5) * 40;


textBox1.Text = y.ToString();

//for (; ; )
//{
// stringVal = serialPort1.ReadLine();
// if (stringVal.Contains("."))
// stringVal = stringVal.Replace(".", ",");
// doubleval = System.Convert.ToDouble(stringVal);
// y = (doubleval / 5) * 40;


// textBox1.Text = y.ToString();
//}


}

}
}


In the form i have 2 combo boxes, 3 buttons, progress bar, and text box. Button one Open port button, button2 Close port button. When i press button3 (read) i receive data from arduino and i write it in the textbox. When i have this code and im clicking on read i receive new data. The problem starts, when i want to read data in a loop. The loop is commented, in the code. I cant click on any button, i cant stop the reading. I have read about backgroundworker but i have no idea how to apply it in my code. Can someone please help me?

Answer

You have to use a Thread for the Read (I hope I have forgot nothing in the code):

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.IO.Ports;
using System.Threading;

namespace na_posla
{
    public partial class Form1 : Form
    {
        Thread readThread = null;
        bool threadCanRun = false;

        public Form1()
        {
            InitializeComponent();
            button1.Enabled = true;
            button2.Enabled = false;
            button3.Enabled = false;
            getAvailablePorts();
        }

        void getAvailablePorts()
        {
            String[] ports = SerialPort.GetPortNames();
            comboBox1.Items.AddRange(ports);
        }

        private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
        {

        }

        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                if (comboBox1.Text == "" || comboBox2.Text == "")
                {
                    MessageBox.Show("Please select port settings");
                }
                else
                {
                    serialPort1.PortName = comboBox1.Text;
                    serialPort1.BaudRate = Convert.ToInt32(comboBox2.Text);
                    serialPort1.Open();
                    progressBar1.Value = 100;
                    button1.Enabled = false;
                    button2.Enabled = true;
                    button3.Enabled = true;
                }
            }

            catch (UnauthorizedAccessException)
            {
                textBox1.Text = "Unauthorized Access";
            }
        }

        private void button2_Click(object sender, EventArgs e)
        {
            serialPort1.Close();
            progressBar1.Value = 0;
            textBox1.Text = "";

            button1.Enabled = true;
            button2.Enabled = false;
            button3.Enabled = false;
        }
        private void button3_Click(object sender, EventArgs e)
        {
            StopReadThread();
            StartReadThread();
        }
        private void SetTextBoxText(TextBox txtBox, string value)
        {
            if (txtBox.InvokeRequired)
            {
                txtBox.Invoke((MethodInvoker)delegate { SetTextBoxText(txtBox, value); });
                return;
            }
            txtBox.Text = value;
        }
        private void StartReadThread()
        {
            if (readThread == null)
                readThread = new Thread(ReadDataLooped);
            if (readThread.ThreadState == ThreadState.Unstarted)
            {
                threadCanRun = true;
                readThread.Start();
            }
        }
        private void StopReadThread()
        {
            if(readThread != null && readThread.ThreadState != ThreadState.Unstarted)
            {
                if (readThread.IsAlive)
                {
                    threadCanRun = false;
                    Thread.Sleep(50);
                    readThread.Abort();
                    readThread.Join();
                }
            }
        }
        private void ReadDataLooped(object obj)
        {
            try
            {
                while (threadCanRun)
                {
                    ReadAndSetData();
                    Thread.Sleep(1000); //Wait a second between the reads
                }
            }
            catch (ThreadAbortException)
            {
                Thread.ResetAbort();
            }
        }
        private double ReadValueFromSerialPort()
        {
            string stringVal = serialPort1.ReadLine();
            if (stringVal.Contains("."))
                stringVal = stringVal.Replace(".", ",");
            double doubleval = System.Convert.ToDouble(stringVal);
            double y = (doubleval / 5) * 40;
            return y;
        }
        private void ReadAndSetData()
        {
            this.SetTextBoxText(textBox1, this.ReadValueFromSerialPort().ToString());
        }

    }
}

Explanation:

Your Form runs in the STAThread of your application. If you want do some work your Thread is blocked since the work is done. So if you want update the data permanently, you have to run this code in a seperate Thread and the UI can update and respond in the first STAThread. There are plenty ways of doing this. I've decided to use a simple Thread, because it is .Net 3.5 compatible. Task and await is implemented since .Net 4.0 (and is a lot easier / less work to do).