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
{...}
Again, I will use the UI thread affinity as a model for demonstrating the way WCF is integrated with synchronization context, but as you will see later on, the discussion here is just as relevant with more powerful examples, especially those involving custom synchronization contexts.
You can use the UseSynchronizationContext
property 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 the thread-sensitive resource) that the service needs to interact with be the one that opens the host, 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 Thread
Static
Attribute
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; } private 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 in 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 Concurrency
Mode.
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 one another 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.
18.191.94.249