Hosting on the UI Thread

Windows applications provide functionality by responding to messages. Each Windows application has a message queue and a window procedure that contains a message loop. The message loop retrieves messages from the message queue, one by one, and processes them, usually by forwarding the message to the appropriate target window. All controls on a form, for example, are also windows that have a unique window handle (HWND) and functionality to respond to particular windows messages.

Windows applications usually include some form of user interface that runs on a UI thread, meaning that messages are processed through the message loop. When WCF services are hosted inside a Windows application, operations can execute on thread pool threads or on the UI thread—as determined by design choices of both the service and its host. This brings to light some interesting considerations:

  • When services are hosted in a Windows application and hosted on the UI thread, the UI thread acts as a throttle for message processing—that is, messages are processed one at a time in the order received. To achieve additional throughput (process many requests concurrently) you may want to avoid hosting on the UI thread.

  • If you are concerned about hosting on the UI thread, you must also consider the location where you construct the ServiceHost. ServiceOperationBehavior settings can be used to control whether services allow messages to be processed on the UI thread.

  • When you are hosting a service on the UI thread, that usually implies some level of coupling between the service and the user interface. As such, your choice to host on the UI thread, or not, may also influence your decisions about assembly allocation for services, hosts, and related UI components.

When you create the ServiceHost on the UI thread, all requests are processed through the message loop. In a service that supports multithreading (to be discussed in Chapter 5), this throttles how many messages can be processed concurrently since the message loop processes one message at a time. One of the benefits of this is that requests can freely interact with form and control properties, since they are all running on the same thread (concurrency issues are also discussed in Chapter 5).

There are ways for both the host and service to control if the service runs on the UI thread. Hosts have control over which thread constructs the ServiceHost. Services can reject the UI thread using the UseSynchronizationContext property of the ServiceBehaviorAttribute. These are all considerations that I will elaborate on in this section. You’ll also complete a lab to gain some experience with each option.

Windows Applications and UI Threads

Both Windows Forms and WPF can be used to create Windows applications. Although each provides a distinct approach to UI design and internal application architecture, both types of applications usually create a main window running on the UI thread. The safest and simplest way to host services in a Windows application is for requests to be processed on this UI thread because it removes concerns over concurrency and complexities associated with multiple threads and communication with UI components. In fact, the default behavior for services hosted in a Windows application is to process requests on the UI thread if you construct the ServiceHost instance while running on that UI thread. Example 4-5 illustrates constructing the ServiceHost in the form constructor for a Windows Forms application.

Example 4-5. Constructing a ServiceHost instance in the Form constructor

public partial class Form1 : Form
{
  ServiceHost m_serviceHost;

  public Form1( )
  {
    InitializeComponent( );

    m_serviceHost = new ServiceHost(typeof(Messaging.MessagingService));
  }

  // other code
}

You can verify that the service is running on the UI thread by checking the Application.MessageLoop property as the operation executes. The following code, when placed inside any service operation, will throw an exception while debugging if the service is not running on the UI thread:

Debug.Assert(Application.MessageLoop);

When requests are processed through the message loop, service operations (including downstream code) can freely interact with the user interface, setting form and control properties for example. The only downside, as I have mentioned, is that the message loop acts as a throttle for request processing, even if the service were configured to allow multiple concurrent requests.

From the same Windows Form application, if you construct the ServiceHost instance before the UI thread is started, the host instance will run on its own thread. That means messages are processed by worker threads allocated from the thread pool, instead of passing through the message loop. This enables services to process multiple concurrent requests but introduces the need to provide multithreading protection when communicating with controls and forms and other shared application resources (concurrency is discussed in Chapter 5).

Processing requests on the UI thread is not practical for services that require decent throughput. By definition this usually rules out server deployments. In fact, most services requiring any level of throughput on the server will not be associated with a user interface. However, assuming some service operations do interact with the UI thread, while others do not and require throughput, services can request to be run on a separate thread to ensure this throughput using the ServiceBehaviorAttribute.

Synchronization Context

As I have mentioned, you can control which thread the ServiceHost is constructed on either at the host or service implementation. If you create the ServiceHost on the UI thread, messages are processed by that thread. If you create the ServiceHost before the UI thread is created, by default the ServiceHost creates a new thread when the ServiceHost is initialized. It is said that in the former case, the service joins the synchronization context of the UI, where in the latter case the service creates a new synchronization context. The synchronization context refers to the thread in which code is executing.

