我正在使用WPF和WCF开发客户端/服务器应用程序。 服务器应用程序托管一个WCF服务,该服务负责执行客户端请求并在发生某些事件时回调它们。
服务接口定义了一个包含所有OneWay
操作的双工回调合约。
(简化)IService
[ServiceContract(CallbackContract = typeof(ISrvServiceCallback))]
public interface ISrvService
{
[OperationContract(IsOneWay = true)]
void Ping();
[OperationContract(IsOneWay = true)]
void LongRunningOperation();
}
public interface ISrvServiceCallback
{
[OperationContract(IsOneWay = true)]
void PingReply();
[OperationContract(IsOneWay = true)]
void LongRunningOperationStatus(string reply);
}
服务需要保留一些根据客户端调用更改状态的对象。出于这个原因,我决定提供单身人士服务。
(简化)服务
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class SrvService : ISrvService
{
MyObject statusObject;
public void LongRunningOperation()
{
//calling back the client reporting operation status
OperationContext.Current.GetCallbackChannel<ISrvServiceCallback>()
.LongRunningOperationStatus("starting long running application");
statusObject.elaborateStatus();
//calling back the client reporting object status
OperationContext.Current.GetCallbackChannel<ISrvServiceCallback>()
.LongRunningOperationStatus("object status: " + statusObject.ToString());
}
public void Ping()
{
OperationContext.Current.GetCallbackChannel<ISrvServiceCallback>().PingReply();
}
public SrvService()
{
statusObject= ...statusObject init...
}
}
正如您所看到的,客户端调用的服务(每5秒)显示一个 Ping 操作,以检查服务器应用程序是否在网络上可用(每个客户端都有一个服务器连接图标 红色 =服务器不可用,绿色 =服务器不可用。)
当客户端请求长时间运行时,服务器开始处理该操作并且无法回复ping请求(客户端的服务器连接图标变为红色)。
长时间运行操作完成后,服务器将回复所有客户端发出的请求,服务器连接图标将变回绿色。)
我想找到一种开发服务的方法,以便服务器始终回复ping请求,也就是在长时间运行时。
InstanceContextMode.Single
保持对象的状态
服务? 答案 0 :(得分:1)
使用单例服务,您将需要服务器实例的多线程实现来获得所需的行为,至少您需要在单独的线程上运行LongRunningOperation。如果此操作本质上不是线程安全的,那么您需要在实现中防止对其进行多次并发调用,特别是使用锁或信号量等。这样,当客户端调用LongRunningOperation()时,它在一个单独的线程中执行,并可以自由响应ping请求。
有很多方法可以实现这一点。顺便提一下你的问题,客户端似乎正在进行异步调用(因为它似乎在等待LongRunningOperation返回时发出ping请求) - 所以我也假设你对异步编程有一些了解。 WCF有一些built in ways of handling concurrency,但大多数文档都没有涵盖单例实例,因此您需要仔细阅读并专注于该特殊情况。
我使用async / await模式取得了最大的成功(请参阅here和here) - 一旦设置正确,我有一个非常可靠和可预测的模式,用于长时间运行的服务调用在有状态的单身人士服务中。
此外,就ping而言,您确实指出您只是显示用户的连接状态,但是您是否计划将其用于控制(在拨打电话之前检查服务是否在线){ {3}}
编辑:使用async / await
的快速示例[ServiceContract]
public interface ISrvService()
{
[OperationContract]
bool Ping(); // doesnt need to be async
[OperationContract]
Task<string> LongRunningOperation();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
public class SrvService : ISrvService
{
MyObject statusObject;
public async Task LongRunningOperation()
{
// lock/semaphore here if needed
await Task.Run(() => statusObject.elaborateStatus()); // you could impliment elaborateStatus() as an async Task method and call it without Task.Run
return statusObject.ToString();
}
public bool Ping()
{
return true;
}
public SrvService()
{
statusObject= ...statusObject init...
}
}
public class SrvClient : ClientBase<ISrvService>
{
public async Task<string> LongRunningOperation()
{
return await base.Channel.LongRunningOperation();
}
public async Task<bool> Ping()
{
// note that we still call this with an await. In the client we are awaiting the wcf service call
// this is independent of any async/await that happens on the server
return await Task.Run(() => base.Channel.Ping());
}
}
使用客户端:
public class SomeApplicationClass()
{
SrvClient Client;
DispatcherTimer PingTimer;
public SomeClass()
{
BasicHttpBinding binding = new BasicHttpBinding();
EndpointAddress endpoint = new EndpointAddress(
"http://...:8000/Service/Address");
OutpostClient = new OutpostRemoteClient(binding, endpoint);
// pingTimer setup
}
// async voids are scary, make sure you handle exceptions inside this function
public async void PingTimer_Tick()
{
try
{
await Client.Ping();
// ping succeeded, do stuff
}
catch // specify exceptions here
{
// ping failed, do stuff
}
}
public async Task DoTheLongRunningOperation()
{
// set busy variables here etc.
string response = await Client.LongRunningOperation();
// handle the response status here
}
}
同样there is a lot of discussions here on why you should avoid it.似乎也很重要。