Question C# ending thread problem

Zatnikitelman

Addon Developer
Addon Developer
Joined
Jan 13, 2008
Messages
2,303
Reaction score
6
Points
38
Location
Atlanta, GA, USA, North America
I'm working on a simple C# program that can send and receive ORB:Connect information. However, I'm having a small problem with getting my program to exit properly. Currently, it's setup as a Windows Form application, so in order to continuously receive data, I've spun receiving the information into its own little thread. However, that thread never seems to terminate, and the program hangs on exit. Here's my code pretty much in its entirety:
Code:
public partial class Form1 : Form
    {
        public delegate void myDelegate(string aString);
        myDelegate set;
        TcpClient client;
        StreamReader reader;
        Thread rThread;
        private volatile bool stop = false;

        public Form1()
        {
            this.FormClosed += Form1_FormClosing;
            InitializeComponent();
            client = new TcpClient();
            client.Connect("127.0.0.1", 37777);

            set += new myDelegate(this.setText);
            rThread = new Thread(new ThreadStart(receive));
            //rThread.IsBackground = true;
            rThread.Start();
        }

        ~Form1()
        {
            stop = true;
        }

        public void receive()
        {
            reader = new StreamReader(client.GetStream());
            while (!stop)
            {
                set(reader.ReadLine());
                Thread.Sleep(1);
            }
            reader.Close();
            client.Close();
        }
        private void Form1_FormClosing(object sender, EventArgs e)
        {
            stop = true;            
            rThread.Join(2000);
        }
        
        private void button1_Click_1(object sender, EventArgs e)
        {
            StreamWriter writer = new StreamWriter(client.GetStream());
            writer.WriteLine(textBox2.Text);
            writer.Flush();
        }

        private void setText(string s)
        {
            if (this.textBox1.InvokeRequired)
            {
                myDelegate ss = new myDelegate(setText);
                this.Invoke(ss, new object[] { s });
            }
            else
            {
                textBox1.Text = s;
                //textBox1.AppendText(s + "\r\n");
                //textBox1.SelectionStart = textBox1.Text.Length;
            }
        }
    }
In the form closing method, I set the bool to true so the while loop (in the receive method) should stop, and the thread should close the things, then exit. But it doesn't seem to, as I said, it hangs up on closing and I have to to "Stop Debugging" from the menu. Any idea what I'm missing here?

Thanks!
 
I would recommend you look at using BackgroundWorker since you are working with a form.

BackgroundWorker class

That link has a good concise example.

Constructors in C# behave differently then in C++ due to the way garbage collection is designed, my guess is the thread is not ending because the destructor is not being called.
 
Stop is definitely being set to true, it's actually being set not in the destructor, but in the Form1_FormClosing method. I checked it in the debugger and stop is being set correctly.
 
ReadLine() blocks. Maybe you're never getting past that point in the while loop.
 
Ah, I think that's it, Shifty. I closed it while the stream was sending data this time, and it closed correctly. Looks like I'll need to look at other ways of handling the incoming data.
 
Taken from a working program:

Code:
        Thread m_Thread;
        TcpClient m_client;
        StreamReader m_reader;
        StreamWriter m_writer;
        bool m_shutdown = false;

        public MFDQueryForm(string host, int port, Form form)
        {
 [...]

            try
            {
                m_client = new TcpClient(host, port);
                System.Diagnostics.Debug.WriteLine("NetDialog Connected to " + host + " port " + port);
                m_reader = new StreamReader(m_client.GetStream(), Encoding.ASCII);
                m_writer = new StreamWriter(m_client.GetStream(), Encoding.ASCII);

                m_Thread = new Thread(new ThreadStart(this.ThreadMain));
                m_Thread.IsBackground = true;
                m_Thread.Start();
            }
            catch (SocketException se)
            {
                MessageBox.Show(se.Message);
                System.Diagnostics.Process.GetCurrentProcess().Kill();
            }

        }

        public void ThreadMain()
        {
            try {
                while (! m_shutdown )
                {
                    string str = m_reader.ReadLine();                    
                    System.Diagnostics.Debug.WriteLine("NetDialog read [" + str + "]");
                    parseMessage(str);
                }
            }
            catch (Exception ex) {
                if (!m_shutdown)
                {
                    MessageBox.Show("NetDialog error:" + ex);
                    System.Diagnostics.Process.GetCurrentProcess().Kill();
                }
            }
            System.Diagnostics.Debug.WriteLine("NetDialog thread done");
        }

        public void Shutdown()
        {
            System.Diagnostics.Debug.WriteLine("MFDQueryForm shutdown");
            m_shutdown = true;
            m_client.Close();
            m_Thread.Join();
        }


---------- Post added at 03:56 PM ---------- Previous post was at 03:42 PM ----------

Also, here is my Orb::Connect client class in C#, feel free to reuse:

Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;

namespace OrbiterRemoteControl
{
    class OrbConnectClient
    {
        string m_host;
        int m_port;
        Socket m_socket;
        Dictionary<string, string> m_response;