Example 4-6 shows how to construct the ServiceHost before Application.Run( ), which means the service executes in a new synchronization context.

Example 4-6. Initializing the ServiceHost before Application.Run( )

static class Program
{public static ServiceHost MessageServiceHost;

  [STAThread]
  static void Main( )
  {
    Program.MessageServiceHost = new
ServiceHost(typeof(Messaging.MessagingService));
    Program.MessageServiceHost.Open( );

    Application.EnableVisualStyles( );
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new Form1( ));
  }
}

If you want to initialize the ServiceHost on a separate thread, after the UI thread has been created, you can use the technique shown in Example 4-7. In this example, to avoid synchronization with the UI thread a new thread is run to create and open the ServiceHost.

Tip

The following code sample illustrates this scenario: <YourLearningWCF Path>\Samples\Hosting\ServiceHostNewThread.

Example 4-7. Constructing the ServiceHost on a new thread

// procedure to run on a new thread
private void StartService( )
{
  m_serviceHost = new ServiceHost(typeof(Messaging.MessagingService));
  m_serviceHost.Open( );
}

// starting the thread
Thread t;
t = new Thread(StartService);
t.IsBackground = true;
t.Start( );

The point is that a new synchronization context is created for the ServiceHost if the current synchronization context is not the UI thread. As I have stated, this means that messages are processed on threads from the thread pool instead of through the message loop. To synchronize with the UI thread, the host can construct each ServiceHost instance after the UI thread has been created. This is particularly valuable when the service operation will interact with the UI.

Services can also prevent synchronization with the UI thread using the UseSynchronizationContext property of the ServiceBehaviorAttribute. This property defaults to true, which is why services will join the UI thread if one exists. By setting it to false, even if the host constructs the ServiceHost on the UI thread, it will use a new thread. You apply this attribute to a service type as follows:

[ServiceBehavior(UseSynchronizationContext=false)]
public class MessagingService : IMessagingService

Table 4-2 summarizes the resulting synchronization context for a ServiceHost instance based on the thread on which it is constructed, and the UseSynchronizationContext setting.

Table 4-2. ServiceHost synchronization context options

UI thread created

UseSynchronizationContext

Synchronization context

Non-UI thread

True or false

New thread

UI thread

True (default)

UI thread

UI thread

False

New thread

ServiceHost Lifetime

By default, the lifetime of a ServiceHost instance is that of its thread. Typically, that means the lifetime of the application unless you explicitly call the Close( ) method of the ServiceHost. If you allow the application to be shut down without closing the ServiceHost and its associated channel listeners first, messages may continue to flow in while the application is about to dispose of all channels. To avoid this race condition between the time that the host application is shut down and incoming client requests, it is best to explicitly close each ServiceHost instance.

When the ServiceHost lifetime is tied to a particular form, you can hook the FormClosing event as shown in Example 4-8.

Example 4-8. Cleaning up the ServiceHost in the FormClosing event handler

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
  DialogResult result = MessageBox.Show("Are you sure you want to close the
service?", "Service Controller", MessageBoxButtons.YesNo, MessageBoxIcon.Question,
MessageBoxDefaultButton.Button2);

  if (result == DialogResult.Yes)
  {
    if (m_serviceHost!=null)
    {
      m_serviceHost.Close( );
      m_serviceHost =null;
    }
  }

  else
    e.Cancel=true;
}

If the ServiceHost instance is tied to the application lifetime, as opposed to a particular form, you can hook the Application object’s Exit event. This is shown later in Example 4-11.

You can also call Open( ) and Close( ) methods of the ServiceHost while the host is running, to start and stop the service when it is running on the UI thread. When the ServiceHost is not running on the UI thread, Close( ) ends the thread on which it initialized. This cleans up the ServiceHost instance and renders any references you have useless. In this case, your application must recreate and open the ServiceHost in order to receive subsequent requests.

Lab: Working with a Windows Forms Host

In this lab, you will host an existing service inside a Windows Forms application. During the lab, you’ll experiment with the effects of constructing the ServiceHost from different locations. You’ll also use the ServiceBehaviorAttribute to control whether services can be hosted on the UI thread.

