How to check for Main thread from the background?

I have a background operation (a web fetch) that grabs some data from the internet. I need to dispatch out that data as soon as possible during the fetch and have been using the following:


    func dispatchWebData(myNumber: Double) {
        if UIApplication.shared.applicationState == .active  {
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {
                self.myFunction(myNumber: myNumber)
            }
        } else {
            DispatchQueue.global(qos: .userInteractive).asyncAfter(deadline: .now() + 0.2) {
                self.myFunction(myNumber: myNumber)
            }
        }
    }


If the app is active, data handling is dispatched to the Main thread and the UI gets updated as soon as the data is available. If the app is not active, the main thread is not available so I dispatched data handling to the global thread. This was/is working fine.

Now the MainThread Checker is sending me the little purple square warning that "UIApplication.applicationState must be used from main thread only".

First, it seems silly that you can't check the application state from the background. What's the point if you can only check the state while the state is Active?

But I wouldn't care about that issue as long as I can get my data handling to move forward. So I'm wondering is if I can check for the existence of the main thread and then dispatch to it if it's there, otherwise to the global thread. Other ideas welcome.

Replies

There's a difference between "app in background" and "background thread". The API warning is about the thread, not the application (though the application state does affect the larger problem, of course).


I'm sorry if this is a stupid question, but why can't you track the app state in your app delegate? There are delegate methods to override for each possible state change. Provided you update it atomically (or otherwise thread-safely), you can maintain a global variable with the state, can't you?

Yes, I suppose I could do that. Do you think that's the most elegant way to decide which thread to dispatch to? It doesn't feel elegant and I was hoping there was some simple way to handle this that I've overlooked.

I didn't answer that part of the question because I've never run into this scenario. I'm surprised that the main queue is absent (even if its thread is gone), but I suppose it's not entirely illogical for a background task. Maybe someone with more experience in background tasks can give you a more helpful answer.

What are you doing with the data when it's done being received? Storing, storing and forwarding, updating the UI, running complex calcs...is it fault tolerant if queued or is that not a factor? How often is it updated?


Is there a basic function that allows the app to run/do things in the background otherwise?

It's just a warning I'm trying to clear. As far as I can tell, the app runs fine with no issues.


Well, one issue. I'm still struggling with the problem in this thread: https://forums.developer.apple.com/thread/94899

In that thread I found that clearing some layout constraint conflicts temporarily solved that problem. Now the problem has returned and so I'm trying to clear up warnings and such in hope that it will solve the "denied launch" issue.

If the app is not active, the main thread is not available …

This is where you’ve left the path of sanity (-: I’m not sure why you think that the main thread is unavailable when your app is inactive but that’s simply not true.

The active / inactive state of your app has no affect on which threads run.

What does have an effect is whether your app is in the foreground or the background. If your app is in the foreground and there’s no specific reason to keep it running, your app will get suspended. At this point all threads in your process stop running. This means that there’s never a point where your background thread is running but the main thread is not.

Having said that, if you’re doing networking while your app is eligible for suspension then you do have to be careful because, if your app gets suspended while there’s a networking request in flight, it’s likely that this request will fail. There’s a bunch of ways you can deal with this:

  • Accept the failure and retry.

  • Use a UIApplication background task to prevent the app from being suspended while your networking request is in flight. However, you still have to deal with this possibility that this task might expire.

    If you plan to go down this route make sure you read my UIApplication Background Task Notes post.

  • Cancel any network requests when you become eligible for suspension.

  • Run the networking requests in a background session.

    IMPORTANT Background sessions are best suited to handling a small number of large requests. Moving all of your work to a background session is probably not a good idea.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I have to admit I have no memory of why I was differentiating between active and inactive application states as the deciding factor for which queue to dispatch my function to. I'm testing right now to see if dispatching only to the main queue works in all cases.

>Now the MainThread Checker is sending me the little purple square warning that "UIApplication.applicationState must be used from main thread only".


I skimmed posts in this thread a bit, so what I'm saying may have already been said by someone else. But UIKit is main thread only. So if you do work on the background and then want to update your UI make sure your UI code is on the main queue:


dispatch_async(dispatch_get_main_queue(), ^{
     //UI code here.

 });


That means don't update anything like a UILabel, UITableView, ect. from the background. The main thread checker is telling you not to call UIApplication.applicationState from a background queue.


If you want to check if you are already on the main thread you could try:


if (NSThread.currentThread.isMainThread)
{

}


or maybe:

if ([NSOperationQueue.currentQueue isEqual:NSOperationQueue.mainQueue])
{

}

But if you *know* certain code is always going to be called from a background queue you shouldn't need to check if you are on the main thread, just dispatch to the main without asking.

For the record, I can't detect any problem caused by adjusting my code above to dispatch only to the main thread queue whether the app is active or not. I had experimented quite a while ago with using the global queue instead for some reason, but that reason is now lost to antiquity. 😕


Thanks all for setting me straight.

Keeping a flag on the AppDelegate and update it in the delegate methods is not just better, but necessary now.

Using a method that requires main_thread when you are not foreground will trigger the exception reporting and sometimes hangs your app (it works most of the time, but not always). I learned it the hard way in production.
I am doing it like this:

Code Block
    if Thread.isMainThread {
      return UIApplication.shared.applicationState
    } else {
      var state: UIApplication.State?
      DispatchQueue.main.sync {
        state = UIApplication.shared.applicationState
      }
      return state!
    }


works like a charm

I am doing it like this:

The main drawback with this approach is its latency. If you’re running on a secondary thread then you need to bounce to the main thread to get the value. If main thread is busy, that round trip can take a long time. If someone is calling this code a lot, those delays will add up.

Honestly, I like billylo’s approach. You can protect that variable with a lock and because you only take that lock while reading the variable there’s unlikely to be any significant latency.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
This can be done using completion handler. As soon as it gets the data, the data can be sent to the UI using the main thread (DispatchQueue.main.async{}).