shinyorg / shiny

.NET Framework for Backgrounding & Device Hardware Services (iOS, Android, & Catalyst)
https://shinylib.net
MIT License
1.44k stars 227 forks source link

Job being ran / executed in quick succession + no reference to CrossJobs #609

Closed nicolasHul closed 3 years ago

nicolasHul commented 3 years ago

Enter Question Below (don't delete this line)

I am quite new in working / using the Shiny library so my apologies if this are bad questions. I have several questions listed below.

  1. I am currently using the 'Jobs' of the Shiny library. I followed the Samples project to setup my Job. The job should run every X seconds when the APP is in the foreground. To Achieve this I have set the following in the ShinyStartup:

services.UseJobForegroundService(TimeSpan.FromSeconds(180));

When the APP starts I am registering my Job as follows:

var syncdatajob = new JobInfo(typeof(SyncDataJob), "DataSync")
{
    Repeat = true,
    BatteryNotLow = true,
    RunOnForeground = true,
    RequiredInternetAccess = InternetAccess.Any,
    PeriodicTime = TimeSpan.FromSeconds(180)
};

await _jobManager.Register(syncdatajob);

I am not sure that setting the PeriodicTime on the job itself is necessary since I already set the RunOnForeground property to true, is setting the PeriodicTime redundant in this case? It would be nice if the job also ran when the APP is backgrounded, but it is not necessary.

  1. I am noticing that my Job Task is ran several times in quick succession (5~ seconds for 2 to 4 times) when I start my APP, while it has not been registered yet (though, it has been some times before / in earlier APP starts). Is this normal behavior? I expect it to run only when it has been registered by the APP. I now deal with this by checking the LastRunUtc being more than X seconds..
  2. In the documentation of Shiny Jobs, I see the use of Schedule , do I need this in order to properly schedule my task? What is the main difference between registering the job and scheduling it in this case?
  3. In the Jobs documentation I also see the use of CrossJobs.Current.Schedule, though I don't have any reference to CrossJobs, nor can I see it being used in the samples solution.
aritchie commented 3 years ago

Hi @nicolasHul Thanks for being a sponsor

  1. PeriodicTime is a value that is used by UWP and Android. It tries to influence the OS on how often to run your job while in the background. Foreground jobs is a Shiny concept in that a timer runs while your is in the foreground. It also tries to execute your job when your app resumes or is going to the background. Jobs were designed to run in the background - on Android it is roughly every 15mins, iOS uses some smart techniques to run based on your user's usage.

  2. Jobs are sticky, once they are registered, they hang around until you unregister them. There is only a single timer so I'm not sure why your app would fire them in quick succession. If you feel there is a bug here, please send a reproducible sample.

  3. No - for the job you have listed above, you should use the Shiny Startup, services.RegisterJob since it is meant to be a system service. Schedule no longer exists on IJobManager, it is now called register.

3/4. The documentation is not complete yet. It is a work in progress. There are some artifacts from previous libraries that I merged into this project.

Hopefully that helps.

jmichas commented 3 years ago

I seem to be having the same problem with my job firing multiple times at the same time. This happens when I cold start or resume my app on iOS simulator. I haven't tested on physical device yet. In my log below you can see that my STEPJOB gets called 3 times on 3 threads at the same time. This causes data duplication down the line as my service reads and writes steps to a database.

How can I ignore the duplicate calls easily?

Thread started:  #137
2021-07-14 11:43:24.402745-0400 MyApp.Mobile.iOS[54262:4973414] [INF (1)] [MyApp.Mobile.Services.Jobs.SendStepsToPlatformJob] STEPJOB Running job...(7/14/2021 3:42:42 PM)
Thread started:  #138
2021-07-14 11:43:24.414445-0400 MyApp.Mobile.iOS[54262:4973414] [INF (1)] [MyApp.Mobile.Services.Jobs.SendStepsToPlatformJob] STEPJOB Running job...(7/14/2021 3:42:42 PM)
Thread started:  #139
2021-07-14 11:43:24.423427-0400 MyApp.Mobile.iOS[54262:4973414] [INF (1)] [MyApp.Mobile.Services.Jobs.SendStepsToPlatformJob] STEPJOB Running job...(7/14/2021 3:42:42 PM)
Thread started:  #140
2021-07-14 11:43:24.452576-0400 MyApp.Mobile.iOS[54262:4988568] [INF (141)] [MyApp.Mobile.iOS.Services.AppleHealthService] GetCountSinceLastAnchor: type: HKQuantityTypeIdentifierStepCount, count: 1000, lastAnchor: 1, New Anchor: 2
2021-07-14 11:43:24.453183-0400 MyApp.Mobile.iOS[54262:4988568] [INF (141)] [MyApp.Mobile.iOS.Services.AppleHealthService] GetCountSinceLastAnchor: type: HKQuantityTypeIdentifierStepCount, count: 1000, lastAnchor: 1, New Anchor: 2
2021-07-14 11:43:24.454210-0400 MyApp.Mobile.iOS[54262:4988568] [INF (141)] [MyApp.Mobile.iOS.Services.AppleHealthService] GetCountSinceLastAnchor: type: HKQuantityTypeIdentifierStepCount, count: 1000, lastAnchor: 1, New Anchor: 2
jmichas commented 3 years ago

Sorry, I should have added that the date time in Running job...(7/14/2021 3:42:42 PM) is the JobInfo LastRunUtc. So you can see that the last run does not update after the first job fires in the 3 rapid fire group.

aritchie commented 3 years ago

@jmichas Support is for sponsors only. If you are having an issue, please file it appropriately with a reproducible sample

jmichas commented 3 years ago

@aritchie Sponsoring now :) Can you maybe give me a quick answer to this problem? Is there a way to easily filter or throttle the job firing multiples on resume?