Configuring the ServiceHost

To get started, the first thing you’ll do is set up the host.

  1. Open the startup solution for this lab at <YourLearningWCFPath>\Labs\Chapter4\WindowsApplicationHost\WindowsApplicationHost.sln. This solution contains a completed service and a shell service host and client application.

  2. Modify the WindowsHost project to host the MessagingService type from the Messaging assembly. Start by adding a reference to the Messaging project so that you’ll have access to the service.

  3. Now, add a <system.serviceModel> section to the app.config for the WindowsHost project. Add a <service> section for the Messaging.MessagingService type with a NetTcpBinding endpoint, a metadata exchange endpoint, and a base address for TCP and HTTP protocols. The resulting configuration is shown in Example 4-9.

    Example 4-9. WindowsHost configuration for MessagingService

    <system.serviceModel>
      <services>
        <service name="Messaging.MessagingService"
    behaviorConfiguration="serviceBehavior">
          <endpoint contract="Messaging.IMessagingService" binding="netTcpBinding" />
          <endpoint contract="IMetadataExchange" binding="mexHttpBinding" />
    
          <host>
            <baseAddresses>
              <add baseAddress="net.tcp://localhost:9000"/>
              <add baseAddress="http://localhost:8000"/>
            </baseAddresses>
          </host>
        </service>
      </services>
      <behaviors>
        <serviceBehaviors>
          <behavior name="serviceBehavior">
            <serviceMetadata/>
          </behavior>
        </serviceBehaviors>
      </behaviors>
    </system.serviceModel>

At this point, you have configured a Windows application to host a service, which looks a lot like what you have seen in earlier chapters for console hosts. What will be different is the location where you will initialize the ServiceHost.

Opening and closing the ServiceHost

As with all of the self-hosting examples shown in this book so far, you have to construct a ServiceHost to host a service. In this step, you’re going to construct and open the ServiceHost before creating the UI thread and provide code to close the ServiceHost gracefully on application exit.

  1. Go to the WindowsHost project and add a reference to the System.ServiceModel assembly.

  2. Next, open Program.cs and add code to construct a static reference to the ServiceHost instance, scoped to the application domain. Initialize the ServiceHost instance before the Application.Run( ) command, which creates the UI thread. Example 4-10 shows the changes you’ll make to Program.cs.

    Example 4-10. Creating the ServiceHost instance before the UI thread

    static class Program
    {public static ServiceHost MessageServiceHost;
    
      [STAThread]
      static void Main( )
      {
        Program.MessageServiceHost = new
    ServiceHost(typeof(Messaging.MessagingService));
        Program.MessageServiceHost.Open( );
    
        Application.EnableVisualStyles( );
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1( ));
      }
    }
  3. With the code just added, the ServiceHost lifetime is for the duration of the application. Once the application is closed, its application domain and the ServiceHost instance will be destroyed along with it. As a matter of good practice, the ServiceHost should be closed immediately when the application is exiting to prevent new requests from being processed. In the Program.cs file where the ServiceHost is created, add some code to hook the ApplicationExit event and call the Close( ) method of the ServiceHost to speed up this process.

    Add the code shown in bold in Example 4-11 to achieve this.

    Example 4-11. Hooking the ApplicationExit event to close the ServiceHost

    static void Main( )
    {Application.ApplicationExit += new EventHandler(Application_ApplicationExit);
      Program.MessageServiceHost = new ServiceHost(typeof(Messaging.MessagingService));
      Program.MessageServiceHost.Open( );
    
      // other code
    }
    
    static void Application_ApplicationExit(object sender, EventArgs e)
    {
      if (Program.MessageServiceHost != null)
      {
        Program.MessageServiceHost.Close( );
        Program.MessageServiceHost = null;
      }
    }
  4. Compile and run the WindowsHost project to verify that there are no exceptions. Leave the host running for the next section of the lab.

Now you have created a Windows host for your services. Next, you will configure the client application to call the service.

Calling services on a non-UI thread

