Jasper Chan
Jasper Chan

Reputation:

WCF ServiceHost.Close() Delay

I have a simple WCF duplex TCP service that I am trying to stop programmatically. If I don't have any connected users, ServiceHost.Close() is very quick but if I even have one connected user, I find the Close() function to take quite a bit of time, sometimes >30seconds. Is this usual behavior?

On the other hand, Abort() is almost instantaneous and I am tempted to use that instead.

Upvotes: 6

Views: 10595

Answers (5)

Aelphaeis
Aelphaeis

Reputation: 2613

I noticed this problem as well. My code looks originally looked like this :

[TestMethod]
[Timeout(2000)]
public void ApiClientTest()
    {
        bool ApiSuccessSet, ClientSuccessSet = ApiSuccessSet = false;
        Api apiService = new ApiTestService();
        var clientService = new ClientTestService();

        ServiceHost clientHost = new ServiceHost(clientService, new Uri(PipeService));
        ServiceHost apiHost = new ServiceHost(apiService, new Uri(PipeService));

        //To let us know the services were successfully opened
        clientHost.Opened += (s, e) => ClientSuccessSet = true;
        apiHost.Opened += (s, e) => ApiSuccessSet = true;
        clientHost.AddServiceEndpoint(typeof(IClientService), new NetNamedPipeBinding(), ClientPipeServiceName);
        apiHost.AddServiceEndpoint(typeof(IApiService), new NetNamedPipeBinding(), ApiPipeServiceName);
        clientHost.BeginOpen(OnOpen, clientHost);
        apiHost.BeginOpen(OnOpen, apiHost);

        //This allows both services to be open for communication.
        while (!ApiSuccessSet || !ClientSuccessSet)
            Thread.Yield();


        EndpointAddress ApiEndpoint = new EndpointAddress(PipeService + @"/" + ApiPipeServiceName);
        EndpointAddress clientEndpoint = new EndpointAddress(PipeService + @"/" + ClientPipeServiceName);

        InstanceContext context = new InstanceContext((IClientCallback)new TestClientCallback());
        var ClientChannelFactory = new DuplexChannelFactory<IClientService>(context, new NetNamedPipeBinding(), clientEndpoint);
        var ApiChannelFactory = new ChannelFactory<IApiService>(new NetNamedPipeBinding(), ApiEndpoint);
        var ClientChannel = ClientChannelFactory.CreateChannel();
        var ApiChannel = ApiChannelFactory.CreateChannel();


        clientHost.Close();
        apiHost.Close();
    }

void OnOpen(IAsyncResult ar)
    {
        ServiceHost service = (ServiceHost)ar.AsyncState;
        service.EndOpen(ar);
    }

I noticed that the this code took 20 secondsto run. I then decided to close the channel factories like this :

    [TestMethod]
    [Timeout(2000)]
    public void ApiClientTest()
    {
        bool ApiSuccessSet, ClientSuccessSet = ApiSuccessSet = false;
        Api apiService = new ApiTestService();
        var clientService = new ClientTestService();

        ServiceHost clientHost = new ServiceHost(clientService, new Uri(PipeService));
        ServiceHost apiHost = new ServiceHost(apiService, new Uri(PipeService));

        //To let us know the services were successfully opened
        clientHost.Opened += (s, e) => ClientSuccessSet = true;
        apiHost.Opened += (s, e) => ApiSuccessSet = true;
        clientHost.AddServiceEndpoint(typeof(IClientService), new NetNamedPipeBinding(), ClientPipeServiceName);
        apiHost.AddServiceEndpoint(typeof(IApiService), new NetNamedPipeBinding(), ApiPipeServiceName);
        clientHost.BeginOpen(OnOpen, clientHost);
        apiHost.BeginOpen(OnOpen, apiHost);


        //This allows both services to be open for communication.
        while (!ApiSuccessSet || !ClientSuccessSet)
            Thread.Yield();


        EndpointAddress ApiEndpoint = new EndpointAddress(PipeService + @"/" + ApiPipeServiceName);
        EndpointAddress clientEndpoint = new EndpointAddress(PipeService + @"/" + ClientPipeServiceName);

        InstanceContext context = new InstanceContext((IClientCallback)new TestClientCallback());
        var ClientChannelFactory = new DuplexChannelFactory<IClientService>(context, new NetNamedPipeBinding(), clientEndpoint);
        var ApiChannelFactory = new ChannelFactory<IApiService>(new NetNamedPipeBinding(), ApiEndpoint);
        var ClientChannel = ClientChannelFactory.CreateChannel();
        var ApiChannel = ApiChannelFactory.CreateChannel();


        ClientChannelFactory.Close();
        ApiChannelFactory.Close();
        clientHost.Close();
        apiHost.Close();
    }

This leads me to believe that the long time it takes is disposing the client's instance context.

I suspect that there are 3 ways to handle this solution better.

The first is to create a function on the client that manages ending the session. This way you can call that method before the service plans to shut down and it will speed up the shut down time.

the second is to close asynchronously and do other processing while the connection is closing.

The third is to program into the client when to close the connection (especially if you control both the client and the service) so that the client can terminate the session itself and the service can shut down gracefully and quickly.

Upvotes: 0

Scott Weinstein
Scott Weinstein

Reputation: 19117

It may be. The docs state that

The Close method allows any unfinished work to be completed before returning. For example, finish sending any buffered messages.

There is an overload to Close() which takes a TimeSpan (and throws if the timespan is exceeded)

Abort() looks like the best way to stop a WCF host without delay.

Upvotes: 10

John Hatton
John Hatton

Reputation: 1784

Make sure you're closing the client-side connection, like this:

var channel = factory.CreateChannel();
var channel.DoSomethingForMe();
(channel as ICommunicationObject).Close();

If you don't do this Close() on the channel, Close() on the server waits a very, very long time, even if you specify a short timeout.

Upvotes: 8

Kyle Lahnakoski
Kyle Lahnakoski

Reputation: 934

I could see the benefit of Abort() over Close() for expediency, but I would imagine something bad may happen. In my case, I want to wait for Close() so I can reuse the port(s). This code will wait for the services to be actually closed before resuming.

Semaphore cont=new Semaphore(0, 1);
foreach (ServiceHost s in WCFServices) {
    try {
        s.Closed += delegate(Object o, System.EventArgs n) {
            cont.Release();
        };
        s.Close();
        cont.WaitOne();                 
    } catch (Exception) {
    }//try
}//for

Upvotes: 0

AgileJon
AgileJon

Reputation: 53606

If you are ok with killing any in-progress service calls then Abort() is the way to go. Close() is the polite way of closing down a service.

Upvotes: 1

Related Questions