aritchie commented 3 years ago

@jmichas Thank you. I don't know what could be causing the issue which is why I requested a sample.

A quick workaround is to simply set a static flag

class YourJob
{
   static bool running = false;

   Task Run(...) {
      if (running)
           return;
      try {
          running = true;
      }
      finally {
          running = false;
      }
   }
}
jmichas commented 3 years ago

@aritchie Thanks for getting back to me. I am using services.UseJobForegroundService(TimeSpan.FromMinutes(1)); in testing right now, in production this would be like 15 or 30 minutes.

I tried using the RegisterJob method but when I do that any dependencies in my IJob constructor do not get injected, so I moved my job registration to App.OnStart. My app is using Prism / Unity as well so maybe the dependency injection isn't working because of the container not being shared at that time.

I am "shinifying" my app as much as I can. I have a ton of home grown stuff that overlaps what you have created. But right now background Job would be a bing improvement in code quality and user experience.

So, I have copied the code from the JobLifecycleTask and registered my own local version so I could set some breakpoints. In the TryRun method I set a breakpoint to see what was happening in the foreach loop. It has my one and only job in the jobs but that foreach gets hit 3 times when the app starts.

I was able to ignore the multiples when the app starts by setting a "run" property in my job that is initially false, then in App.OnStart I set that to true so that when the foreground timer runs the job it will actually do some work.

I will try the static running and see how it goes. I was trying to set a static lastrun date but it was the same for all 3 jobs that fired within the same second down to the tick.

aritchie commented 3 years ago

Ok so a few things are going here that are going to really get you regardless of multiple runs

1) Your jobs will never run in the background if they depend on services that are only registered with Prism. Shiny jobs will execute on Android without ever bringing up your UI layer which means those services will be missing in the background. ANY service(s) used by a shiny service need to be explicitly registered with Shiny in the startup class. You should have a look at https://github.com/shinyorg/framework as this implements the best practices between Shiny & Prism. Dan (from Prism) and I have worked on mastering this over the last couple of years.

2) Foreground jobs should never be more than 2-3 minutes apart max. This is a timer that ticks only while your app is running on the screen. It will restart the timer if your user exits and resumes the app.

I will look into what may be going on with the foreground servicing.

jmichas commented 3 years ago

When you say "only registered with Prism" do you mean they are registered on the container with protected override void RegisterServices(IContainerRegistry containerRegistry)? I have mostly just the register for navigation stuff in there, but I have a few named registrations for multiple implementation interfaces like IStartupStep. How can I register a named registration in shiny on the IServiceCollection?

Good to know on the job interval, I assumed less was more.

Also, please see my stack question that I answered myself, it is about how to register platform specific service implementations using shiny, Im not sure if Im doing it right. https://stackoverflow.com/questions/68302150/shiny-prism-platform-service-example

aritchie commented 3 years ago

