How to run a method in the background only when app is open and running?

38,843

Solution 1

What we did in our forms application was to make use of the Device.Timer and the Stopwatch class that available in System.Diagnostics, and Xamarin.Forms to create a very generic managed timer that we could interact with using the onStart, onSleep and onResume methods in Xamarin.Forms.

This particular solution doesn't require any special platform specific logic, and the device timer and stopwatch are non UI blocking.

using Xamarin.Forms;
using System;
using System.Linq;
using System.Diagnostics;

namespace YourNamespace
{
    public partial class App : Application
    {
        private static Stopwatch stopWatch = new Stopwatch();
        private const int defaultTimespan = 1;

        protected override void OnStart()
        {
            // On start runs when your application launches from a closed state, 

            if (!stopWatch.IsRunning)
            {
                stopWatch.Start();
            }

            Device.StartTimer(new TimeSpan(0, 0, 1), () =>
            {
                // Logic for logging out if the device is inactive for a period of time.

                if (stopWatch.IsRunning && stopWatch.Elapsed.Minutes >= defaultTimespan)
                {
                    //prepare to perform your data pull here as we have hit the 1 minute mark   

                        // Perform your long running operations here.

                        Device.InvokeOnMainThread(()=>{
                            // If you need to do anything with your UI, you need to wrap it in this.
                        });

                    stopwatch.Restart();
                }

                // Always return true as to keep our device timer running.
                return true;
            });
        }

        protected override void OnSleep()
        {
            // Ensure our stopwatch is reset so the elapsed time is 0.
            stopWatch.Reset();
        }

        protected override void OnResume()
        {
            // App enters the foreground so start our stopwatch again.
            stopWatch.Start();
        }
    }
}


Edit:

To give some context as to how the above solution works step by step:

The application starts from a closed state and the 'OnStart()' method creates our Device.Timer that ticks every second. It also starts our stopwatch that counts upto a minute.

When the app goes into the background it hits the 'OnSleep' method at this point if we were to pass a 'false' value into our Device.StartTimer() action it would not start up again. So instead we simply reset our stopwatch ready for when the app is opened again.

When the app comes back into the foreground it hits the 'OnResume' method, which simply starts the existing stopwatch.

2018 Edit:

This answer still has some merits even in 2018, but mainly for very specific situations. There are better platform specific ways to replicate this functionality even in Xamarin.Forms. The above still remains a platform agnostic way to perform a task after a period of time, taking into account user activity/inactivity.

Solution 2

you can use this,

 System.Threading.Tasks.Task.Run(() =>
 {
      //Add your code here.
 }).ConfigureAwait(false);

Solution 3

There are a few ways to do this in both iOS and Android. In Xamarin Forms most of this functionality falls under the moniker of Backgrounding. There are a lot of tutorials out there. This one is quite elaborate and definitely worth checking out:

http://arteksoftware.com/backgrounding-with-xamarin-forms/

In Android a lot of this work is done in a Background Service. For iOS look into Long Running or Finite-Length Tasks. As you can tell from this there is no Xamarin Forms way of doing this. You will need to write Xamarin.Android and Xamarin.iOS specific code.

Solution 4

To run a background task use a Service. Generally classifies tasks as either Long Running Tasks or Periodic Tasks.

The code for service in android look like this

[Service]
public class PeriodicService : Service
{ 
    public override IBinder OnBind(Intent intent)
    {
        return null;
    }

    public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
    {
        // From shared code or in your PCL


        return StartCommandResult.NotSticky;
    }
}

And to invoke the service in background

   var intent = new Intent (this, typeof(PeriodicService));
   StartService(intent);

In case wants to invoke and check after every minute

private void StartBackgroundDataRefreshService ()
{
    var pt = new PeriodicTask.Builder ()
        .SetPeriod (1800) // in seconds; minimum is 30 seconds
        .SetService (Java.Lang.Class.FromType (typeof(BackgroundService)))
        .SetRequiredNetwork (0)
        .SetTag (your package name) // package name
        .Build ();

        GcmNetworkManager.GetInstance (this).Schedule (pt);
}

