What happens in WCF to methods with IsOneWay=true at application termination

14.1k Views Asked by At

I have a client application that once in while notifies about its progress a service. The method call to the service is marked with IsOneWay=true, because the notification doesn't need any return value and I don't want to delay.

The client may notify about errors to the service, and afterward it terminates.

The question is: does a oneway method call returns to the caller code after it sent the message? or it queues the message and later on it is sent by another thread?

The two processes (the client and the service) are on the same machine, and I noticed that sometimes (when the machine is overloaded) the service doesn't get the error notification. I suspect that the second option I mentioned happens, but I am not sure.

If I am right, how can I make sure the notification is send and keep the method oneway?

6

There are 6 best solutions below

0
On

Good question. Before client application calls a method it opens the channel. The channel is used for all data communication. There are two ways of sending: 1) reliable session - when your packets are deleveried reliably and cracked packets are resent, 2) ordering - when requests on the service are computed in the order they were transfered from client (not how they are delivered). If you have reliable ordered session and service host is getting some problems with data your after closing application, host will try to ask the client resent data and after no responce reject all you request. In other situation (unreliable) after opening channel you can send data and destroy communication, oneway method will compute you request, if there will not be exception.

To test some possibilities with service problem (not exactly your client paroblem however is helpful) I create a solution:

1) Library project "WcfContracts" with one file "IService1.cs":

    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        void ThrowException();

        [OperationContract(IsOneWay=true)]
        void ThrowExceptionUseIsOneWay();
    }

2) Console project "WcfConsoleHoster" which has reference two "WcfContracts", and consists of the three files:

a) Service1.cs, which is service implementation

public class Service1 : WcfContracts.IService1
{    
        public void ThrowException()
        {
            throw new Exception("Basic exception");
        }

        public void ThrowExceptionUseIsOneWay()
        {
            throw new Exception("Basic exception using IsOneWay=true");
        }
}

b) Program.cs, which has default entry point and just starts the service

static void Main(string[] args)
    {
        ServiceHost host = new ServiceHost(typeof(Service1));
        host.Open();
        Console.WriteLine("host 1 opened");
        Console.ReadKey();
    }

c) Service "App.config"

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service behaviorConfiguration="behavourHttpGet" name="WcfConsoleHoster.Service1">
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8732/Design_Time_Addresses/WcfConsoleHoster/Service1/" />
          </baseAddresses>
        </host>
        <endpoint binding="wsHttpBinding" contract="WcfContracts.IService1" />        
        <endpoint address ="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="behavourHttpGet">
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

3) Console project "WcfConsoleClient", which simply calls the service

a) In "Program.cs"

Console.WriteLine("Wcf client. Press any key to start");
Console.ReadKey();
ChannelFactory<IService1> factory = new ChannelFactory<IService1>("Service1_Endpoint");
IService1 channel = factory.CreateChannel();
//Call service method
channel.ThrowException();

Console.WriteLine("Operation executed");
Console.ReadKey();

b) Client "App.config"

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <client>
      <endpoint name="Service1_Endpoint"
        address="http://localhost:8732/Design_Time_Addresses/WcfConsoleHoster/Service1/"
        binding="wsHttpBinding"
        contract="WcfContracts.IService1">
      </endpoint>
    </client>
  </system.serviceModel>
</configuration>

1. Throwing exception. First, we call two way method wich throws exception in the server host. Then this exception goes back to the client and channel raises it on the client side, application is destroyed. Of course you can handle this with try()catch{} block.

Let's look the same with one way method by calling channel.ThrowExceptionUseIsOneWay();. Exception is raised in the service host, but there is no exception on the client side and we get "Operation executed". It is important to realize that channel will be unavailable for the next use.

So IsOneWay=true works as expected - it sends message only in one way. You cannot return any object from method(void is expected) and you cannot use FaultContract, or get InvalidOperationException after the service start up.

2. Thread.Sleep(). Next test is on massive operation with Thread.Sleep(). IService1 is extended to

  [OperationContract]
  int ThreadSleep();

  [OperationContract(IsOneWay=true)]

and the realization in Service1.cs is waiting for 5 seconds

public int ThreadSleep()
{
    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5));
    return 1;
}

public void ThreadSleepUseIsOneWay()
{
    System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5));
}

Now a bit modification for the client for counting elapsed calling time

System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
stopwatch.Start();
//call methode
channel.ThreadSleep();
stopwatch.Stop();
Console.WriteLine(string.Format("Operation executed in {0} seconds", stopwatch.Elapsed.Seconds));
Console.ReadKey();

Calling two way method ThreadSleep() has the result "Operation executed in 7 seconds" (5 sec for thread sleep + 2 sec for channel initialization).

One way method with calling channel.ThreadSleepUseIsOneWay() has the result "0 seconds"! There is no waiting for the service response!

It is best to use NetNamedPipeBinding, which is reliable and fast connection on the same machine.

0
On

This might be a good place to use a netMsmqBinding. All MSMQ messages are inherently OneWay because queues are inherently disconnected. Once the client queues the message with a netMsmq client, it can safely shutdown, and the server can later pickup the message and process it.

0
On

Check out this post for some detailed info: http://kennyw.com/?p=130

Also, I believe that if you have reliable messaging enabled that the request would be verified as sent successfully, but as the above post notes, the service will end the connection after that point.

1
On

You can't "make sure the notification is sent" ... and "keep the method oneway". That kinda goes against what "OneWay" means :)

If you want to ensure that the message is sent, it's ok to do TwoWay. You most likely won't notice the slight performance hit. And if the server and client are on the same machine as you mentioned... then you will not notice the performance hit at all.

0
On

I agree with Timothy. I also want to add that the WCF service keeps a queue for incoming messages. That queue may become full if the service isn't able to process messages as fast as they are coming in. When the incoming queue becomes full, WCF will drop new messages.

I'm not sure what happens on the client side if one-way messages are dropped though. I assume that no exception/fault is thrown but I don't know that for sure.

0
On

I had a similar problem (one-way call not doing anything or throwing an exception). I found that the deserilizer responsible for transforming the method argument from xml back into objects was creating an exception. My service method was never getting reached and not exception was getting returned because it was a one-way call. I didn't detect it until I temporarily disabled the 'one-way' so my exception got propagated back to the client.

Hope this helps someone else in similar situation.