In this part of the lab, you will add code to the client application to invoke service operations.

  1. Go to the Client project and add a service reference to the MessagingService providing the base address http://localhost:8000 and using localhost as the namespace.

  2. Open Form1.cs and create a Click event handler for the Call Service button. Add code to the event handler to construct the proxy and call its SendMessage( ) function, as shown here:

    private void button1_Click(object sender, EventArgs e)
    {
      localhost.MessagingServiceClient proxy = new
    localhost.MessagingServiceClient( );
      proxy.SendMessage(string.Format("Hello from {0}", this.Text));
    }
  3. Compile and run the WindowsHost and the Client application. Click the Call Service button to invoke the MessagingService and a message box will display with the following information:

    Message 'Hello' from Client: Process [processId]' received on thread [threadId]:
    MessageLoop = False

    The value for threadId represents the server thread processing the call to SendMessage( ). You should note that this thread is different from the UI thread indicated in the Service Controller UI shown in Figure 4-2.

    The UI thread for the service host, Service Controller

    Figure 4-2. The UI thread for the service host, Service Controller

Calling services on the UI thread

Now you’ll switch gears and see what happens when you create the ServiceHost instance on the UI thread.

  1. Go to the WindowsHost project and open Program.cs and remove the code that you just wrote so that the file is left with only the code shown in Example 4-12.

    Example 4-12. Program.cs after removing ServiceHost code

    using System;
    using System.Collections.Generic;
    using System.Windows.Forms;
    
    namespace WindowsHost
    {
      static class Program
    
      {
        [STAThread]
        static void Main( )
        {
          Application.EnableVisualStyles( );
          Application.SetCompatibleTextRenderingDefault(false);
          Application.Run(new Form1( ));
        }
      }
    }
  2. Open Form1.cs in design view and add two Button controls. Set their Text properties to Start Service and Stop Service, respectively, as shown in Figure 4-3.

    The Service Controller UI after adding two Button controls

    Figure 4-3. The Service Controller UI after adding two Button controls

  3. Add code to the form to create and initialize the ServiceHost instance; to clean up the instance when the form is closed; and to start and stop the service through the command buttons. Add Click event handlers for each button and then open Form1.cs in code view. Add code to each of the handlers as shown in bold in Example 4-13.

    You’ll notice that the code is similar to what was removed from Program.cs, with the exception that you’re now scoping the ServiceHost instance to the form’s lifetime, allowing the user to start and stop the service, and cleaning up the ServiceHost immediately when the user confirms exiting the application.

    Example 4-13. Creating the ServiceHost instance on the UI thread

    public partial class Form1 : Form
    {ServiceHost m_serviceHost;
    
      public Form1( )
      {
        InitializeComponent( );
        this.button2.Enabled=false;
    
        m_serviceHost = new ServiceHost(typeof(Messaging.MessagingService));
      }
    
      private void button1_Click(object sender, EventArgs e)
      {
        this.button1.Enabled = false;
        this.button2.Enabled = true;
        m_serviceHost.Open( );
      }
    
      private void button2_Click(object sender, EventArgs e)
      {
        this.button1.Enabled = true;
        this.button2.Enabled = false;
        m_serviceHost.Close( );
      }
    
      private void Form1_FormClosing(object sender, FormClosingEventArgs e)
      {
        DialogResult result = MessageBox.Show("Are you sure you want to close the
    service?", "Service Controller", MessageBoxButtons.YesNo, MessageBoxIcon.Question,
    MessageBoxDefaultButton.Button2);
    
        if (result == DialogResult.Yes)
        {
          if (m_serviceHost != null)
          {
            m_serviceHost.Close( );
            m_serviceHost = null;
          }
        }
        else
          e.Cancel=true;
      }
    }
  4. Compile and run the WindowsHost and Client projects. This time, you’ll have to click Start Service in the Service Controller UI before you can invoke the service from the client. Once the service is started, click Call Service from the client and observe the message box text. This time, you’ll notice that the host’s UI thread matches that of the request thread and that the request thread is running on the message loop.

Preventing services from joining the UI thread

In this part of the lab, you’ll edit the MessagingService so that it explicitly prevents the service from running on the UI thread, using the OperationBehaviorAttribute.

  1. Go to the Messaging project and open MessagingService.cs. Add the ServiceBehaviorAttribute to the MessagingService type and set the synchronization context to false, as shown here:

    [ServiceBehavior(UseSynchronizationContext=false)]public class MessagingService : IMessagingService
  2. Compile and run the WindowsHost and Client projects again. Run the same tests as before. This time, observe that calls to the service operation are executed on a thread other than the UI thread.