        public OrbConnectClient(string host, int port = 37777)
        {
            m_host = host;
            m_port = port;

            try
            {
                m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                System.Net.IPAddress remoteIPAddress = null;
                foreach (System.Net.IPAddress IPA in System.Net.Dns.GetHostAddresses(host))
                {
                    if (IPA.AddressFamily == AddressFamily.InterNetwork)
                    {
                        remoteIPAddress = IPA;
                        break;
                    }
                }

                System.Net.IPEndPoint remoteEndPoint = new System.Net.IPEndPoint(remoteIPAddress, port);
                m_socket.Connect(remoteEndPoint);
                System.Diagnostics.Debug.WriteLine("Connected to " + host + " port " + port);
            }
            catch (SocketException se)
            {
                System.Windows.Forms.MessageBox.Show(se.Message);
                System.Diagnostics.Process.GetCurrentProcess().Kill();
            }
        }

        public void ParseResponse()
        {
            byte[] buf = new byte[10000];
            m_socket.Receive(buf);
            string resp = System.Text.Encoding.ASCII.GetString(buf).TrimEnd('\0'); ;

            System.Diagnostics.Debug.WriteLine("Server said: [" + resp + "]");

            m_response = new Dictionary<string, string>();
            string[] lines = resp.Split('\n');
            foreach (string ll in lines)
            {
                if (ll.Length > 0)
                {
                    string l = ll.TrimEnd('\r', '\n');
                    string[] parts = l.Split('=');
                    m_response.Add(parts[0], parts[1]);
                    System.Diagnostics.Debug.WriteLine("[" + parts[0] + "] = [" + parts[1] + "]");
                }
            }
        }

        public string GetValue(string parameter)
        {
            if (m_response.ContainsKey(parameter))
                return m_response[parameter];
            else
                return null;
        }

        public void Send(string text)
        {
            try
            {
                byte[] buf = System.Text.Encoding.ASCII.GetBytes(text + "\r\n");
                m_socket.Send(buf);
                System.Diagnostics.Debug.WriteLine("Sent [" + text + "]");
            }
            
            catch (Exception ex) 
            {
                System.Windows.Forms.MessageBox.Show("Orb::Connect client error in Send():" + ex);
            }
        }

        public string Query(string parameter)
        {
            try
            {
                Send(parameter);
                ParseResponse();
                return GetValue(parameter);
            }
            catch (Exception ex)
            {
                System.Windows.Forms.MessageBox.Show("Orb::Connect client error in Query():" + ex);
            }
            return null;
        }

        public List<int> ParseIntListResponse(string resp)
        {
            List<int> ret = new List<int>();
            string[] items = resp.Split(',');
            foreach (string it in items)
            {
                int val = Convert.ToInt32(it);
                ret.Add(val);
            }

            return ret;
        }

        public bool ParseBoolResponse(string resp)
        {
            return (resp == "OK") ? true : false;
        }

        public bool SendKey(int mfd, int keycode)
        {
            string resp = Query("ORB:SendMFDKey:" + mfd + ":" + keycode);
            return ParseBoolResponse(resp);
        }

        public bool SendSELKey(int mfd)
        {
            return SendKey(mfd, 0x3B); // OAPI_KEY_F1 = 0x3B
        }

        public bool SendMNUKey(int mfd)
        {
            return SendKey(mfd, 0x29); // OAPI_KEY_GRAVE = 0x29
        }

        public List<int> GetVNCMFDs()
        {
            return ParseIntListResponse(Query("ORB:VirtualMFDs"));
        }
    }
}
 
Last edited:
This is pretty unrelated but what's with:
return (resp == "OK") ? true : false;
Is that not the same as:
return (resp == "OK");
It just stands out because the ternary operator is unusual enough, despite seeming unneeded here. /random
 
It is redundant in C#, it's just a habit I picked up from C programming.

The problem is that in C, true is remarkably ill-defined. In context of == operator, true always equals 1. But in context of a conditional expression, true is any non-zero value. This leads to confusion in case of library functions, because if the documentation says on success, the function returns true, you don't know if the author meant on success, the function always returns 1 or on success, the function returns (some, unspecified, and possibly variable) non-zero value.

This, in turn, can lead to problems if you decide to plug your true into a bitwise operator for some reason. Consider the following:

int result1 = ALibraryFunctionReturningTrueOrFalse();
int result2 = OtherLibraryFunctionReturningTrueOrFalse();

if ( result1 && result2 ) { printf("both functions succeeded"); }
if ( result1 & result2 ) { printf("both functions succeeded"); }

You'd think that both ifs are equivalent, but they are not. Yes, your compiler will usually give you a warning for trying to use & instead of && under if. But imagine a situation where you are doing embedded programming, and you're using function results to set some control register:

REGISTER = (result1 << 1) | result2;

You'd expect that REGISTER should be equal to 2 but it actually depends on what your library author decided to use as true. If he actually used 1, you're fine, but if he used 0xDEADBEEF you're toast. Or even worse if the value of true is variable, cf:

int StringContainsADot(char *s) {
return (int) strchr(s, '.');
}

Hence doing

int myFunction(int par1, int par2) {
return someLibraryFunction(par1, par2) ? 1 : 0
}

to ensure that the true is 1 convention is used consistently.
 
Last edited:
Back
Top