带有来自后台线程的回调的WCF服务?
这是我的情况。我写了一个WCF服务,它调用我们供应商的代码库来执行操作,例如登录,注销等。此操作的要求是我们有一个后台线程来接收由此操作导致的事件。例如,登录操作在主线程上发送。然后,作为登录的结果,从供应商服务接收到几个事件。可以收到1个,2个或几个事件。后台线程运行在一个定时器上,接收这些事件并在wcf服务中触发一个事件来通知新事件已经到达。带有来自后台线程的回调的WCF服务?
我已经在双工模式下实现了WCF服务,并且计划使用回调来通知UI事件已经到达。这是我的问题:我如何从后台线程发送新事件到正在执行服务的线程?
现在,当我拨打OperationContext.Current.GetCallbackChannel<IMyCallback>()
时,OperationContext为空。有没有一个标准模式来解决这个问题?
我在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是在服务中定义的对象:
0123:[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 porperty在UI上填充数据。 如何将主线程的OperationContext.Current实例传递给后台线程?
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(...);
}
}
如果您决定切换到一个不同的实例模式以后,那么这将无法工作,因为每个客户都会有不同的回调。只要你保持会话模式,这应该没问题。
@Aaronaught,我添加了特定于我的实例的代码。有关实施该方案的任何想法? 具体来说,我登录()方法是不依赖于任何方式回调。 LoggedIn事件发生在登录启动后,但有时不会被接收(如果发生通信故障等)。出于这个原因,我从我的后端库中发出事件以指示它们何时收到。 – 2010-04-09 12:57:15
完美!将回调设置为局部变量有诀窍。谢谢你的帮助! – 2010-04-09 15:38:15
将相关事件置于线程安全(锁定)队列中,并让执行中的服务(如您调用它)检查队列的计数。根据需要出列。
您没有提出IVendorServer
,但以防万一你不知道,它需要以下属性:
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(IMyCallback))]
而且,你并不需要一个后台线程接收事件(I”米甚至不知道是否以及如何适用)。 所有你需要的是落实在扩展IMyCallback
类回调API。 在该API中收到答复。
您也可以拥有已验证客户端的IMyCallback
实例集合,以及执行异步回调的单独线程,但这超出了您的问题范围,并且需要进行有条不紊的规划。
您的更新中没有足够的信息来真正理解正在发生的事情。此代码位于何处?你如何执行“登录”?它是某种异步方法吗?那个'AgentEvent'发生了什么?(它实际使用在哪里)?那么'agentEvent.Callback'的类型是什么以及它的值如何/何时被使用? – Aaronaught 2010-04-09 13:33:54
我添加了一个更完整的例子,我正在尝试做什么。我收到的事件是从我在服务中使用的图书馆中发出的,这就是为什么我必须订阅它们。 – 2010-04-09 13:50:51