Understanding Multithreading in iOS - ios

I am trying to understand multi-threading on iOS in more detail. I went through some of the class references like NSThread, NSRunLoop, NSTask..
First of all as indicated on the following link:
use of runloop
Runloop runs within a Thread.
So why do we need to define our own Runloop in our app? In the case of NSThread it is useful because some of time-consuming processes can run in a separate thread so that the app will still be responsive on the main thread.

Interacting with the thread's run loop may be useful if you have a thread whose work you want to continue periodically. That is, a run loop would do some work, and then when it is finished with that work, it would put the thread to rest for some time, then resume work at a later time -- effectively preventing the thread from exiting. You won't need to interact with them or configure/create them yourself regularly (only a small percentage of apps would qualify, if you are using high level abstractions such as Foundation because Foundation would set them up on your behalf in most scenarios).
If your secondary thread just does a specified task and does not need to wait for some external event (e.g. a download to finish), you would (typically) not need to interact with the run loop.

You might consider looking at using NSOperationQueues, NSOperations and NSBlockOperations instead as these will manage themselves, will allow for cancellation of tasks and can be scheduled on main and background threads.

Related

Why does Swift not resume an asynchronous function on the same thread it was started?

In the introductory section of the Concurrency chapter of "The Swift Programming Language" I read:
When an asynchronous function resumes, Swift doesn’t make any
guarantee about which thread that function will run on.
This surprised me. It seems odd, comparing for example with waiting on semaphore in pthreads, that execution can jump threads.
This leads me to the following questions:
Why doesn't Swift guarantee resuming on the same thread?
Are there any rules by which the resuming thread could be
determined?
Are there ways to influence this behaviour, for example make sure it's resumed on the main thread?
EDIT: My study of Swift concurrency & subsequent questions above were triggered by finding that a Task started from code running on the main thread (in SwiftUI) was executing it's block on another thread.
It helps to approach Swift concurrency with some context: Swift concurrency attempts to provide a higher-level approach to working with concurrent code, and represents a departure from what you may already be used to with threading models, and low-level management of threads, concurrency primitives (locking, semaphores), and so on, so that you don't have to spend any time thinking about low-level management.
From the Actors section of TSPL, a little further down on the page from your quote:
You can use tasks to break up your program into isolated, concurrent pieces. Tasks are isolated from each other, which is what makes it safe for them to run at the same time…
In Swift Concurrency, a Task represents an isolated bit of work which can be done concurrently, and the concept of isolation here is really important: when code is isolated from the context around it, it can do the work it needs to without having an effect on the outside world, or be affected by it. This means that in the ideal case, a truly isolated task can run on any thread, at any time, and be swapped across threads as needed, without having any measurable effect on the work being done (or the rest of the program).
As #Alexander mentions in comments above, this is a huge benefit, when done right: when work is isolated in this way, any available thread can pick up that work and execute it, giving your process the opportunity to get a lot more work done, instead of waiting for particular threads to be come available.
However: not all code can be so fully isolated that it runs in this manner; at some point, some code needs to interface with the outside world. In some cases, tasks need to interface with one another to get work done together; in others, like UI work, tasks need to coordinate with non-concurrent code to have that effect. Actors are the tool that Swift Concurrency provides to help with this coordination.
Actors help ensure that tasks run in a specific context, serially relative to other tasks which also need to run in that context. To continue the quote from above:
…which is what makes it safe for them to run at the same time, but sometimes you need to share some information between tasks. Actors let you safely share information between concurrent code.
… actors allow only one task to access their mutable state at a time, which makes it safe for code in multiple tasks to interact with the same instance of an actor.
Besides using Actors as isolated havens of state as the rest of that section shows, you can also create Tasks and explicitly annotate their bodies with the Actor within whose context they should run. For example, to use the TemperatureLogger example from TSPL, you could run a task within the context of TemperatureLogger as such:
Task { #TemperatureLogger in
// This task is now isolated from all other tasks which run against
// TemperatureLogger. It is guaranteed to run _only_ within the
// context of TemperatureLogger.
}
The same goes for running against the MainActor:
Task { #MainActor in
// This code is isolated to the main actor now, and won't run concurrently
// with any other #MainActor code.
}
This approach works well for tasks which may need to access shared state, and need to be isolated from one another, but: if you test this out, you may notice that multiple tasks running against the same (non-main) actor may still run on multiple threads, or may resume on different threads. What gives?
Tasks and Actors are the high-level tools in Swift concurrency, and they're the tools that you interface with most as a developer, but let's get into implementation details:
Tasks are actually not the low-level primitive of work in Swift concurrency; Jobs are. A Job represents the code in a Task between await statements, and you never write a Job yourself; the Swift compiler takes Tasks and creates Jobs out of them
Jobs are not themselves run by Actors, but by Executors, and again, you never instantiate or use an Executor directly yourself. However, each Actor has an Executor associated with it, that actually runs the jobs submitted to that actor
This is where scheduling actually comes into play. At the moment there are two main executors in Swift concurrency:
A cooperative, global executor, which schedules jobs on a cooperative thread pool, and
A main executor, which schedules jobs exclusively on the main thread
All non-MainActor actors currently use the global executor for scheduling and executing jobs, and the MainActor uses the main executor for doing the same.
As a user of Swift concurrency, this means that:
If you need a piece of code to run exclusively on the main thread, you can schedule it on the MainActor, and it will be guaranteed to run only on that thread
If you create a task on any other Actor, it will run on one (or more) of the threads in the global cooperative thread pool
And if you run against a specific Actor, the Actor will manage locks and other concurrency primitives for you, so that tasks don't modify shared state concurrently
With all of this, to get to your questions:
Why doesn't Swift guarantee resuming on the same thread?
As mentioned in the comments above — because:
It shouldn't be necessary (as tasks should be isolated in a way that the specifics of "which thread are we on?" don't matter), and
Being able to use any one of the available cooperative threads means that you can continue making progress on all of your work much faster
However, the "main thread" is special in many ways, and as such, the #MainActor is bound to using only that thread. When you do need to ensure you're exclusively on the main thread, you use the main actor.
Are there any rules by which the resuming thread could be determined?
The only rule for non-#MainActor-annotated tasks are: the first available thread in the cooperative thread pool will pick up the work.
Changing this behavior would require writing and using your own Executor, which isn't quite possible yet (though there are some plans on making this possible).
Are there ways to influence this behaviour, for example make sure it's resumed on the main thread?
For arbitrary threads, no — you would need to provide your own executor to control that low-level detail.
However, for the main thread, you have several tools:
When you create a Task using Task.init(priority:operation:), it defaults to inheriting from the current actor, whatever actor this happens to be. This means that if you're already running on the main actor, the task will continue using the current actor; but if you aren't, it will not. To explicitly annotate that you want the task to run on the main actor, you can annotate its operation explicitly:
Task { #MainActor in
// ...
}
This will ensure that regardless of what actor the Task was created on, the contained code will only run on the main actor.
From within a Task: regardless of the actor you're currently on, you can always submit a job directly onto the main actor with MainActor.run(resultType:body:). The body closure is already annotated as #MainActor, and will guarantee execution on the main thread
Note that creating a detached task will never inherit from the current actor, so guaranteed that a detached task will be implicitly scheduled through the global executor instead.
My study of Swift concurrency & subsequent questions above were triggered by finding that a Task started from code running on the main thread (in SwiftUI) was executing it's block on another thread.
It would help to see specific code here to explain exactly what happened, but two possibilities:
You created a non-explicitly #MainActor-annotated Task, and it happened to begin execution on the current thread. However, because you weren't bound to the main actor, it happened to get suspended and resumed by one of the cooperative threads
You created a Task which contained other Tasks within it, which may have run on other actors, or were explicitly detached tasks — and that work continued on another thread
For even more insight into the specifics here, check out Swift concurrency: Behind the scenes from WWDC2021, which #Rob linked in a comment. There's a lot more to the specifics of what's going on, and it may be interesting to get an even lower-level view.
If you want insights into the threading model underlying Swift concurrency, watch WWDC 2021 video Swift concurrency: Behind the scenes.
In answer to a few of your questions:
Why doesn't Swift guarantee resuming on the same thread?
Because, as an optimization, it can often be more efficient to run it on some thread that is already running on a CPU core. As they say in that video:
When threads execute work under Swift concurrency they switch between continuations instead of performing a full thread context switch. This means that we now only pay the cost of a function call instead. …
You go on to ask:
Are there any rules by which the resuming thread could be determined?
Other than the main actor, no, there are no assurances as to which thread it uses.
(As an aside, we’ve been living with this sort of environment for a long time. Notably, GCD dispatch queues, other than the main queue, make no such guarantee that two blocks dispatched to a particular serial queue will run on the same thread, either.)
Are there ways to influence this behaviour, for example make sure it's resumed on the main thread?
If we need something to run on the main actor, we simply isolate that method to the main actor (with #MainActor designation on either the closure, method, or the enclosing class). Theoretically, one can also use MainActor.run {…}, but that is generally the wrong way to tackle it.

What is the difference between DispatchQueue schedule() vs DispatchQueue async() vs DispatchQueue concurrentPerform() for iOS Application

What are the differences between DispatchQueue schedule(), DispatchQueue async() and DispatchQueue concurrentPerform()?
Under what circumstances will it be more appropriate to use each?
I could not find much resource that says the difference between these three.
I went through these:
Links: schedule, concurrentPerform, async, Raywenderlich, AppCoda , EonCodes and few others.
The async is just for asynchronously dispatching a task to a queue (running it as soon as the queue can). It is used to dispatch some block of code to another queue. For example, one might call it from the main thread it to dispatch computationally expensive code off to some background queue, to avoid blocking the main thread. Or, if you are already on a background queue, you use it to dispatch code that must run on the main thread back to the main queue (e.g., UI updates). You can also use asyncAfter if you want to specify when this dispatched task should run (e.g., after a specified time/delay).
The schedule is an API that largely serves the same purpose as async/asyncAfter, but was introduced with Combine in iOS 13. It just dispatches blocks of code to run on the specified queue, optionally with some delay (or other constraints). If you need to support older iOS versions before iOS 13, just use async/asyncAfter instead. But if you are supporting contemporary iOS versions (especially if you are using Combine), then you can use this API if you want.
The concurrentPerform serves a very different functional need, namely if you are for dispatching a block of code repeatedly and in parallel to as many worker threads as your device can support. It is often used when writing computationally intense and massively parallelized routines. It is uniquely well suited for solving those cases where you might otherwise have “thread explosion”. (The number of worker threads that can be used at any given moment at time is quite limited and if you exceed this, your app can deadlock if you accidentally “explode” how many threads you are trying to use at any moment in time.) So for example, if you want to run hundreds or thousands of iterations, in parallel, concurrentPerform automatically constrains the degree of concurrency to the capabilities of your device (e.g. if you have 8 cores in your device, it only runs a maximum of 8 concurrent tasks at any given time). Think of this as a for loop where the various iterations run in parallel with each other. But unless you are writing massively parallelized code, you might not need to ever use this. But when you are, it is extremely useful.

Are threads shared between different Dispatch queues?

Can a single thread be used in different dispatch queues?
I'm developing an app where i want to get a value depending on the thread where the code is running. For that i change the name of the thread when the block starts in a custom queue.
For example, if I add a block to mi custom queue, and i change the name of that thread to "SyncThread". Does a block called in a default system queue will be executed in that "SyncThread"?
Can a single thread be used in different dispatch queues?
Yes, and this is common. (The concept of "different dispatch queues" is itself problematic, since queues can, and do, target other queues. Seemingly simple questions like "what is the current queue" are not well defined.)
I'm developing an app where i want to get a value depending on the thread where the code is running. For that i change the name of the thread when the block starts in a custom queue.
What you likely want for this is queue contextual data rather than thread-specific data (usually called thread-local storage). See DispatchQueue.setSpecific(key:value:).
You use the terms thread and queue interchangeably but they aren't interchangeable; they're two distinct things. Apple doesn't want developers threading anymore, they want developers queueing their tasks. The queueing is handled by GCD which does all the threading behind the scenes. This means that two queues can certainly run on the same thread. And while all tasks within a queue run on distinct threads, not every task is guaranteed to run on the same thread, so keep that in mind. Therefore, I would take the advice given here to focus instead on queue context, not thread context.

Are all methods in an iOS app usually in a single thread? (for race condition prevention)

We can have many handlers: touches handler, UIControl handler (buttons, sliders), performSelector, CADisplayLink, NSTimer events, Gesture Recognizer, accelerometer handler, and UIView animation completion block, and some other ones.
Are all of them in the same thread? That is, only one of them can be running at the same time?
Can some other method or handler be part of another thread and therefore can create race conditions?
In general, you'll find that most simple applications on iOS tend to perform almost every action on the main thread. As you noted, the instant that you bring multithreading into the picture you add another set of tricky issues to watch out for. Many developers don't want to bother with this added complexity, or are unfamiliar with GCD or threading in general, so they avoid doing anything on a background thread or GCD queue.
Several of the items you list in your question involve interactions with UIKit, and in general those interactions must occur on the main thread (iOS 4.x added the ability to perform some drawing functions in the background, though). You receive touch and other related events on the main thread. If you wish to update most aspects of an interface, the safe way to do that is by performing these updates on the main thread.
Timers (NSTimer, CADisplayLink) can have their updates be fired on a background thread by attaching them to an NSRunLoop operating on that background thread. You rarely see people do this, but it can be done. Usually, people configure timers on the main run loop, which causes callbacks to be delivered on the main thread.
When performing animations, the animations themselves will run on a background thread (you see that they don't stop while you're blocking the main thread with something else), but you'll almost always receive a completion block or callback on the main thread when they're done. If I remember correctly, there are one or two exceptions to this and they are noted as such in Apple's documentation. Having these callbacks trigger on the main thread is a safe approach when dealing with developers who might not realize what's going on behind the scenes.
All that said, there are very good reasons to want to add multithreading to your application. Because all user interface updates and touch interactions occur on the main thread, if you have something that is computationally expensive or that simply will take a lot of time to perform, if you run this on your main thread you'll appear to have frozen your application. This is a terrible user experience, so you want to move this task onto a background thread so that the user can keep interacting with your application while this is going on. Additionally, more and more iOS devices are shipping every day with multiple cores in them, and balancing your work load across these cores and being efficient with this hardware requires some degree of concurrent processing.
People have written books about best practices when making code multithreaded, and you can find a lot of questions about this here, so I won't go into too much detail. What I can tell you is that you should read Apple's Concurrency Programming Guide and watch the WWDC videos from the last two years that deal with Grand Central Dispatch. With GCD, Apple has made it a lot easier to add multithreading to your application in an efficient and (relatively) safe manner, and I encourage you to look into this for your own applications.
For example, I have an open source iOS application that performs detailed rendering of molecular structures. I render each frame on a background GCD queue because sometimes they take more than 1/60th of a second to process, and in those cases they'd cause touch events to be dropped and the interface to stutter if this was all on the main thread. Additionally, I've seen up to a 40% performance boost by doing this when running on the newer multicore devices. To avoid race conditions, I wrap interactions with shared data structures and contexts in serial dispatch queues so that only one action can be using a resource at a time, no matter what thread a particular block is running on. This only required the addition of a few lines of code, but the performance and user experience benefits were huge.

when to use multithreading in iOS development?

beside heavy processing, should multithreading mainly be used when you have a not quite responsive UI? or does it have other considerations?
How can I know if my application should have multithreading or not?
One of the Important Application of thread in ios is during network communication.Whole your app is communication with server and if you want to show busy view on UR UI you need to create thread in such scenario to perform network communication in background thread.
In IOS 5,You can opt for GCD(Grand Central Dispatch)Instead of thread to perform same functionality..
Basically in iOS development Threads are used when you don't want to affect you UI by a process which will take long time to complete. for example when you make a connection to parse xml,json,image data etc then you don't want to stop user interaction at that time you can use threads.
you can start a thread by using NSThread.
Things to have in mind before using threads -
You should never do a graphical change in a thread. If you need to
that in a thread then you can do only on main thread.
Never use a NSTimer in a secondary thread, because your thread may
complete before timer execution so timer may not run.
whenever you want to perform a long process then you can use thread.
The use of threading in ios is to ensure hussle-free and seamless experience by the end-users.
You can implement thread whenever you want to extract some resource over the network such as parsing or data retrieval and you don't want the ui to be affected as application would run on main thread and the web-operation on your custom thread.
You may want to use the thread when you need to have concurrent operations or simultaneous such as in game when you hae to have multiple animations on same object at same time.There can be quite a large number of scenarios which may need threading.
You may read Concurrency Programming Guide By Apple
and Thread Management
but threads may be an overhead in the application as it needs memory allocation and large operations on thread may affect the performance so use it when it can't be avoided.
You can use NSThread,NSOperations to create threads .GCD is deprecated now.

Resources