|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionSimply put, duplex operations provide a mechanism for allowing a service to callback to the client. When a contract is defined for a service, it is possible to specify a corresponding callback contract. The standard service contract defines the operations of the service that can be invoked by the client. The callback contract defines the operations of the client that can be invoked by the service. It is the client's responsibility to implement the callback contract and host the callback object. Each time the client invokes a service operation that is associated to the callback contract, the client supplies the necessary information for the service to locate and invoke the callback operation on the client. One of the most popular uses for duplex operations seems to be for events. This is typically accomplished by using a publish-subscribe pattern. One or more clients will subscribe to the service. When something of interest occurs, the service will publish the information to the subscribed clients. This is the approach used by the demo. It is based on the concept of assisting the host of a party by tracking guests and their consumption of beer. Although intentionally humorous, it effectively illustrates the key concepts involved with duplex operations. Each guest subscribes to the service by joining the party. When something interesting happens, such as other guests joining/leaving the party as well as the consumption of beer, the service publishes the information to the subscribed clients.
Let's take a closer look at how the magic happens. Service Contract and Callback ContractAs usual, an interface is decorated with the [ServiceContract(
SessionMode = SessionMode.Required,
CallbackContract = typeof(IBeerInventoryCallback))]
public interface IBeerInventory
{
[OperationContract()]
int JoinTheParty(string guestName);
[OperationContract(IsOneWay = true)]
void MakeBeerRun(string guestName, int numberOfBeers);
[OperationContract(IsOneWay = true)]
void DrinkBeer(string guestName);
[OperationContract(IsOneWay = true)]
void LeaveTheParty(string guestName);
}
public interface IBeerInventoryCallback
{
[OperationContract(IsOneWay = true)]
void NotifyGuestJoinedParty(string guestName);
[OperationContract(IsOneWay = true)]
void NotifyBeerInventoryChanged(string guestName, int numberOfBeers);
[OperationContract(IsOneWay = true)]
void NotifyGuestLeftParty(string guestName);
}
It should be noted that the Service ImplementationIn order to invoke the client callback from the service, it is necessary to acquire a reference to the callback object. Each time the client invokes a service operation associated to the callback contract, it supplies a callback channel that can be used to communicate with the callback object. The callback channel can be found within the private List<IBeerInventoryCallback> _callbackList;
public int JoinTheParty(string guestName)
{
IBeerInventoryCallback guest =
OperationContext.Current.GetCallbackChannel<IBeerInventoryCallback>();
if (!_callbackList.Contains(guest))
{
_callbackList.Add(guest);
}
_callbackList.ForEach(
delegate(IBeerInventoryCallback callback)
{ callback.NotifyGuestJoinedParty(guestName); });
return _beerInventory;
}
The service stores a reference to the callback channel in a collection. When the service is ready to publish a notification to subscribers, each callback channel in the collection is invoked. Now, this is where you can run into some issues if your service isn't properly configured and/or implemented. Specifically, the concurrency model becomes a factor. Concurrency ModeBy default, WCF services are configured to be single-threaded. This means that only one thread will process messages at any given time for a service instance. Consequently, if additional messages arrive while a message is already being processed, they are blocked until the current message has completed processing and releases the lock. If you attempt to invoke a client callback during the middle of a service operation that is single-threaded, a nasty little scenario known as a deadlock will occur. This is due to the locking required by the concurrency model. When the callback operation is invoked on the client, the service must wait for the reply to return for processing. However, the service is already locked for the processing of the current operation. So, a deadlock occurs. Fortunately, WCF can detect when you attempt to do this sort of thing and throws an So, how do you invoke a callback in the middle of a service operation? It boils down to pretty much three choices:
The demo uses the third option. Client ImplementationAs previously mentioned, the client must implement the callback contract and host the callback object. For the demo, the callback contract has been directly implemented in the client form. When the form is loaded, an _proxy = new BeerInventoryServiceClient(new InstanceContext(this));
_proxy.Open();
UI Thread WoesNow, when the user interacts with the form, the corresponding operations are invoked on the service. Some of these operations result in a callback to the subscribed clients. However, this poses a problem that is specific to Windows form (and WPF) applications. If the UI thread invokes a service operation that results in a callback operation, a deadlock scenario will occur. Consider the following line of code that is executed when the Join Party button is clicked: this.BeerInventory = _proxy.JoinTheParty(this.txtGuestName.Text);
This may seem innocent at first, but consider what is taking place. When the service operation is invoked from the UI thread, it will block until the return value has been received. However, the service operation invokes a callback to the client prior to the sending the return value. The callback operation will be marshaled to the UI thread. Since the UI thread is still waiting for the return value, a deadlock will occur. The root of this problem has to do with synchronization contexts. By default, WCF provides thread affinity to the current synchronization context that is used to establish a connection with a service. All service requests and replies will be executed on the thread of the originating synchronization context. In some situations, this may be desired behavior. At other times, it may not. In our case, it is causing a problem. Synchronization ContextsFortunately, WCF provides a simple mechanism for overriding the automatic association of synchronization contexts. This behavior can be turned off by setting the [CallbackBehavior(UseSynchronizationContext = false)]
Unfortunately, this solves one problem and creates another for our demo. The callback operations on the client require updating attributes of the UI. But, only the UI thread can directly manipulate the properties of form controls. Since the callback operations will be running on a worker thread, it requires a little extra effort to manipulate the UI on the UI thread. There are a variety of ways to accomplish the task, but the demo employs the use of the
SendOrPostCallback callback =
delegate (object state)
{ this.WritePartyLogMessage(String.Format("{0} has joined the party.",
state.ToString())); };
_uiSyncContext.Post(callback, guestName);
ConclusionDuplex operations are a very powerful concept that is easily implemented in WCF. Using this ability, it is very easy to achieve event-like behavior between a service and client. Hopefully, this article has provided you with enough information to get started applying the concept.
|
||||||||||||||||||||||