这是我的情况。我编写了一个WCF服务,该服务调用我们供应商的代码库之一来执行操作,例如Login,Logout等。此操作的一个要求是我们有一个后台线程来接收由该操作引起的事件。例如,Login操作在主线程上发送。然后,由于登录,从供应商服务接收到多个事件。可以收到1,2或几个事件。在计时器上运行的后台线程接收这些事件并在wcf服务中触发事件以通知新事件已到达。
我已在双工模式下实现了WCF服务,并计划使用回调来通知UI事件已到达。这是我的问题:如何将后台线程中的新事件发送到正在执行服务的线程?
现在,当我调用OperationContext.Current.GetCallbackChannel<IMyCallback>()
时,OperationContext为null。是否有一种标准模式来解决这个问题?
我在ServiceContract上使用PerSession作为我的SessionMode。
更新: 我想我会通过演示如何从供应商代码接收事件来使我的确切情况更加清晰。我的库接收每个事件,确定事件是什么,并触发该特定事件的事件。
我有另一个项目,它是一个专门用于连接供应商服务的类库。我将发布服务的整个实现,以提供更清晰的图片:
[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对象包含回调作为其属性之一,我以为我会像这样执行回调:
agentEvent.Callback = OperationContext.Current.GetCallbackChannel<ICallback>();
AgentEvent是服务中定义的对象:
[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看起来像这样:
public interface IVendorCallback
{
[OperationContract(IsOneWay = true)]
void SendEvent(AgentEvent agentEvent);
}
回调在客户端上实现,并使用AgentEvent的EventArgs属性在UI上填充数据。 如何将OperationContext.Current实例从主线程传递到后台线程?
答案 0 :(得分:5)
OperationContext.Current
仅在实际执行操作的线程上可用。如果您希望它可供工作线程使用,那么您需要实际将对该回调通道的引用传递给该线程。
因此,您的操作可能看起来有些模糊:
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
作为参数。
具体示例的更新:
似乎这里发生的是IVendorService
使用一些事件驱动的模型进行回调。
由于您正在使用InstanceContextMode.PerSession
,您实际上只需将回调存储在服务类本身的私有字段中,然后在事件处理程序中引用该字段。
[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(...);
}
}
如果您决定稍后切换到其他实例模式,那么这将无效,因为每个客户端将具有不同的回调。只要你保持会话模式,这应该没问题。
答案 1 :(得分:0)
将有问题的事件放在线程安全(已锁定)队列中,并让执行服务(当您调用它时)检查队列的计数。根据需要出列。
答案 2 :(得分:0)
您没有出现IVendorServer
,但是如果您不知道,则需要以下属性:
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IMyCallback))]
此外,您不需要后台线程来接收事件(我甚至不确定它是否以及如何适用)。
您所需要的只是在扩展IMyCallback
的类中实现回调API。
在该API中是收到回复的地方。
您还可以拥有经过身份验证的客户端的IMyCallback
个实例的集合,以及执行异步回调的单独线程,但这超出了您的问题的范围,并且需要进行有条理的规划。