The programming techniques shown so far put the onus of accessing the resource on the
correct thread squarely on the service or resource developer. It would be preferable if the
service had a way of associating itself with a particular synchronization context, and could
have WCF detect that context and automatically marshal the call from the worker thread to
the associated service synchronization context. In fact, WCF lets you do just that. You can
instruct WCF to maintain an affinity between all service instances from a particular host
and a specific synchronization context. The ServiceBehavior
attribute offers the UseSynchronizationContext
Boolean property, defined as:
[AttributeUsage(AttributeTargets.Class)] public sealed class ServiceBehaviorAttribute : ... { public bool UseSynchronizationContext {get;set;} //More members }
The affinity between the service type, its host, and a synchronization context is locked
in when the host is opened. If the thread opening the host has a synchronization context and
UseSynchronizationContext
is true
, WCF will establish an affinity between that synchronization context and
all instances of the service hosted by that host. WCF will automatically marshal all
incoming calls to the service's synchronization context. All the thread-specific information
stored in the TLS, such as the client's transaction or the security information (discussed
in Chapter 10), will be marshaled correctly to the synchronization
context.
If UseSynchronizationContext
is false
, regardless of any synchronization context the opening
thread might have, the service will have no affinity to any synchronization context.
Likewise, even if UseSynchronizationContext
is true
, if the opening thread has no synchronization context the
service will not have one either.
The default value of UseSynchronizationContext
is
true
, so these definitions are equivalent:
[ServiceContract]
interface IMyContract
{...}
class MyService : IMyContract
{...}
[ServiceBehavior(UseSynchronizationContext = true
)]
class MyService : IMyContract
{...}
The classic use for UseSynchronizationContext
is to
enable the service to update user interface controls and windows directly, without
resorting to techniques such as those illustrated in Example 8-6 and Example 8-7. WCF greatly simplifies UI updates
by providing an affinity between all service instances from a particular host and a
specific UI thread. To achieve that end, host the service on the UI thread that also
creates the windows or controls with which the service needs to interact. Since the
Windows Forms synchronization context is established during the instantiation of the base
window, you need to open the host before that. For example, this sequence from Example 8-6:
ServiceHost host = new ServiceHost(typeof(MyService));
host.Open( );
Application.Run(new MyForm( )
);
will not have the host associate itself with the form synchronization context, since the host is opened before the form is created.
However, this minute change in the order of the lines of instantiation will achieve the desired effect:
Form form = new MyForm( )
;
ServiceHost host = new ServiceHost(typeof(MyService));
host.Open( );
Application.Run(form);
Although this change has no apparent effect in classic .NET, it is actually monumental
for WCF, since now the thread that opened the host does have a synchronization context,
and the host will use it for all calls to the service. The problem with this approach is
that it is fragile—most developers maintaining your code will not be aware that simply
rearranging the same independent lines of code will have this effect. It is also wrong to
design the form and the service that needs to update it so that they are both at the mercy
of the Main( )
method and the hosting code to such a
degree.
The simple solution is to have the window or form that the service needs to interact with be the one that opens the host before loading the form, as shown in Example 8-9.
Example 8-9. The form hosting the service
class MyService : IMyContract {...} partial class HostForm :Form
{ ServiceHost m_Host; Label m_CounterLabel; public HostForm( ) { InitializeComponent( ); m_Host = new ServiceHost(typeof(MyService));m_Host.Open( );
} void OnFormClosed(object sender,EventArgs e) { m_Host.Close( ); } public int Counter { get { return Convert.ToInt32(m_CounterLabel.Text); } set { m_CounterLabel.Text = value.ToString( ); } } } static class Program { static void Main( ) { Application.Run(new HostForm( )); } }
The service in Example 8-9 defaults to using
whichever synchronization context its host encounters. The form HostForm
stores the service host in a member variable so that the form can
close the service when the form is closed. The constructor of HostForm
already has a synchronization context, so when it opens the host, an
affinity to that synchronization context is established.
Even though the form hosts the service in Example 8-9, the service instances must have some
proprietary application-specific mechanism to reach into the form. If a service instance
needs to update multiple forms, you can use the Application.OpenForms
collections (as in Example 8-6) to find the correct form. Once the
service has the form, it can freely access it directly, as opposed to the code in Example 8-6, which required
marshaling:
class MyService : IFormManager { public void IncrementLabel( ) { HostForm form = Application.OpenForms[0] as HostForm; Debug.Assert(form != null); form.Counter++; } }
You could also store references to the forms to use in static variables, but the problem with such global variables is that if multiple UI threads are used to pump messages to different instances of the same form type, you cannot use a single static variable for each form type—you need a static variable for each thread used, which complicates things significantly.
Instead, the form (or forms) can store a reference to itself in the TLS, and have
the service instance access that store and obtain the reference. However, using the TLS
is a cumbersome and non-type-safe programming model. An improvement on this approach is
to use thread-relative static variables. By default, static variables are visible to all
threads in an app domain. With thread-relative static variables, each thread in the app
domain gets its own copy of the static variable. You use the ThreadStaticAttribute
to mark a static variable as thread-relative.
Thread-relative static variables are always thread-safe because they can be accessed
only by a single thread and because each thread gets its own copy of the static
variable. Thread-relative static variables are stored in the TLS, yet they provide a
type-safe, simplified programming model. Example 8-10 demonstrates this
technique.
Example 8-10. Storing form reference in a thread-relative static variable
partial class HostForm : Form { Label m_CounterLabel; ServiceHost m_Host;[ThreadStatic]
static
HostForm m_CurrentForm; public static HostForm CurrentForm { get { return m_CurrentForm; } set { m_CurrentForm = value; } } public int Counter { get { return Convert.ToInt32(m_CounterLabel.Text); } set { m_CounterLabel.Text = value.ToString( ); } } public HostForm( ) { InitializeComponent( );CurrentForm = this;
m_Host = new ServiceHost(typeof(MyService)); m_Host.Open( ); } void OnFormClosed(object sender,EventArgs e) { m_Host.Close( ); } } [ServiceContract] interface IFormManager { [OperationContract] void IncrementLabel( ); } class MyService : IFormManager { public void IncrementLabel( ) { HostForm form = HostForm.CurrentForm
; form.Counter++; } } static class Program { static void Main( ) { Application.Run(new HostForm( )); } }
The form HostForm
stores a reference to itself in
a thread-relative static variable called m_CurrentForm
. The service accesses the static property CurrentForm
and obtains a reference to the instance of
HostForm
on that UI thread.
Your service host process can actually have multiple UI threads, each pumping messages to its own set of windows. Such a setup is usually required with UI-intensive applications that want to avoid having multiple windows sharing a single UI thread and hosting the services, because while the UI thread is processing a service call (or a complicated UI update), not all of the windows will be responsive. Since the service synchronization context is established per host, if you have multiple UI threads you will need to open a service host instance for the same service type on each UI thread. Each service host will therefore have a different synchronization context for its service instances. As mentioned in Chapter 1, in order to have multiple hosts for the same service type, you must provide each host with a different base address. The easiest way of doing that is to provide the form constructor with the base address to use as a construction parameter. I also recommend in such a case to use base address-relative addresses for the service endpoints. The clients will still invoke calls on the various service endpoints, yet each endpoint will now correspond to a different host, according to the base address schema and the binding used. Example 8-11 demonstrates this configuration.
Example 8-11. Hosting on multiple UI threads
partial class HostForm : Form { public HostForm(string baseAddress
) { InitializeComponent( ); CurrentForm = this; m_Host = new ServiceHost(typeof(MyService),new Uri(baseAddress)
); m_Host.Open( ); } //Rest same as Example 8-10 } static class Program { static void Main( ) { ParameterizedThreadStart threadMethod = (baseAddress)=> { string address = baseAddress as string; Application.Run(new HostForm(address)); }; Thread thread1 = new Thread(threadMethod); thread1.Start("http://localhost:8001/"); Thread thread2 = new Thread(threadMethod); thread2.Start("http://localhost:8002/"); } } /* MyService same as Example 8-10 */ ////////////////////////////// Host Config File ////////////////////////////// <services> <service name = "MyService"> <endpoint address ="MyService"
binding = "basicHttpBinding" contract = "IFormManager" /> </service> </services> ////////////////////////////// Client Config File //////////////////////////// <client> <endpoint name = "Form A" address = "http://localhost:8001/MyService/" binding = "basicHttpBinding" contract = "IFormManager" /> <endpoint name = "Form B" Address = "http://localhost:8002/MyService/" binding = "basicHttpBinding" contract = "IFormManager" /> </client>
In Example 8-11, the Main( )
method launches two UI threads, each with its own instance of
HostForm
. Each form instance accepts as a
construction parameter a base address that it in turn provides for its own host
instance. Once the host is opened, it establishes an affinity to that UI thread's
synchronization context. Calls from the client to the corresponding base address are now
routed to the respective UI thread.
The main motivation for hosting a WCF service on a UI thread is when the service needs to update the UI or the form. The problem is, how does the service reach out and obtain a reference to the form? While the techniques and ideas shown in the examples so far certainly work, the separation between the service and the form is artificial. It would be simpler if the form were the service and hosted itself. For this to work, the form (or any window) must be a singleton service. The reason is that singleton is the only instancing mode that enables you to provide WCF with a live instance to host. In addition, it wouldn't be desirable to use a per-call form that exists only during a client call (which is usually very brief), or a sessionful form that only a single client can establish a session with and update. When a form is also a service, having that form as a singleton is the best instancing mode all around. Example 8-12 lists just such a service.
Example 8-12. Form as a singleton service
[ServiceContract] interface IFormManager { [OperationContract] void IncrementLabel( ); } [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single
)] partial class MyForm : Form,IFormManager { Label m_CounterLabel; ServiceHost m_Host; public MyForm( ) { InitializeComponent( ); m_Host = new ServiceHost(this
); m_Host.Open( ); } void OnFormClosed(object sender,EventArgs args) { m_Host.Close( ); } public void IncrementLabel( ) { Counter++; } public int Counter { get { return Convert.ToInt32(m_CounterLabel.Text); } set { m_CounterLabel.Text = value.ToString( ); } } }
MyForm
implements the IFormManager
contract and is configured as a WCF singleton service. MyForm
has a ServiceHost
as
a member variable, as before. When MyForm
constructs
the host, it uses the host constructor that accepts an object reference, as shown in Chapter 4. MyForm
passes itself
as the object. MyForm
opens the host when the form is
created and closes the host when the form is closed. Updating the form's controls as a
result of client calls is done by accessing them directly, because the form, of course,
runs in its own synchronization context.
You can streamline and automate the code in Example 8-12 using my FormHost<F>
class, defined as:
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] public abstract class FormHost<F> : Form where F : Form { public FormHost(params string[] baseAddresses); protected ServiceHost<F> Host {get;} }
Using FormHost<F>
, Example 8-12 is reduced to:
partial class MyForm : FormHost<MyForm>
,IFormManager
{
Label m_CounterLabel;
public MyForm( )
{
InitializeComponent( );
}
public void IncrementLabel( )
{
Counter++;
}
public int Counter
{
get
{
return Convert.ToInt32(m_CounterLabel.Text);
}
set
{
m_CounterLabel.Text = value.ToString( );
}
}
}
The Windows Forms designer is incapable of rendering a form that has an abstract
base class, let alone one that uses generics. You will have to change the base class
to Form
for visual editing, then revert to FormHost<F>
for debugging. To compensate, copy the
Debug configuration into a new solution configuration called Design, then add the
DESIGN
symbol to the Design configuration.
Finally, define the form to render properly in design mode and to execute properly in
debug and release modes:
#if DESIGN public partial class MyForm : Form,IFormManager #else public partial class MyForm : FormHost<MyForm>,IFormManager #endif {...}
Example 8-13 shows the implementation
of FormHost<F>
.
Example 8-13. Implementing FormHost<F>
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] public abstract class FormHost<F> : Form where F : Form { protected ServiceHost<F> Host {get;private set;} public FormHost(params string[] baseAddresses) { Host = new ServiceHost<F>(this as F,baseAddresses); Load += delegate { if(Host.State == CommunicationState.Created) { Host.Open( ); } }; FormClosed += delegate { if(Host.State == CommunicationState.Opened) { Host.Close( ); } }; } }
FormHost<F>
is an abstract generic class
configured as a singleton service. It takes a single type parameter, F
, which is constrained to be a Windows Forms Form
class. FormHost<F>
uses my ServiceHost<T>
as a member variable, specifying F
for the type parameter for the host. FormHost<F>
offers the host access to the derived
forms, mostly for advanced configuration, so the Host
property is marked as protected
. The constructor of
FormHost<F>
creates the host, but does not
open it. The reason is that the subform may want to perform some host initialization,
such as configuring a throttle, and this initialization can only be done before opening
the host. The subclass should place that initialization in its own constructor:
public MyForm( ) { InitializeComponent( ); Host.SetThrottle(10,20,1); }
To allow for this, the constructor uses an anonymous method to subscribe to the
form's Load
event, where it first verifies that the
subform has not yet opened the host and then opens it. In a similar manner, the
constructor subscribes to the form's FormClosed
event, where it closes the host.
Whenever you use hosting on the UI thread (or in any other case of a single-thread
affinity synchronization context), deadlocks are possible. For example, the following
setup is guaranteed to result with a deadlock: a Windows Forms application is hosting a
service with UseSynchronizationContext
set to true
, and UI thread affinity is established; the Windows Forms
application then calls the service in-proc over one of its endpoints. The call to the
service blocks the UI thread, while WCF posts a message to the UI thread to invoke the
service. That message is never processed due to the blocking UI thread—hence the
deadlock.
Another possible case for a deadlock occurs when a Windows Forms application is
hosting a service with UseSynchronizationContext
set to
true
and UI thread affinity established. The service
receives a call from a remote client, which is marshaled to the UI thread and eventually
executed on that thread. If the service is allowed to call out to another service, that
may result in a deadlock if the callout causality tries somehow to update the UI or call
back to the service's endpoint, since all service instances associated with any endpoint
(regardless of the service instancing mode) share the same UI thread. Similarly, you risk
a deadlock if the service is configured for reentrancy and it calls back to its client: a
deadlock will occur if the callback causality tries to update the UI or enter the service,
since that reentrance must be marshaled to the blocked UI thread.
Every client call to a service hosted on the UI thread is converted to a Windows
message and is eventually executed on the UI thread—the same thread that is responsible
for updating the UI and for continuing to respond to user input, as well as updating the
user about the state of the application. While the UI thread is processing the service
call, it does not process UI messages. Consequently, you should avoid lengthy processing
in the service operation, because that can severely degrade the UI's responsiveness. You
can alleviate this somewhat by pumping Windows messages in the service operation, either
by explicitly calling the static method Application.DoEvents(
)
to process all the queued-up Windows messages or by using a method such as
MessageBox.Show( )
that pumps some but not all of
the queued messages. The downside of trying to refresh the UI this way is that it may
dispatch queued client calls to the service instance and may cause unwanted reentrancy
or a deadlock.
To make things even worse, what if clients dispatch a number of calls to the service all at once? Depending on the service concurrency mode (discussed next) even if those service calls are of short duration, the calls will all be queued back-to-back in the Windows message queue, and processing them in order might take time—and all the while, the UI will not be updated.
Whenever you're hosting on a UI thread, carefully examine the calls' duration and
frequency to see whether the resulting degradation in UI responsiveness is acceptable.
What is acceptable may be application-specific, but as a rule of thumb, most users will
not mind a UI latency of less than half a second, will notice a delay of more than three
quarters of a second, and will be annoyed if the delay is more than a second. If that is
the case, consider hosting parts of the UI (and the associated services) on multiple UI
threads, as explained previously. By having multiple UI threads you maximize
responsiveness, because while one thread is busy servicing a client call, the rest can
still update their windows and controls. If using multiple UI threads is impossible in
your application and processing service calls introduces unacceptable UI responsiveness,
examine what the service operations do and what is causing the latency. Typically, the
latency would be caused not by the UI updates but rather by performing lengthy
operations, such as calling other services, or computational-intensive operations, such
as image processing. Because the service is hosted on the UI thread, WCF performs all of
that work on the UI thread, not just the critical part that interacts with the UI
directly. If that is indeed your situation, disallow the affinity to the UI thread
altogether by setting UseSynchronizationContext
to
false
:
[ServiceBehavior(UseSynchronizationContext = false)] class MyService : IMyContract { public void MyMethod( ) { Debug.Assert(Application.MessageLoop == false); //Rest of the implementation } }
(You can even assert that the thread executing the service call does not have a
message loop.) Perform the lengthy operations on the incoming worker thread, and use
safe controls (such as SafeLabel
) to marshal the
calls to the UI thread only when required, as opposed to all the time. The downside of
this approach is that it is an expert programming model: the service cannot be the
window or form itself (by relying on the simplicity of FormHost<F>
), so you need a way of binding to the form, and the
service developer has to work together with the UI developers to ensure they use the
safe controls or provide access to the form's synchronization context.
A service with a UI thread affinity is inherently thread-safe because only that UI
thread can ever call its instances. Since only a single thread (and the same thread, at
that) can ever access an instance, that instance is by definition thread-safe. Since the
service is single-threaded anyway, configuring the service with ConcurrencyMode.Single
adds no safety. When you configure with ConcurrencyMode.Single
, concurrent client calls are first
queued up by the instance lock and then dispatched to the service's message loop one at
a time, in order. These client calls are therefore given the opportunity of being
interleaved with other UI Windows messages. ConcurrencyMode.Single
thus yields the best responsiveness, because the UI
thread will alternate between processing client calls and user interactions. When you
configure the service with ConcurrencyMode.Multiple
,
client calls are dispatched to the service message loop as soon as they arrive off the
channel and are invoked in order. The problem is that this mode allows the possibility
of a batch of client calls arriving either back-to-back or in close proximity to each
other in the Windows message queue, and while the UI thread processes that batch, the UI
will be unresponsive. Consequently, ConcurrencyMode.Multiple
is the worst option for UI responsiveness. When
configured with ConcurrencyMode.Reentrant
, the
service is not reentrant at all, and deadlocks are still possible, as explained at the
beginning of this section. Clearly, the best practice with UI thread affinity is to
configure the service with ConcurrencyMode.Single
.
Avoid ConcurrencyMode.Multiple
due to its detrimental
effect on responsiveness and ConcurrencyMode.Reentrant
due to its unfulfilled ability.
3.141.200.3