In the following sections I’ll discuss the concepts introduced in this lab in more detail.

Callbacks and the UI Thread

In Chapter 3, I explained how to work with callbacks and duplex communication. In the section "One-Way and Duplex Communication,” you completed a lab that illustrates the use of one-way operations, callbacks, and duplex bindings. I also briefly touched on synchronization context for callbacks. In this section, I’ll give a quick review and discuss the similarities between how services and client callback types behave in relation to the UI thread.

Recall that when services support callbacks, the client exposes an endpoint to receive messages from the service. At the service, the service contract includes a callback contract as Example 4-14 illustrates. In this example, the service operation is one-way, as is the callback operation.

Example 4-14. Adding a callback contract to the MessagingService

[ServiceContract(Namespace = "http://www.thatindigogirl.com/samples/2006/06",CallbackContract=typeof(IMessagingServiceCallback))]
public interface IMessagingService
{
  [OperationContract(IsOneWay = true)]
  void SendMessage(string message);
}

public interface IMessagingServiceCallback
{
  [OperationContract(IsOneWay = true)]
  void MessageNotification(string message);
}

Clients implement the callback contract as shown in Example 4-15 and pass the callback instance to the proxy. For Windows clients, the default behavior is for callback operations to execute on the UI thread. If the service operation is one-way, this is a nonissue. If the service operation is not one-way, the UI thread will be blocking on the outgoing call and be unable to receive a callback on the UI thread.

Example 4-15. Implementation of IMessagingServiceCallback

class MessagingServiceCallback:IMessagingServiceCallback
{
  public void MessageNotification(string message)
  {
    MessageBox.Show(String.Format("Message '{0}' received on thread {1} :
MessageLoop = {2}", message, Thread.CurrentThread.GetHashCode( ),
Application.MessageLoop), "IMessagingServiceCallback.MessageNotification( )");
  }
}

// initializing the MessagingService proxyMessagingServiceCallback callbackType = new MessagingServiceCallback( );
InstanceContext context = new InstanceContext(callbackType);
m_proxy = new localhost.MessagingServiceClient(context);

In general practice, because clients can’t control when the service will invoke the callback, they should have callback operations execute on a new thread for non-one-way calls. This is achieved by applying the CallbackBehaviorAttribute to the callback type and setting UseSynchronizationContext to false. By default, UseSynchronizationContext is set to true, as with the ServiceBehaviorAttribute discussed earlier. Here is an example where the CallbackBehaviorAttribute is applied:

[CallbackBehavior(UseSynchronizationContext=false)]class MessagingServiceCallback:IMessagingServiceCallback

Tip

See the following code samples for an implementation of callbacks with and without UI thread synchronization:

  • <YourLearningWCFPath>\Samples\Hosting\Callbacks_Synchronized

  • <YourLearningWCFPath>\Samples\Hosting\Callbacks_NoSynchronization

UI Hosting Scenarios

Now that you have more information about the process of hosting in a Windows application, I’ll discuss some implementation patterns. The truth is that you will rarely, if ever, host services on a UI thread when they are deployed to server machines. So, most of what I have discussed in this section relates to client-side service deployment. Assuming that the service you are creating is associated with a user interface, and does not run on an unattended server machine, one of the following implementation techniques apply:

  • The service may be coupled to the host user interface such that as operations are invoked, the user interface is immediately updated. This means that the service operations must know something about the host UI to interact with it.

  • The service may provide information to its own user interface that can be shown or hidden at the discretion of the host application. In this case, the service knows about its own UI but exposes static operations for the host to show or hide that UI.

  • The service may not concern itself with a user interface, but host applications may choose to present information related to service execution. In this case, the service is independent of UI, but it may expose static properties or push data to a common data store that the host can access.

In this section, I’ll discuss how to handle each approach.

Coupling services to the host user interface

If the user interface exposed by the host is directly related to service functionality, it is likely that the service will not be exposed by another hosting environment. This removes the need to decouple service code from the host; in fact, it will probably reduce confusion if the service is part of the host assembly, as illustrated in Figure 4-4.

