WCF服务与来自后台线程的回调?

问题描述:

这是我的情况.我已经编写了WCF服务,该服务调用了我们供应商的代码库之一来执行诸如Login,Logout等之类的操作.此操作的要求是,我们有一个后台线程来接收由于该操作而产生的事件.例如,登录操作是在主线程上发送的.然后,作为登录的结果,从供应商服务接收回了几个事件.可能会收到1个,2个或几个事件.在计时器上运行的后台线程接收这些事件,并在wcf服务中触发一个事件,以通知新事件已到达.

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.

我已经在双工模式下实现了WCF服务,并计划使用回调来通知UI事件已到达.这是我的问题:如何从后台线程向执行服务的线程发送新事件?

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?

现在,当我调用OperationContext.Current.GetCallbackChannel<IMyCallback>()时,OperationContext为null.有没有解决这个问题的标准模式?

Right now, when I call OperationContext.Current.GetCallbackChannel<IMyCallback>(), the OperationContext is null. Is there a standard pattern to get around this?

我正在使用PerSession作为ServiceContract上的SessionMode.

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
                             };
    }
}

AgentEvent对象包含回调作为它的属性之一,我想我应该像这样执行回调:

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>();

AgentEvent是服务中定义的对象:

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看起来像这样:

IVendorCallback looks like this:

    public interface IVendorCallback
    {
        [OperationContract(IsOneWay = true)]
        void SendEvent(AgentEvent agentEvent);
    }

回调在客户端上实现,并使用AgentEvent的EventArgs属性填充UI上的数据. 如何将OperationContext.Current实例从主线程传递到后台线程?

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仅在实际执行该操作的线程上可用.如果希望它可用于工作线程,则实际上需要将对回调通道的引用传递给该线程.

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);
        });
    }
}

这是使用ThreadPool和匿名方法变量捕获的简单方法.如果要使用自由运行的线程来执行此操作,则必须使用ParameterizedThreadStart代替,并将callback作为参数传递.

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.

针对特定示例进行更新:

似乎这里发生的是IVendorService使用某种事件驱动的模型进行回调.

Seems that what's going on here is that the IVendorService uses some event-driven model for callbacks.

由于使用的是InstanceContextMode.PerSession,因此实际上您可以仅将回调存储在服务类本身的私有字段中,然后在事件处理程序中引用该字段.

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.