Home Contact Sitemap
What is thinktecture? thinktecture is a European software development support company. We help software development and architecture teams with an influx of in-depth technical knowledge and expertise in the areas of application design, scalability, security and maintainability.

Location: Home > Resources > HandlingEventsHangsApplication

PRB: GUI application hangs when using non-[OneWay]-Events

2003
Ingo Rammer

Question

I've implemented a small GUI-based chat application using .NET Remoting. I tried to use your approach of weeding dead clients out by not using [OneWay]-Event handlers but instead calling each element in the InvocationList manually in a try/catch block. Unfortunately, after the user sends his text to the server which in turn calls the event-delegate to broadcast it to all listeners, the client application hangs. What can I do about is? Do I have to use [OneWay] here?

Answer

No. Don't use [OneWay] - this will drain your server's resources quicker than you might expect.

Instead let me tell you about the reason for this problem and how you should work around it:

First and foremost: It's not a problem with Remoting; it's about multiple threads, concurrency and the "UI-Thread".

Let's have a look at what's happening in this application. I'll simplify it a little: basically you have a client and a server. The client has to be a GUI application but it doesn't really matter how the server is implemented to show this problem. It also doesn't matter how many clients are connected to the server. The client has a Textbox (or RichTextBox), a button and listens for events from the server. As soon as the button is clicked, a message is sent to the server - the server will then broadcast the message to all clients using the eventhandler-delegate [basically, a chat-application ;-)].

As soon as the client receives the event it will somehow display the message in some element of the GUI (add it to a RichTextBox or such). So, let's have a look what happens here:

  • 1st step: Client Thread 1: Button-Click -> Send Message to server -> [wait for server to complete the call. Thread blocks!]
  • 2nd step: Server Thread 1: Receive the message -> Broadcast it to all listeners -> Wait for all listeners to accept it before completion
  • 3rd step: Client Thread 2: Receive Event -> Update GUI

Shouldn't be a problem, right? Actually not, if it wasn't for this mysterious creature called the UI-Thread. That means that only one thread is responsible for handing interaction with the GUI in .NET applications.

In the client shown above, Thread 1 is the UI-Thread. As soon as the message is sent to the server it will block (after all, it's still handling the button's click), waiting for the response to the method call. In the meantime, when Thread 2 receives the event from the server, it will handle it without any problems as long as you aren't updating the GUI. As soon as you interact with the GUI, it will try to delegate the work to the UI-Thread - which is unfortunately still blocked as it's waiting for the response. To further intensify the problem, the server's method call will not return until all client-side eventhandlers have processed the message: Deadlock!

So, the reason why [OneWay] solves the problem (actually, it only cures the symptoms here) is that in this case, the server's call to all event handlers will return before the events are actually processed at the clients. This means that Thread 1 will be "free" again (it has finished handling the button's click after the server-side method call returned) and Thread 2 can delegate it's UI-work to it.

But you already know that [OneWay] isn't actually the best thing to do for events because of the stale event handlers which will drain your server's performance. So what can you do about it? Well ... the easiest thing is to change the originating call inside the button's click-handler.

Suppose, you started like this:

private void button1_Click(object sender, System.EventArgs e)
{
   SendMessageToServer()
}

private void SendMessageToServer()
{
   // call the server's method
}

You can now for example easily put the SendMessageToServer() method in a new thread as shown in the following sample to solve your problems (You could also use a delegate and call .BeginInvoke() on it or such):

private void button1_Click(object sender, System.EventArgs e)
{
  Thread thr = new Thread(new ThreadStart(SendMessageToServer));
  thr.Start();
}

private void SendMessageToServer()
{
   // call the server's method
}

When you're doing this, you simply don't block the UI thread anymore.

Something else which you should also really care about is the general interaction with the UI in multithreaded applications. Every Control has the property InvokeRequired - this property will be true if you're accessing it from any thread that's not the UI thread. If it is true, you must not directly call any other of the Control's methods but instead have to call Control.Invoke() which pushes the method call on to the UI-Thread.

Instead of calling MyRichTextBox.AppendText("hello world") you can for example use a helper class like this and call AppendHelper.SafeAppend("hello world", MyRichTextBox):

public class AppendHelper
{
  delegate void AppendTextDelegate(String text, RichTextBox rtb);
  private static void InternalSafeAppend(String text, RichTextBox rtb)
  {
    rtb.AppendText(text);
  }

  public static void SafeAppend(String text, RichTextBox rtb)
  {
    if (rtb.InvokeRequired)
    {
       // create the parameter array
       Object[] parameters = {text, rtb};
       rtb.Invoke(new AppendTextDelegate(InternalSafeAppend),parameters);
       Console.WriteLine("Invoke returned");
    }
    else
    {
       rtb.AppendText(text);
       Console.WriteLine("AppendText returned");
    };
  }
}

To summarize: Something you should never forget: As soon as you are working with Events via Remoting, you are essentially creating a multi-threaded application. The issue shown here is only the beginning ;-)

By the way, you should also take those things into account when you're doing GUI-based Remoting servers!

 

 

 
© 2002-2012 by thinktecture GmbH & Co KG. All rights reserved. Contact | Impressum