Coupling service and host

Figure 4-4. Coupling service and host

In this scenario, the following settings apply:

  • UseSynchronizationContext is set to true (the default).

  • The UI thread initializes the ServiceHost.

  • Services can safely communicate directly with forms and controls.

Since the service and host are coupled, to communicate with controls, the service can use the OpenForms collection for the application. For example, the following code assumes that the first form in the OpenForms collection is the MessagingForm:

MessagingForm f = Application.OpenForms[0] as MessagingForm;
f.AddMessage(s);

In this case, the MessagingForm exposes a public AddMessage( ) method that the service calls to add information to a ListBox control:

 void IMessagingForm.AddMessage(string message)
 {
   this.lstMessages.Items.Add(message);
 }

To achieve some measure of decoupling, and to support the case where the service is indeed hosted elsewhere, the service could look for a particular interface to be implemented on the form. Consider this interface, for example:

public interface IMessagingForm
{
  void AddMessage(string message);
}

If the form implements this interface, the service code changes to this:

IMessagingForm messagingForm = null;
foreach (Form f in Application.OpenForms)
{
  messagingForm = f as IMessagingForm;
  if (messagingForm != null) break;
}
if (messagingForm!=null)
  messagingForm.AddMessage(s);

Tip

See the following code samples illustrating service and host UI coupling:

  • <YourLearningWCFPath>\Samples\Hosting\CouplingServiceAndHostUI

  • <YourLearningWCFPath>\Samples\Hosting\CouplingServiceAndHostUI_InterfaceBased

Exposing a reusable service user interface

In some cases, it may make more sense to couple the service with its own user interface and provide methods that a host can call to show the interface on demand. This makes it possible to provide multiple hosts for the service and share a common UI for functionality directly associated with the service. It also makes it possible for hosts to determine if and when it is appropriate to show the UI. The assembly allocation is illustrated in Figure 4-5.

Coupling service with UI separate from host

Figure 4-5. Coupling service with UI separate from host

In this scenario, the following settings apply:

  • UseSynchronizationContext is set to true if all service operations communicate with the UI, and false if some operations require more throughput than that provided through the message loop.

  • The UI thread may or may not initialize the ServiceHost—this is host dependent.

  • Services may not be running on the UI thread, so the service UI should handle the possibility of communication from a non-UI thread.

In this case, the service could provide a static method so that the host application can show or hide the service UI. The service can directly access its UI through a static member, in this case sending messages to the UI only if the form is visible:

public class MessagingService : IMessagingService
{
  private static ServiceForm m_form;

   public void SendMessage(string message)
  {
    if (m_form != null && m_form.Visible)
      m_form.AddMessage(message);
  }

   // other code
}

Since the service is not guaranteed to run on the UI thread, the service form should encapsulate a check to see if calls should be explicitly invoked on the UI thread:

public void AddMessage(string message)
{
  if (this.InvokeRequired)
  {
    MethodInvoker del = delegate
      {
        this.listBox1.Items.Add(message);
      };
    this.Invoke(del);
  }
  else
    this.listBox1.Items.Add(message);
}

Tip

InvokeRequired is a property of forms and controls indicating if the calling thread is not the same as the thread where the form or control was constructed (usually the UI thread). If this property is set to true, the Invoke( ) method of the form or control must be used to interact with its methods and properties that interact with UI elements.

Code like this must be written for all communication with form and control members if service operations aren’t guaranteed to run on the UI thread.

Tip

The following code sample illustrates a service that owns its own UI: <YourLearningWCFPath>\Samples\Hosting\ ServiceWithThreadSafeUI.

Decoupling services from user interfaces

More than likely, services and UI are not coupled, but they may share a common data store. A user interface may be used to present this data to users, and it could be owned by the host, as illustrated in Figure 4-6.

Decoupling service from UI

Figure 4-6. Decoupling service from UI

In this case the following settings apply:

  • UseSynchronizationContext should be set to false so that the service can optionally have increased throughput via multithreading.

  • The service host may have a UI thread but it will be completely independent of the service code.

  • Services push data to a cache or data store.

  • Hosts present data from that same cache or data store without communicating with the service type.

Get Learning WCF now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.