Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Awaiting a service task gets TaskCanceledException: A task was canceled

I have one app (UWP - Win10) and a Windows service.

The service is running in background, and they were both developed in C#. "callAsync" is the method on the service. I am using await to call it on the client.

var obj = await callAsync(10);

The problem is: If this call takes less than 1min40s (100 seconds), then everything works ok. But if it takes more than 1min40s, then an exception will occur "TaskCanceledException: A task was canceled".

I have search SO and the web but still could not find any indication on how to resolve this "timeout" issue. I have added all the "open/close/receive/send" timeout flags on both app and service app.config, although the exception that is thrown in that case is different.

If I try with a simple delay in the client:

await Task.delay(200000); 

it works properly.

This service was added through VS2015 "Add Service Reference". I have also "attached" to the server and the server keeps running and prints in the console before and after logs (to confirm that everything is ok).

What am I missing? What configuration and where do I need to change so that the task can run for more than 1 minute and 40 seconds?

CODE:

Example of Server Pseudo-Code:

Interface File:

[ServiceContract(Namespace="http://.....")]
interface ICom {

   [OperationContract]
   int call(int val);

}

Service.cs

    public ServiceHost serviceHost = null;
    public BlaBlaWindowsService()
    {
        ServiceName = "BlaBlaWindowsService";
    }

    public static void Main()
    {
        ServiceBase.Run(new BlaBlaWindowsService());
    }


    protected override void OnStart(string[] args)
    {
        if (serviceHost != null)
        {
            serviceHost.Close();
        }

        serviceHost = new ServiceHost(typeof(BlaBlaService));

        serviceHost.Open();
    }

    protected override void OnStop()
    {
        if (serviceHost != null)
        {
            serviceHost.Close();
            serviceHost = null;
        }
    }
}

[RunInstaller(true)]
public class ProjectInstaller : Installer
{
    private ServiceProcessInstaller process;
    private ServiceInstaller service;

    public ProjectInstaller()
    {
        process = new ServiceProcessInstaller();
        process.Account = ServiceAccount.LocalSystem;
        service = new ServiceInstaller();
        service.ServiceName = "BlaBlaWindowsService";
        Installers.Add(process);
        Installers.Add(service);
    }
}

BlaBlaService.cs

class TPAService : ITPAComunication {

   public int call(int val) {

      System.Threading.Thread.Sleep(200000)
      return 0;
   }

}

App.config file:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<binding name="ServiceTimeout" closeTimeout="00:10:00" receiveTimeout="00:10:00" openTimeout="00:10:00" sendTimeout="00:10:00"/>
</bindings>
        <services>
          <service name="BlaBla.Service.Service"
                   behaviorConfiguration="ServiceBehavior">
            <host>
              <baseAddresses>
                <add baseAddress="http://localhost:8000/BlaBla/service"/>
              </baseAddresses>
            </host>
            <endpoint address=""
                      binding="basicHttpBinding"
                      bindingConfiguration="ServiceTimeout"
                      contract="BlaBla.Service.ICom" />
            <endpoint address="mex"
                      binding="mexHttpBinding"
                      contract="IMetadataExchange" />
          </service>
        </services>
        <behaviors>
          <serviceBehaviors>
            <behavior name="ServiceBehavior">
              <serviceMetadata httpGetEnabled="true"/>
              <serviceDebug includeExceptionDetailInFaults="False"/>
            </behavior>
          </serviceBehaviors>
        </behaviors>
      </system.serviceModel>
    </configuration>

Example of App pseudo-code:

System.ServiceModel.EndpointAddress epa = new System.ServiceModel.EndpointAddress("http://localhost:8000/blabla/service");

System.ServiceModel.BasicHttpBinding bhb = new System.ServiceModel.BasicHttpBinding();

Timespan t = new TimeSpan(0, 10, 0);

bhb.SendTimeout = t; bhb.ReceiveTimeout =t; bhb.OpenTimeout = t; bhb.CloseTimeout = t;

Blabla.ComunicationClient com = new Blabla.ComunicationClient(bhb, epa);

var obj = await com.callAsync(int val);

return obj;

UPDATE #1

This situation only happens in UWP. I have created a similar WinForms project and everything works as expected. This means that it is probably something related to UWP.

like image 451
nunofmendes Avatar asked Oct 30 '22 01:10

nunofmendes


1 Answers

After several tries, manipulating different config files, I have not found a solution regarding how to remove the timeout limitation of 100 seconds. To solve this specific problem, I implemented a counter-measure.

What I have found during my tries was:

  • If the project is in WinForms, everything works as expected. This means, this 100-second-limit, is an UWP "Feature";
  • If you reduce the SendTimeout to less than 100 seconds, it will throw a TimeoutException with the corresponding timer;
  • This is not exclusive to the Windows Service. It also happens when comunicating with a SOAP Webservice implementation;
  • This seems to happen only if you are doing a task the requires "external communication" with a service reference. If you have a "internal" task that takes more than 100 seconds, it works as expected (eg. await Task.delay(250000)).

How I solved this?

After chatting at C# SO channel, @Squiggle suggested a polling approach and that is what I implemented and tested successfully.

These are the steps I took:

  1. I updated the existing service request (call(int val)) to accept a another argument, a Guid, so I could identify which request I wanted to do the "polling";
  2. I created an additional request at the service to InquireAboutCurrentRequest that accepted also accepted a GUID parameter, and returned an int;
  3. I updated the Service Reference at the UWP app with the new request;
  4. I called "await call(val,guid)" with a try catch. I did this because 90% of these calls return in less than 30 seconds*;
  5. In the catch I added an "If" that checked if the exception was a CancelTask, and if was I call "await InquireAboutCurrentRequest(guid)";
  6. This method, at the windows service, keeps checking if the other operation has ended and sleeps every X seconds. Since the total time of the first call can only be at most 2 minutes, I only need to wait 20 seconds;
  7. After that I will deal with the result accordingly, but at least this time I know I have "waited 2 minutes" for the response.

There are other possible solutions such as sockets, which I have not tried, that could work.

* If all the requests take more than 100 seconds, I suggest using the polling approach from the beginning of the requests, instead of waiting for the try/catch.

like image 145
nunofmendes Avatar answered Nov 09 '22 05:11

nunofmendes