In order to know which service type is good for you read this tutorial Types of Services

Xamarin Blog for periodic background service Xamarin Service Blog

The other example is

public class PeriodicService : Service
{ 
 private static Timer timer = new Timer();     
  public override IBinder OnBind(Intent intent)
    {
        return null;
    }

    public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
    {
        timer.scheduleAtFixedRate(new mainTask(), 0, 5000);
        return StartCommandResult.NotSticky;
    }

   private class mainTask extends TimerTask
    { 
        public void run() 
        {
         //your code
        }
    } 
}

Here is Sample Code of XAMARIN Android Service Which will perform task after every 10 Seconds

using System;
using System.Threading;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Util;

namespace SimpleService
{

[Service]
public class SimpleStartedService : Service
{
    static readonly string TAG = "X:" + typeof(SimpleStartedService).Name;
    static readonly int TimerWait = 10000;
    Timer timer;
    DateTime startTime;
    bool isStarted = false;

    public override void OnCreate()
    {
        base.OnCreate();
    }

    public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
    {
        Log.Debug(TAG, $"OnStartCommand called at {startTime}, flags={flags}, startid={startId}");
        if (isStarted)
        {
            TimeSpan runtime = DateTime.UtcNow.Subtract(startTime);
            Log.Debug(TAG, $"This service was already started, it's been running for {runtime:c}.");
        }
        else
        {
            startTime = DateTime.UtcNow;
            Log.Debug(TAG, $"Starting the service, at {startTime}.");
            timer = new Timer(HandleTimerCallback, startTime, 0, TimerWait);
            isStarted = true;
        }
        return StartCommandResult.NotSticky;
    }

    public override IBinder OnBind(Intent intent)
    {
        // This is a started service, not a bound service, so we just return null.
        return null;
    }


    public override void OnDestroy()
    {
        timer.Dispose();
        timer = null;
        isStarted = false;

        TimeSpan runtime = DateTime.UtcNow.Subtract(startTime);
        Log.Debug(TAG, $"Simple Service destroyed at {DateTime.UtcNow} after running for {runtime:c}.");
        base.OnDestroy();
    }

    void HandleTimerCallback(object state)
    {
        TimeSpan runTime = DateTime.UtcNow.Subtract(startTime);
        Log.Debug(TAG, $"This service has been running for {runTime:c} (since ${state})." );
    }
}

}

Solution 5

You can use

Device.StartTimer(TimeSpan.FromMinutes(1), () =>
{
   var shouldTimerContinueWork = true;
   /*your code*/
   return shouldTimerContinueWork;
});

This timer runs on background thread, uses devices clock and reentrance safe.
To stop this timer when app is in background you can use Xamarin.Forms.Application methods OnSleep and OnResume as described here

Share:
38,843
Alan2
Author by

Alan2

Updated on April 26, 2020

