-1

I am new in C# and I want to write a chat program using thread in which there are two threads in each side (one for listening and another for sending messages).

However, threads could not work with components (i.e. Textbox) because another thread already catch that.

Here is my codes:

server side:

private TcpClient clientSocket;

private void btnStartListening_Click(object sender, EventArgs e)
{
    TcpListener serverSocket = new TcpListener(int.Parse(txtPort.Text));
    int requestCount = 0;
    clientSocket = default(TcpClient);
    serverSocket.Start();
    txtReceivedMessages.Text +=" >> Server Started\n";
    clientSocket = serverSocket.AcceptTcpClient();
    txtReceivedMessages.Text += " >> Accept connection from client\n";
    requestCount = 0;
    Thread t = new Thread(listeningThread);          
    t.Start();      
    clientSocket.Close();
    serverSocket.Stop();                    
}

private void listeningThread()
{
    while (true)
    {
        try
        {                  
            NetworkStream networkStream = clientSocket.GetStream();
            byte[] bytesFrom = new byte[10025];
            networkStream.Read(bytesFrom, 0, (int)clientSocket.ReceiveBufferSize);
            string dataFromClient = System.Text.Encoding.ASCII.GetString(bytesFrom);
            dataFromClient = dataFromClient.Substring(0, dataFromClient.IndexOf("$"));                
            string serverResponse = "Last Message from client" + dataFromClient;                
            networkStream.Flush();                
            txtReceivedMessages.Text += dataFromClient;
            txtReceivedMessages.Update();
        }
        catch (Exception ex)
        {
            txtReceivedMessages.Text += ex.ToString() + "\n";
        }
    }
}

private void btnSend_Click(object sender, EventArgs e)
{
    NetworkStream networkStream = clientSocket.GetStream();
    Byte[] data = Encoding.ASCII.GetBytes(txtSendMessage.Text);
    networkStream.Write(data, 0, data.Length);
}

enter image description here

client side:

private NetworkStream serverStream;
private void btnSend_Click(object sender, EventArgs e)
{
    serverStream = clientSocket.GetStream();
    byte[] outStream = System.Text.Encoding.ASCII.GetBytes(txtMsgToSend.Text + "$");
    serverStream.Write(outStream, 0, outStream.Length);
    serverStream.Flush();


}

private void listeningThread()
{
    byte[] inStream = new byte[10025];
        serverStream.Read(inStream, 0, (int)clientSocket.ReceiveBufferSize);
        string returndata = System.Text.Encoding.ASCII.GetString(inStream);
        txtReceivedMsgs.Text += ">>" + returndata;
        txtMsgToSend.Text = "";
        txtMsgToSend.Focus();
}

private void btnSetConfiguration_Click(object sender, EventArgs e)
{
    txtReceivedMsgs.Text += "Client Started";
    clientSocket.Connect(txtIP.Text, int.Parse(txtPort.Text));
    lblStatus.Text = "Client Socket Program - Server Connected ...";
}

enter image description here

Mong Zhu
  • 23,309
  • 10
  • 44
  • 76
fateme
  • 83
  • 1
  • 6
  • 2
    You haven't asked a question. – Servy Nov 18 '16 at 19:16
  • you need to research about the `Invoke` or `BeginInvoke` methods. They can execute the code that is throwing the exception on the main thread that created the control you want to manipulate like the `TextBox` – Mong Zhu Nov 18 '16 at 19:16
  • Dear @Mong Zhu I have an error in the first line of listeningThread function (server side) which is: Cannot access a disposed object. – fateme Nov 18 '16 at 19:41
  • the problem is that you close the socket: `clientSocket.Close();` in the `btnStartListening_Click` method. Then you start the thread and try to access the closed `clientSocket`. don't close or dispose an object that you are currently using. – Mong Zhu Nov 19 '16 at 11:39

1 Answers1

0

If you want to update form controls from another thread you need to use the Invoke() method.

See more from this post on SO How to update the GUI from another thread in C#?

Edit: Content from linked post

For .NET 2.0, here's a nice bit of code I wrote that does exactly what you want, and works for any property on a Control:

private delegate void SetControlPropertyThreadSafeDelegate(
                         Control control, 
                         string propertyName, 
                         object propertyValue);

public static void SetControlPropertyThreadSafe(
                         Control control, 
                         string propertyName, 
                         object propertyValue)
{
  if (control.InvokeRequired)
  {
    control.Invoke(new SetControlPropertyThreadSafeDelegate               
    (SetControlPropertyThreadSafe), 
    new object[] { control, propertyName, propertyValue });
  }
  else
  {
    control.GetType().InvokeMember(
    propertyName, 
    BindingFlags.SetProperty, 
    null, 
    control, 
    new object[] { propertyValue });
  }
}

Call it like this:

// thread-safe equivalent of
// myLabel.Text = status;
SetControlPropertyThreadSafe(myLabel, "Text", status);

If you're using .NET 3.0 or above, you could rewrite the above method as an extension method of the Control class, which would then simplify the call to:

myLabel.SetPropertyThreadSafe("Text", status);

UPDATE 05/10/2010:

For .NET 3.0 you should use this code:

private delegate void SetPropertyThreadSafeDelegate<TResult>(
                          Control @this, 
                          Expression<Func<TResult>> property, 
                          TResult value);

public static void SetPropertyThreadSafe<TResult>(
                          this Control @this, 
                          Expression<Func<TResult>> property, 
                          TResult value)
{
  var propertyInfo = (property.Body as MemberExpression).Member 
  as PropertyInfo;

  if (propertyInfo == null ||
      !@this.GetType().IsSubclassOf(propertyInfo.ReflectedType) ||
      @this.GetType().GetProperty(
      propertyInfo.Name, 
      propertyInfo.PropertyType) == null)
  {
    throw new ArgumentException("The lambda expression 'property' must      reference a valid property on this Control.");
  }

  if (@this.InvokeRequired)
  {
  @this.Invoke(new SetPropertyThreadSafeDelegate<TResult> 
  (SetPropertyThreadSafe), 
  new object[] { @this, property, value });
  }
  else
  {
      @this.GetType().InvokeMember(
      propertyInfo.Name, 
      BindingFlags.SetProperty, 
      null, 
      @this, 
      new object[] { value });
  }
}

which uses LINQ and lambda expressions to allow much cleaner, simpler and safer syntax:

myLabel.SetPropertyThreadSafe(() => myLabel.Text, status); // status has to be a string or this will fail to compile

Not only is the property name now checked at compile time, the property's type is as well, so it's impossible to (for example) assign a string value to a boolean property, and hence cause a runtime exception.

Unfortunately this doesn't stop anyone from doing stupid things such as passing in another Control's property and value, so the following will happily compile:

myLabel.SetPropertyThreadSafe(() => aForm.ShowIcon, false);

Hence I added the runtime checks to ensure that the passed-in property does actually belong to the Control that the method's being called on. Not perfect, but still a lot better than the .NET 2.0 version.

If anyone has any further suggestions on how to improve this code for compile-time safety, please comment!

Community
  • 1
  • 1
Sam Marion
  • 690
  • 1
  • 4
  • 17
  • While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/low-quality-posts/14335410) – Mark Nov 18 '16 at 20:43
  • I feel I did with saying he needs to use the Invoke method? I can edit my answer though. – Sam Marion Nov 18 '16 at 20:45