Device.StartTimer async

Hi,

Is there any trick to use Device.StartTimer with async?

Device.StartTimer(TimeSpan.FromMinutes(1), async () =>
{
        await DisplayAlert("","","");
}

It doesn't compile: Cannot convert async lambda to delegate type Func

Any help?

Posts

  • adamkempadamkemp USInsider, Developer Group Leader mod

    The callback for a timer returns a value (bool, whether to repeat). You can't make a lambda that returns a value async. Instead you can return false (stop), use ContinueWith instead of await, and then in the continuation callback start a new timer if you need to.

  • Adam,

    Could you explain more with an example? I am doing a background downloading/uploading process which is calling my web services. However I need to do this on a timer once a user logs into the app. In order to keep my web services from slowing down the UI, I would like it to call them asynchronously every 30 seconds to a minute. I haven't found a way to have this work besides calling my methods synchronously using the Device.StartTimer.

    Thanks for any help or direction you may be able to give.

  • Thank you so much for the example and the blog. I will definitely take a look at it but it does seem like I will be able to utilize the first example you gave me.

  • WinSomeLoseSomeWinSomeLoseSome USMember ✭✭
    edited October 2017

    My experience was that returning false from the callback stops the callback from being called, but the timer never works normally again after that, appears to call callback multiple times for each timer tick, one for each time I have called Device.StartTimer. I modified my code to only call this method once. Something not right here. Break points in the callback do not get hit after returning false, but if I call Device.StartTime again, I get two callbacks for each interval. In essence, the callback collection gets added to but never emptied?

  • JohnHardmanJohnHardman GBUniversity mod

    @WinSomeLoseSome - if that's the case, can you log a bug for that at bugzilla.xamarin.com if nobody else has already done so.

  • batmacibatmaci DEMember ✭✭✭✭✭

    @adamkemp said:
    Something like this:

    private void ScheduleWebRequest()
    {
        // Start a timer that runs after 1 minute.
        Device.StartTimer(TimeSpan.FromMinutes(1), () =>
            {
                Task.Factory.StartNew(async () =>
                    {
                        // Do the actual request and wait for it to finish.
                        await PerformWebRequest();
                        // Switch back to the UI thread to update the UI
                        Device.BeginInvokeOnMainThread(() =>
                            {
                                // Update the UI
                                // ...
                                // Now repeat by scheduling a new request
                                ScheduleWebRequest();
                            });
                    });
    
                // Don't repeat the timer (we will start a new timer when the request is finished)
                return false;
            });
    }
    

    That uses the Device class, but you could also try a pure async/await method:

    private async void PollWebRequest()
    {
        while (_keepPolling)
        {
            await PerformWebRequest();
            // Update the UI (because of async/await magic, this is still in the UI thread!)
            if (_keepPolling)
            {
                await Task.Delay(TimeSpan.FromMinutes(1));
            }
        }
    }
    

    You could also make this cancellable by using a CancellationToken in the call to Task.Delay. There is a very good blog series here about async/await that also covers cancellation. It would be good for you to read through that.

    very old question, i hope you remember but why did you use Task.Factory.StartNew together with Device.BeginInvokeOnMainThread ? i thought that they roughly do similar work to run on a separate thread? wouldnt it be sufficient to use only Device.BeginInvokeOnMainThread?

  • JohnHardmanJohnHardman GBUniversity mod
    edited December 2018

    @batmaci said:
    why did you use Task.Factory.StartNew together with Device.BeginInvokeOnMainThread ? i thought that they roughly do similar work to run on a separate thread? wouldnt it be sufficient to use only Device.BeginInvokeOnMainThread?

    Task.Factory.StartNew starts a new Task.
    Device.BeginInvokeOnMainThread invokes an action on the main (UI) thread.

    Work on the basis that Task.Factory.StartNew does not run things on the UI thread. If having started a Task, if something that Task does will want to interact with the UI, it's necessary to use Device.BeginInvokeOnMainThread to ensure that the UI interaction happens on the UI thread.

    Whether Device.StartTimer runs things on the UI thread is an interesting question. Whilst the current official documentation for Device.StartTimer says "Starts a recurring timer on the UI thread using the device clock capabilities", there are plenty of blogs (including by MVPs) etc on the web saying that Device.StartTimer does not run things on the UI thread and so UI interactions happening in the invoked code will require Device.BeginInvokeOnMainThread. I've asked for clarification on this recently, but I don't think I've had an official answer.

  • adamkempadamkemp USInsider, Developer Group Leader mod
    StartTimer does use the main thread, at least on iOS and Android for sure.
  • batmacibatmaci DEMember ✭✭✭✭✭

    @JohnHardman "it's necessary to use Device.BeginInvokeOnMainThread to ensure that the UI interaction happens on the UI thread."
    quick question regarding this. i understand the concept using in xaml.cs like in the official documentation but when using mvvm, when to use Device.BeginInvokeOnMainThread? should we use it only when we raise propertychanged to update UI bindings? or it isnt meant to be used in viewmodel

  • JohnHardmanJohnHardman GBUniversity mod

    @batmaci said:
    @JohnHardman "it's necessary to use Device.BeginInvokeOnMainThread to ensure that the UI interaction happens on the UI thread."
    quick question regarding this. i understand the concept using in xaml.cs like in the official documentation but when using mvvm, when to use Device.BeginInvokeOnMainThread? should we use it only when we raise propertychanged to update UI bindings? or it isnt meant to be used in viewmodel

    If your View Model is doing work on a thread other than the UI thread and that work involves raising PropertyChanged, then yes you should use Device.BeginInvokeOnMainThread when raising PropertyChanged.

  • jddjdd USMember ✭✭✭

    Hello,

    This thread is not clear at all for me.
    I use the documentation described there:
    https://docs.microsoft.com/en-us/xamarin/xamarin-forms/xaml/xaml-basics/data-bindings-to-mvvm

    But I need to show time and geolocation. So I need to call an async method in a Device.StartTimer.
    Something looking like that but it doesn't work:

            public ClockLocationViewModel()
            {
                this.DateTime = DateTime.Now;
    
                Device.StartTimer(TimeSpan.FromMilliseconds(300), () =>
                    {
                        this.DateTime = DateTime.Now;
                        return true;
                    });
                Device.StartTimer(TimeSpan.FromSeconds(2), () =>
                {
                    this.Position = Position.BuildPositionAsync().Result;
                    return true;
                });
            }
            private static async Task<Position> GetPositionAsync()
            {
                Position position = await Position.BuildPositionAsync();
                return position;
            }
    

    Is the first example in this thread the solution? Is it ok with MVVM? who will start a new Device.StarTimer if the method returns False?

  • Amar_BaitAmar_Bait DZMember ✭✭✭✭✭
    edited June 26

    What do you mean by "doesn't work"? it locks? it never gets executed? it throws an exception?

    Also never use .Result on a task, it defeats the whole purpose of async programming as it will run the task synchronously.

    Device.StartTimer(TimeSpan.FromSeconds(2), async () =>
                {
                    this.Position = await Position.BuildPositionAsync();
                    return true;
                });
    
  • jddjdd USMember ✭✭✭

    @Amar_Bait I am sorry, I did not see your answer. You are right: the issue is dot Result that freezes the app as jamesmontemagno tells here:
    https://github.com/xamarin/Essentials/issues/868
    and here:
    https://montemagno.com/c-sharp-developers-stop-calling-dot-result/

    @adamkemp this is awesome:
    http://www.jeremybytes.com/downloads.aspx

  • LuisDavidDelaCruzLuisDavidDelaCruz Member ✭✭✭

    Hi @AlexandreReyesMartins
    try this, is just an alternative

    XAML

    <Label x:Name="Segundero" HorizontalOptions="Center" FontSize="Large" VerticalOptions="Center" />
    

    C#

    In your Constructor
    try
    {
    Device.StartTimer(TimeSpan.FromSeconds(1), () =>
    {
    Reloj();
    return true;
    });
    }
    catch{}

    //--------------------------

     public void Reloj()
     {
      var sec = now.Second;
            segundoActual = 60 - Convert.ToInt32(sec);
    
            if (segundoActual == 0 || segundoActual <= 10)
            {
                Segundero.TextColor = Color.DarkRed;
                Segundero.FontAttributes = FontAttributes.Bold;
            }
            else
            {
                Segundero.TextColor = Color.Black;
                Segundero.FontAttributes = FontAttributes.Italic;
            }
            Segundero.Text = segundoActual.ToString();
    }
    

    // in your Xamarin.Forms app you will have a chronometer like this

  • JohnHardmanJohnHardman GBUniversity mod
    edited August 2

    @LuisDavidDelaCruz said:

    In your Constructor
    try
    {
    Device.StartTimer(TimeSpan.FromSeconds(1), () =>
    {
    Reloj();
    return true;
    });
    }
    catch{}

    You'll want to re-organise the try/catch in that code to be inside the StartTimer block. Currently, if Reloj() were to throw an exception, your catch block won't catch it, and your app will terminate.

Sign In or Register to comment.