Comments

  • Alan2
    Alan2 about 4 years

    Once the app is open and running I would like a background process to check a database and to make an update depending on the data in the database. I would like to make this check every one minute. I only want this to happen when the app is in the foreground and in view of the user.

    Can someone give me some suggestions as to how I do this? I assume I can call a method from here but I'm not sure how to do this. Also I do not know how to stop or even if I need to manually cancel / stop the process. Would it cancel itself when the app is not in the foreground and restart when the app came back into the foreground?

    public partial class App : Application
    {
    
       protected override void OnStart()
       {
          App.DB.InitData();
          MainPage = new Japanese.MainPage();
       }
    

    But do I need to make this run on a different thread and if so how could I do that.

    Sorry if my question is not clear. Please ask and I can update if it doesn't make sense.

  • Alan2
    Alan2 almost 7 years
    So would I initiate this from the App onStart or the App constructor?
  • Manish Sharma
    Manish Sharma almost 7 years
    depend on your requirement you can use it .
  • Ivan Bukashkin
    Ivan Bukashkin almost 7 years
    why use StopWatch? Timer is already doing it for you. StopWatch + Task.Run inside of timer delegate adding reentrancy. And why use Task.Run to invoke on main thread? And why keeping timer always running? And why StopWatch.restart(); and Stop(); instead just Reset();? maaan
  • JoeTomks
    JoeTomks almost 7 years
    @IvanBukashkin The 'Device.Timer' can't be used as a passable object, it's a static class. So although we can start it at an interval we can't actually stop it or pause it. The stop watch allows us to do this. I'm using task run for long running processes and 'InvokeOnMainThread' to commit actions against the UI. As to reset and stop, yeah restart fits fine there.
  • Ivan Bukashkin
    Ivan Bukashkin almost 7 years
    use field flag that stops current timer or CancellationToken field to stop timer. There is no need in stopwatch at all. If you think that doing Device.StartTimer every time is difficult - encapsulate this.<br> You are using Device.StartTimer for long running operation. There is no need in Task.Run.
  • Ivan Bukashkin
    Ivan Bukashkin almost 7 years
    stopwatch there can be usefull as it protect you from reentrancy if you quickly stop old timer and start a new one. This is only reason i see. And it would work only if you remove Task.Run. In all other cases you need some lock to sinchronize threads
  • JoeTomks
    JoeTomks almost 7 years
    So where would you put this code, in the 'OnResume' method? or in the 'OnStart' method? because you don't want to create this everytime the device resumes from background. If you put it in the 'OnStart' method, you won't have a way of starting it back up again once you stop it.
  • JoeTomks
    JoeTomks almost 7 years
    @IvanBukashkin I think your missing some key interactions between how the OP want's the solution to work. see my comment on your answer.
  • Ivan Bukashkin
    Ivan Bukashkin almost 7 years
    no, I think its just bad solution from your side. Ok now you fixed it in 2 of 3 key points
  • JoeTomks
    JoeTomks almost 7 years
    @IvanBukashkin that's a bit rude. Your solution doesn't make sense, you need to provide more context. I don't think you fully understand what the OP is asking for. Your solution by itself is firstly a smaller rip off of mine, and secondly doesn't consider the actual issues the OP has.
  • Ivan Bukashkin
    Ivan Bukashkin almost 7 years
    well, your solution just containing mistakes and not ideal in part of stopwatch
  • Ivan Bukashkin
    Ivan Bukashkin almost 7 years
    in your code you can do an actual work, or just skip current tick. depending on many factors. Are you in background or not. How long ago was prev sync. Is that suit for your need? i dont know. So thats up to you.
  • Ivan Bukashkin
    Ivan Bukashkin almost 7 years
    Or you can recreate it and do sync just in time when app is resume, but be aware of reentrancy. May be its better? Up to you.
  • Ramankingdom
    Ramankingdom almost 7 years
  • Ivan-San
    Ivan-San about 6 years
    This is the best solution and works fine. Just adjust to your needs
  • Edward Brey
    Edward Brey almost 6 years
    With a one-second timer, battery life is drained every second, even when there is nothing to do, and even when the app is in the background. I believe iOS will even end up terminating the app for this after a few minutes.
  • JoeTomks
    JoeTomks almost 6 years
    @EdwardBrey it's actually not that bad, the above has been in an existing Xamarin.Forms product for about a year and a half now. We've not really noticed any major issues with battery drain etc. it's the equivalent of a basic clock function most of the time, yes it keeps a thread alive, but it processes nothing every second unless it meets the requirements. The most expensive process is the first initialization of the underlying threads that support the stopwatch and the timer.
  • R15
    R15 almost 6 years
    By using your code, would I be able to login immediately? & the code inside Task will be continue in background?
  • Emil
    Emil over 5 years
    Is it very different than using async /await and configureawait(false)?