2

Although this is happening within a Xamarin.Mac project, I think the issue is more to do with MacOS, as the signs are that of App Nap.

In my AppDelegate.cs file, I have this:

public override void DidFinishLaunching(NSNotification notification)
{
    _activity = NSProcessInfo.ProcessInfo.BeginActivity(
                    NSActivityOptions.Background |
                    NSActivityOptions.LatencyCritical,
                    "You charge $3,500AUD for this laptop and yet " +
                    "can't provide enough resources for a smooth " +
                    "operation of this app in the background? " +
                    "Eat a d#@k Apple."
                );
    // ...

I tested the above with the following that runs every ~1s:

var now = DateTime.Now;
var now_str = now.ToString("HH:mm:ss.fff");
Debug.WriteLine(now_str);
var idle_time = now - _prevTime;
if (idle_time.TotalMilliseconds > 1200)
{
    Debug.WriteLine("FFSakes Apple!");
}
_prevTime = now;
await Task.Delay(1000); // yes yes, I know timers aren't precise, hence why I said '~'1s.

After hours of banging my head, I decided to run the app, put it in the background and go have a nap. Turns out, so did my app. In the 1.5 hours I left it on, it did this:

19:23:29.040
19:23:30.041
19:56:07.176
FFSakes Apple!
19:56:08.196
19:56:09.196
...

A lag of over half an hour!

Question 1: Why?
Question 2: How do I get around this?

Repro: https://github.com/FunkyLambda/AppNapRepro

Ash
  • 2,021
  • 2
  • 26
  • 59
  • I should mention that I am running this app in Debug mode via Visual Studio for Mac. – Ash May 13 '20 at 12:12

1 Answers1

5

You didn't instruct your app to avoid napping. The correct options are:

userInitiated:

Flag to indicate the app is performing a user-requested action.

userInitiatedAllowingIdleSystemSleep:

Flag to indicate the app is performing a user-requested action, but that the system can sleep on idle.

Extend App Nap documentation:

If an app isn’t performing user-initiated work such as updating content on screen, playing music, or downloading a file, the system may put the app in App Nap.

Your app is using background:

Flag to indicate the app has initiated some kind of work, but not as the direct result of user request.

And the documentation again ...

App Nap conserves battery life by regulating the app’s CPU usage and by reducing the frequency with which its timers are fired.

... which is what you probably see as a result.


Even if I specify LatencyCriticial, the system can still make my app nap, because I've only specified Background?

Yes. You can simply check it by yourself. Run your app, open Activity Monitor, right click on the table header row and add the App Nap & Preventing Sleep columns. Keep your app running for a while, couple of minutes are enough, and then check these column values.

Essentially, does the condition for App Nap simplify to: if not user-initiated and it's not updating view, it may be made to nap?

Nope, check the documentation.

Generally, an app is a candidate for App Nap if:

  • It isn’t the foreground app
  • It hasn’t recently updated content in the visible portion of a window
  • It isn’t audible
  • It hasn’t taken any IOKit power management or NSProcessInfo assertions
  • It isn’t using OpenGL

When the above conditions are met, OS X may put the app in App Nap.

Also notice the difference - may put != will put, ... Heuristic behind it and it the behavior can slightly differ on different macOS versions.

My app when opened runs several processes, none of which are user initiated, but important for the user to have running without interruption, so if I leave it with the configuration I have but make it update the view as part of the periodic timer, it should be exempted from App Nap?

I wouldn't focus too much on user initiated words. userInitiated docs says:

Flag to indicate the app is performing a user-requested action.

It basically means anything that user is waiting for. And if you're updating UI, based on outputs of some tasks that weren't initiated by the user via some button for example, it can still be user-requested action (user launched your app to get some results, ...).

More info

I don't know what your app is supposed to do -> it's hard to recommend any approach how to achieve it. But I'd highly recommend to watch:

To read:

Even if these presentations are old, it's an archived documentation, ... it's still valid and contains lot of useful info.

zrzka
  • 20,249
  • 5
  • 47
  • 73
  • Thanks for that. Just to clarify: 1) even if I specify `LatencyCriticial`, the system can still make my app nap, because I've only specified `Background`? 2) Essentially, does the condition for App Nap simplify to: if not user-initiated and it's not updating view, it may be made to nap? 3) My app when opened runs several processes, none of which are user initiated, but important for the user to have running without interruption, so if I leave it with the configuration I have but make it update the view as part of the periodic timer, it should be exempted from App Nap? – Ash May 23 '20 at 01:43
  • Awarded bounty but can only accept answer once those items in my previous comment are clarified. – Ash May 23 '20 at 12:15
  • @Ash did update the answer with additional info. You should really watch these presentations. – zrzka May 25 '20 at 08:00
  • Don't completely understand how my app became a candidate for app nap given that I did make `NSProcessInfo` assertions...if the assertions needed to include `userInitiated`, then that's what I meant by point (2). In any case, I've accepted the answer since it has solved my immediate problem of napping; have added `userInitiated`, despite the wording not making sense, but going by what you said about not focusing on the wording. – Ash Jun 14 '20 at 03:53