Here is my situation. I have written a WCF service which calls into one of our vendor's code bases to perform operations, such as Login, Logout, etc. A requirement of this operation is that we have a background thread to receive events as a result of that action. For example, the Login action is sent on the main thread. Then, several events are received back from the vendor service as a result of the login. There can be 1, 2, or several events received. The background thread, which runs on a timer, receives these events and fires an event in the wcf service to notify that a new event has arrived.
I have implemented the WCF service in Duplex mode, and planned to use callbacks to notify the UI that events have arrived. Here is my question: How do I send new events from the background thread to the thread which is executing the service?
Right now, when I call OperationContext.Current.GetCallbackChannel<IMyCallback>()
, the OperationContext is null. Is there a standard pattern to get around this?
I am using PerSession as my SessionMode on the ServiceContract.
UPDATE: I thought I'd make my exact scenario clearer by demonstrating how I'm receiving events from the vendor code. My library receives each event, determines what the event is, and fires off an event for that particular occurrence.
I have another project which is a class library specifically for connecting to the vendor service. I'll post the entire implementation of the service to give a clearer picture:
[ServiceBehavior(
InstanceContextMode = InstanceContextMode.PerSession
)]
public class VendorServer:IVendorServer
{
private IVendorService _vendorService; // This is the reference to my class library
public VendorServer()
{
_vendorServer = new VendorServer();
_vendorServer.AgentManager.AgentLoggedIn += AgentManager_AgentLoggedIn; // This is the eventhandler for the event which arrives from a background thread
}
public void Login(string userName, string password, string stationId)
{
_vendorService.Login(userName, password, stationId); // This is a direct call from the main thread to the vendor service to log in
}
private void AgentManager_AgentLoggedIn(object sender, EventArgs e)
{
var agentEvent = new AgentEvent
{
AgentEventType = AgentEventType.Login,
EventArgs = e
};
}
}
The AgentEvent object contains the callback as one of its properties, and I was thinking I'd perform the callback like this:
agentEvent.Callback = OperationContext.Current.GetCallbackChannel<ICallback>();
The AgentEvent is an object defined in the service:
[DataContract]
public class AgentEvent
{
[DataMember]
public EventArgs EventArgs { get; set; }
[DataMember]
public AgentEventType AgentEventType { get; set; }
[DataMember]
public DateTime TimeStamp{ get; set; }
[DataMember]
public IVendorCallback Callback { get; set; }
}
IVendorCallback looks like this:
开发者_StackOverflow社区 public interface IVendorCallback
{
[OperationContract(IsOneWay = true)]
void SendEvent(AgentEvent agentEvent);
}
The callback is implemented on the client and uses the EventArgs porperty of the AgentEvent to populate data on the UI. How would I pass the OperationContext.Current instance from the main thread into the background thread?
OperationContext.Current
is only available on the thread that is actually executing the operation. If you want it to be available to a worker thread, then you need to actually pass a reference to the callback channel to that thread.
So your operation might look something vaguely like:
public class MyService : IMyService
{
public void Login()
{
var callback =
OperationContext.Current.GetCallbackChannel<ILoginCallback>();
ThreadPool.QueueUserWorkItem(s =>
{
var status = VendorLibrary.PerformLogin();
callback.ReportLoginStatus(status);
});
}
}
This is a straightforward way of doing it using the ThreadPool
and anonymous method variable capturing. If you want to do it with a free-running thread, you'd have to use a ParameterizedThreadStart
instead and pass the callback
as the parameter.
Update for specific example:
Seems that what's going on here is that the IVendorService
uses some event-driven model for callbacks.
Since you're using InstanceContextMode.PerSession
, you can actually just store the callback in a private field of the service class itself, then reference that field in your event handler.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
public class VendorServer : IVendorServer
{
private IMyCallback callback;
private IVendorService vendorService;
public VendorServer()
{
callback = OperationContext.Current.GetCallbackChannel<IMyCallback>();
vendorService = new VendorService();
vendorService.AgentManager.AgentLoggedIn += AgentManager_AgentLoggedIn;
}
public void Login(string userName, string password, string stationId)
{
vendorService.Login(userName, password, stationId);
}
private void AgentManager_AgentLoggedIn(object sender, EventArgs e)
{
callback.ReportLoggedIn(...);
}
}
If you decide to switch to a different instance mode later on then this won't work, because each client will have a different callback. As long as you keep it in session mode, this should be fine.
Put the events in question in a thread-safe (Locked) queue and have the executing service (as you call it) check the count of the queue. Dequeue as needed.
You didn't present IVendorServer
, but just in case you don't know, it requires the following attribute:
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IMyCallback))]
Also, you don't need a background thread to receive events (I'm not even sure if and how it's applicable).
All you need is to implement the callback API in the class that extends IMyCallback
.
Within that API is where the reply is received.
You could also have a collection of IMyCallback
instances of the authenticated clients, plus a separate thread to execute asynchronous callbacks, but that's way beyond the scope of your question and is something that requires methodical planning.
精彩评论