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.
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
.
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.
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.
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.
To get started, the first thing you’ll do is set up the host.
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.
Modify the
WindowsHost
project to host theMessagingService
type from theMessaging
assembly. Start by adding a reference to theMessaging
project so that you’ll have access to the service.Now, add a
<system.serviceModel>
section to the app.config for theWindowsHost
project. Add a<service>
section for theMessaging.MessagingService
type with aNetTcpBinding
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
.
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.
Go to the
WindowsHost
project and add a reference to theSystem.ServiceModel
assembly.Next, open Program.cs and add code to construct a static reference to the
ServiceHost
instance, scoped to the application domain. Initialize theServiceHost
instance before theApplication.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( )); } }
With the code just added, the
ServiceHost
lifetime is for the duration of the application. Once the application is closed, its application domain and theServiceHost
instance will be destroyed along with it. As a matter of good practice, theServiceHost
should be closed immediately when the application is exiting to prevent new requests from being processed. In the Program.cs file where theServiceHost
is created, add some code to hook theApplicationExit
event and call theClose( )
method of theServiceHost
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; } }
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.
In this part of the lab, you will add code to the client application to invoke service operations.
Go to the
Client
project and add a service reference to theMessagingService
providing the base address http://localhost:8000 and usinglocalhost
as the namespace.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)); }
Compile and run the
WindowsHost
and theClient
application. Click the Call Service button to invoke theMessagingService
and a message box will display with the following information:Message 'Hello' from Client: Process [
processId
]' received on thread [threadId
]: MessageLoop = FalseThe value for
threadId
represents the server thread processing the call toSendMessage( )
. You should note that this thread is different from the UI thread indicated in the Service Controller UI shown in Figure 4-2.
Now you’ll switch gears and see what happens when you create the ServiceHost
instance on the UI thread.
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( )); } } }
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.
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 theServiceHost
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; } }
Compile and run the
WindowsHost
andClient
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.
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
.
Go to the
Messaging
project and open MessagingService.cs. Add theServiceBehaviorAttribute
to theMessagingService
type and set the synchronization context tofalse
, as shown here:[ServiceBehavior(UseSynchronizationContext=false)]
public class MessagingService : IMessagingServiceCompile and run the
WindowsHost
andClient
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.
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
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.
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.
In this scenario, the following settings apply:
UseSynchronizationContext
is set totrue
(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
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.
In this scenario, the following settings apply:
UseSynchronizationContext
is set totrue
if all service operations communicate with the UI, andfalse
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.
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.
In this case the following settings apply:
UseSynchronizationContext
should be set tofalse
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.