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

Ami Bar picture Ami Bar · Dec 6, 2008 · Viewed 13.8k times · Source

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?

Answer

Artru picture Artru · Jan 24, 2012

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.