facebook  linkedin  Twitter  skype  Rss googlePlus

Cross-threading issues in Windows Forms/WSE Applications

Dec 24 2004
1615
"Some months ago, I wrote some applications using Web Services Enhancements 2.0 and Visual Studio 2003 in order to check how the new SoapClient and SoapService classes work. Between all my samples, I wrote a point to point chat application that interchanges one-way messages over TCP."

Some months ago, I wrote some applications using Web Services Enhancements 2.0 and Visual Studio 2003 in order to check how the new SoapClient and SoapService classes work. Between all my samples, I wrote a point to point chat application that interchanges one-way messages over TCP.

First versions of WSE were not compatible with the beta version of .Net Framework 2.0. Now, WSE 2.0 sp2 claims to be compatible with new version and I have recently ported my old chat application to Visual C# 2005 Express edition and WSE 2.0 sp 2.

Basically, my sample opens a TCP port to listen incoming SOAP messages and writes information related to them in a  ListBox contained in the main Form.

The chat application compiles fine in VS2005 without making any change to the code but when it is running the next error is raised when a message is received and the application tryes to update the user interface.


"Illegal cross-thread operation: Control 'xxx' accessed from a thread other than the thread it was created on."

It is a threading issue and I think it will be a common error when porting existing Windows Forms applications to the new framework version. One of the main rules in Windows programming says that it is no possible to access Forms and controls from a thread different than its creating one. It seems that Windows O.S. or .Net Framework 1.1 have some security mechanism in order to correct violations to this golden rule, but now, .Net Framework 2.0 performs this check at the runtime raises the previous exception.

I have performed some tests checking the thread that is working at each moment, and in this case I think there are several threads created transparently by WSE that are listening at the TCP port defined, and then these threads are executing concurrently my SoapService object.

I show below some code corresponding to the old version of my chat application.

MessageInfo is a data structure that defines the data passed from the service to the user interface, and contains information related to the incoming messages.

public struct MessageInfo
{
 public string Sender;
 public string Message;
 public string Subject;
 public DateTime Date;
}
Next, the actual Service is shown. It supports only a method called GetMessage and it is listening at a certain TCP port. When a new SOAP message arrives, it fires an event to the main form, passing a MessageInfo structure.

public class ServiceProvider : SoapService
 {
 
  public delegate void MessageArrivedHandler(MessageInfo msgInfo);
  public event MessageArrivedHandler MessageArrived;
 
  public ServiceProvider()
  {
    SoapReceivers.Add(Settings.LocalUri, this);
  }
 
  [SoapMethod(WSChat.ActionNames.GetMessage)]
  public void GetMessage(SoapEnvelope env)
  {
      
    Message message = new Message(env.Body.FirstChild as System.Xml.XmlElement);
    
    MessageInfo msgInfo;
      msgInfo.Sender = env.Context.Addressing.From.Address.Value.Host;
      msgInfo.Message = message.Content.Value;
      msgInfo.Subject = message.Subject.Value;
      msgInfo.Date = env.Context.Security.Timestamp.Created;
 
         MessageArrived(msgInfo);
      }
}
Main form subscribes to events from the service and receives notifications in the OnMessageArrived method. Here, it updates a ListBox in order to show information about the incoming message. The first line of this method raises the previously explained threading exception.

public class Form1 : System.Windows.Forms.Form
 {
      ...
 
  public Form1()
  {
   ...
   messageReceiver = new ServiceProvider();
   messageReceiver.MessageArrived += new ServiceProvider.MessageArrivedHandler(OnMessageArrived);
   ...
  }
 
  ...
 
  void OnMessageArrived(MessageInfo msgInfo)
  {
    //Next line throws a threading exception!!!
 
    lstChat.Items.Add("From: " + msgInfo.Sender + " - " + msgInfo.Date.ToString("dd/mm/yyyy - hh:mm:ss"));
    lstChat.Items.Add("   " + msgInfo.Subject + ": " + msgInfo.Message);
  }
 
      ...
 }

Each Windows Form control has a property (inherited from Control) called InvokeRequired (also present in framework vs 1.1) that 
"returns true if the control's Handle was created on a different thread than the calling thread (indicating that you must make calls to the control through an invoke method); otherwise, false". 
(from the .Net framework reference).

So this property allows you to check some potential illegal cross-thread operation. The .Net framework reference for InvokeRequired contains also the next explanation:

"Controls in Windows Forms are bound to a specific thread and are not thread safe. Therefore, if you are calling a control's method from a different thread, you must use one of the control's invoke methods to marshal the call to the proper thread. This property can be used to determine if you must call an invoke method, which can be useful if you do not know what thread owns a control. There are four methods on a control that are safe to call from any thread: Invoke, BeginInvoke, EndInvoke and CreateGraphics. For all other method calls, you should use one of these invoke methods when calling from a different thread".

The information is clear, I should use the Invoke method Control.Invoke (Delegate, Object[]). Then I have changed the original method that receives the event to make a thread safety check and then perform a secure call using the Invoke method.

Then, in order to get my application working, I have performed the next changes to the OnMessageArrived method:

void OnMessageArrived(MessageInfo msgInfo)
  {
      //this method is entered concurrently by threads from tcp port listener
 
      if (lstChat.InvokeRequired)
      {
         MsgArrivedDelegate msgArrived = new MsgArrivedDelegate(OnMessageArrived);
         this.Invoke(msgArrived, new object[]{msgInfo});
      }
      else
      {
         lstChat.Items.Add("From: " + msgInfo.Sender + " - " + msgInfo.Date.ToString("dd/mm/yyyy - hh:mm:ss"));
         lstChat.Items.Add("   " + msgInfo.Subject + ": " + msgInfo.Message);
      }
   }

And now it works!! I think that a lot of Windows Forms applications will present these issues when being ported to .Net Framework 2.0, mainly if they are using some technology that supposes thread spawnning in background, such as port listeners.

 

About the Author, Staff

You can write for Santry.com, just email us at info@santry.com for more information.


blog comments powered by Disqus

 

Thank you for visiting our site, before you leave, please visit some of the areas or information you may have missed.

Popular Articles

HtmlNoArticles