在忙于长时间运行的操作时,Ping回复WCF服务

时间:2016-07-15 13:39:18

标签: c# wpf wcf

我正在使用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保持对象的状态 服务?
  • 是否有其他/更好的方法来ping WCF服务 可用性并在客户端上直观地显示结果?

1 个答案:

答案 0 :(得分:1)

使用单例服务,您将需要服务器实例的多线程实现来获得所需的行为,至少您需要在单独的线程上运行LongRunningOperation。如果此操作本质上不是线程安全的,那么您需要在实现中防止对其进行多次并发调用,特别是使用锁或信号量等。这样,当客户端调用LongRunningOperation()时,它在一个单独的线程中执行,并可以自由响应ping请求。

有很多方法可以实现这一点。顺便提一下你的问题,客户端似乎正在进行异步调用(因为它似乎在等待LongRunningOperation返回时发出ping请求) - 所以我也假设你对异步编程有一些了解。 WCF有一些built in ways of handling concurrency,但大多数文档都没有涵盖单例实例,因此您需要仔细阅读并专注于该特殊情况。

我使用async / await模式取得了最大的成功(请参阅herehere) - 一旦设置正确,我有一个非常可靠和可预测的模式,用于长时间运行的服务调用在有状态的单身人士服务中。

此外,就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.似乎也很重要。