When you say "only registered with Prism" do you mean they are registered on the container with protected override void RegisterServices(IContainerRegistry containerRegistry)

Correct - those services need to move to the Shiny startup if you are using them with Shiny things like jobs.

As for your post on SO, no..... that is very very wrong. It is still registering services on the UI level when they will be needed in the background.

jmichas commented 3 years ago

Any chance of getting an example on how to register the platform specific services the correct way? (trying to get my money's worth day one 😜)

jmichas commented 3 years ago

@aritchie So, I wired up the shiny logging and in my console I get this when I start my app. Could this be why my job is firing multiple times?

2021-07-15 11:20:10.205697 [ERR (1)] [Shiny.Infrastructure.StartupModule] Failed to bind stateful model - Shiny.Jobs.Infrastructure.JobLifecycleTask
System.ArgumentException: No key/value store named settings
  at Shiny.Stores.KeyValueStoreFactory.GetStore (System.String aliasName) [0x0000d] in /_/src/Shiny.Core/Stores/KeyValueStoreFactory.cs:45 
  at Shiny.Stores.KeyValueStoreFactory.get_DefaultStore () [0x00000] in /_/src/Shiny.Core/Stores/KeyValueStoreFactory.cs:30 
  at Shiny.Stores.ObjectStoreBinder.Bind (System.ComponentModel.INotifyPropertyChanged npc, System.String keyValueStoreAlias) [0x0003d] in /_/src/Shiny.Core/Stores/ObjectStoreBinder.cs:64 
  at Shiny.Infrastructure.StartupModule.TryRun (System.Object instance) [0x0000a] in /_/src/Shiny.Core/Infrastructure/StartupModule.cs:106 

2021-07-15 11:20:10.225967 [INF (1)] [Shiny.Infrastructure.StartupModule] Starting up - Shiny.Jobs.Infrastructure.JobLifecycleTask
2021-07-15 11:20:10.305106 [INF (1)] [Shiny.Infrastructure.StartupModule] Started up - Shiny.Jobs.Infrastructure.JobLifecycleTask
2021-07-15 11:20:10.305698 [ERR (1)] [Shiny.Infrastructure.StartupModule] Failed to bind stateful model - Shiny.Power.PowerManagerImpl
System.ArgumentException: No key/value store named settings
  at Shiny.Stores.KeyValueStoreFactory.GetStore (System.String aliasName) [0x0000d] in /_/src/Shiny.Core/Stores/KeyValueStoreFactory.cs:45 
  at Shiny.Stores.KeyValueStoreFactory.get_DefaultStore () [0x00000] in /_/src/Shiny.Core/Stores/KeyValueStoreFactory.cs:30 
  at Shiny.Stores.ObjectStoreBinder.Bind (System.ComponentModel.INotifyPropertyChanged npc, System.String keyValueStoreAlias) [0x0003d] in /_/src/Shiny.Core/Stores/ObjectStoreBinder.cs:64 
  at Shiny.Infrastructure.StartupModule.TryRun (System.Object instance) [0x0000a] in /_/src/Shiny.Core/Infrastructure/StartupModule.cs:106 

2021-07-15 11:20:10.306223 [ERR (1)] [Shiny.Infrastructure.StartupModule] Failed to bind stateful model - Shiny.Net.ConnectivityImpl
System.ArgumentException: No key/value store named settings
  at Shiny.Stores.KeyValueStoreFactory.GetStore (System.String aliasName) [0x0000d] in /_/src/Shiny.Core/Stores/KeyValueStoreFactory.cs:45 
  at Shiny.Stores.KeyValueStoreFactory.get_DefaultStore () [0x00000] in /_/src/Shiny.Core/Stores/KeyValueStoreFactory.cs:30 
  at Shiny.Stores.ObjectStoreBinder.Bind (System.ComponentModel.INotifyPropertyChanged npc, System.String keyValueStoreAlias) [0x0003d] in /_/src/Shiny.Core/Stores/ObjectStoreBinder.cs:64 
  at Shiny.Infrastructure.StartupModule.TryRun (System.Object instance) [0x0000a] in /_/src/Shiny.Core/Infrastructure/StartupModule.cs:106 
aritchie commented 3 years ago

@jmichas please open different questions going forward (and include some sort of reproducible issue). We're getting into different things. No - this is not why your job is firing multiple times. The multiple jobs being fired is likely already fixed in dev and will be deployed to preview in the next day or two