0
votes

I am working on a Xamarin.Forms Android application that requires the phone to ping to the server every 15 seconds. All my calls are asynchronous and all of them have the "await" attribute, this includes all the main user functions in the main classes that are not found in Device.StartTimer objects. For example, registering data upon button click, logging in and logging out. For the 15 second ping I am using a Device.StartTimer function and I have not had too many issues but at times I do notice that the responses do overlap, however I thought the "await" declaration would have taken care of responses overlapping because I read that Device.StartTimer works on the main thread. What I'm I doing wrong? Is there a better way to managed timed HTTPClient calls?

I tried applying the await attribute to the function to make sure that the calls do not overlap. There is a note about Device.StartTimer running on the main thread, so I thought that ALL my async await functions would be respected. Including the async function in the main classes.

//Function to ping to the server every 15 seconds
private void StartOfflineTimer()
{
    Device.StartTimer(TimeSpan.FromSeconds(15.0), () =>
    {
        if(timerOffline)
        {
            Task.Run(async () =>
                if(await InformarOfflineAsync(Settings.AccessToken, idRutaOffline))
                {
                    DependencyService.Get<ILogUtils>().GuardarLine("**Device conected.."); 
                }
                else
                {
                    DependencyService.Get<ILogUtils>().GuardarLine("**Device disconnected..");
                }
            );
        }
        return timerOffline;
    });
}

//Default Example of how I handle ALL HTTPClient calls on the app, including calls that are in the main classes, not embedded in a device timer. All of these calls are inside their own public async Task<ExampleObject> function. Once again all of the functions that make these calls have an "await" attribute.


var jsonRequest = await Task.Run(() => JsonConvert.SerializeObject(requestObj));

var httpContent = new StringContent(jsonRequest, Encoding.UTF8, "application/json");

using (var httpClient = new HttpClient())
{
    httpClient.Timeout = TimeSpan.FromSeconds(10.0);
    var httpResponse = await httpClient.PostAsync(Constants.BaseUrl + "login/validarOffline", httpContent);
    ExampleObjectResponseObj exampleObject = new ExampleObjectResponseObj();
    var responseContent = await httpResponse.Content.ReadAsStringAsync();
    ExampleObjectResponseObj = JsonConvert.DeserializeObject<InformDataResponseObj>(responseContent);
    return ExampleObjectResponseObj;
}

The HTTPClient responses may overlap, or at times they double up and send at the exact same time.

2
you shouldn't use a HTTP client in a using statement like that every time a new client is created the socket is not cleaned up so this will eventually fill up your socket connections, Rather use a static instance of HTTP client and instantiate it only once.Hawkzey
also, use System.Timers.Timer instead of Device.StartTimerJason
@Hawkzey I had read in another post that that using it in as a var in a using statemente 'ensures that you close the connection' every time you re instantiate it. But I will try your advice. This app can run up to 6 days, do you think a static instance of HTTP Client could last that long?DaWiseguy
@Jason Thanks for the advice, I have not run into problems with Device.StartTimer on the functions I use for design (for example, display a validation popup for 4 seconds). But do you think System.Timers.Timer would take care of my "await" statement not being respected?DaWiseguy
@Hawkzey can you point me to a situation where sockets aren't disposed?Cory Nelson

2 Answers

1
votes

If you don't want your calls to overlap, don't use timer but a loop with a delay:

Task.Run(async () =>
{
    const TimeSpan checkInterval = TimeSpan.FromSeconds(15);

    while (true)
    {
        var callTime = DateTime.UtcNow;
        try
        {
            await server.Ping();
        }
        catch (Exception exception)
        {
            HandleException(exception);
        }

        var elapsedTime = DateTime.UtcNow - callTime;
        var timeToWait = checkInterval - elapsedTime;
        if (timeToWait > TimeSpan.Zero)
        {
            await Task.Delay(timeToWait);
        }
    }
});
1
votes

The code above isn't complete and not enough for a very detailed and precise answer, but still most if not all things can be answered:

  • You run your timer callback in Task.Run, so it is NOT running in the main thread
  • If you wanted to run HttpClient in the UI thread it may prevent the overlap but expect your app to become completely unresponsive.
  • To prevent the overlap you may use several methods but most likely you are looking for SemaphoreSlim