Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Help me understand Pythonista’s threads
-
I would like to understand how Pythonista uses threads, but cannot find any real documentation on the topic.
Are these simple assumptions correct?
- Main program runs in the "main interpreter thread".
- Functions decorated with @ui.in_background also run in the main interpreter thread.
- UI runs in the "main UI thread".
- Main UIKit thread (accessed using the @on_main_thread decorator) is the same thread as the UI thread in the previous point.
- There are no other "standard" threads used by Pythonista scripts.
-
I'm not an iOS development expert, but that is all correct as far as I know.
It's a bit confusing that both the Python interpreter thread and the UI thread are sometimes called the "main XXX thread". The UI thread is "main" in the sense that it's the standard thread provided/used by UIKit (and the only thread on which you can use many UI-related iOS APIs). The Python interpreter thread is "main" in the sense that it is the default thread that Python scripts run on if they don't create any new Python threads.
-
I was curious about this, so wrote something to check most of the use cases I could think of...
gist / execurlThe results are below:
##### current thread: b'com.apple.root.default-qos.overcommit' <_MainThread(MainThread, started 1078071296)> ##### things running from a thread: undecorated b'com.apple.root.default-qos.overcommit' <Thread(Thread-25, started 1085984768)> on_main_thread b'com.apple.main-thread' <_DummyThread(Dummy-1, started daemon 994000896)> animated b'com.apple.main-thread' <_DummyThread(Dummy-1, started daemon 994000896)> animatcompletion b'com.apple.main-thread' <_DummyThread(Dummy-1, started daemon 994000896)> delayed b'com.apple.main-thread' <_DummyThread(Dummy-1, started daemon 994000896)> in_background b'com.apple.root.default-qos.overcommit' <_MainThread(MainThread, started 1078071296)> ##### Called from UI (press each button) update b'com.apple.main-thread' <_DummyThread(Dummy-1, started daemon 994000896)> 0 undecorated b'com.apple.main-thread' <_DummyThread(Dummy-1, started daemon 994000896)> 1 on_main_thread b'com.apple.main-thread' <_DummyThread(Dummy-1, started daemon 994000896)> 2 in_background b'com.apple.root.default-qos.overcommit' <_MainThread(MainThread, started 1078071296)> 3 delayed b'com.apple.main-thread' <_DummyThread(Dummy-1, started daemon 994000896)> 4 animated b'com.apple.main-thread' <_DummyThread(Dummy-1, started daemon 994000896)> animatcompletion b'com.apple.main-thread' <_DummyThread(Dummy-1, started daemon 994000896)>
Basicaly, ui things, including actions/callbacks/update, and things decorated with on_main_thread are run on what ios calls main-thread queue, which is called the Dummy-1 by Python.
ui.in_background gets called on the python MainThread (main interpreter thread), from the standard dispatch queue.
Everything else is run on the standard queue also, but in whatever python thread called it - so either a threading.Thread or MainThread (main interpreter thread).
I don't really understand how threads work with the GCD dispatch queues, but the bottom line seems to be that ui.in_background items only get executed on the python interpreter main thread after the script has ended (so some care has to be used with in_background, since it might not run if your main script is doing other work, or sleeping, etc)
-
Thanks guys!
I will try one more for size:
- Scenes run in the main UI thread.
-
I wrote a riff on @JonB’s script, seemed to confirm #6. Same result whether running the scene with
run
or with explicitSceneView
. -
I find it hard to get my head around your discussion here although I would like to. If anyone has inclination and is able to, it would be great to see a diagram of how this would work. If not, thats ok, I understand.
-
Basically, there are two threads
- The main thread, or ui thread
- The interpreter thread
plus 3) Any threads you create with threading
Any ui callbacks, like layout, update, button actions, delegate methods, etc, are called on the main/ui thread. Nothing interrupts the ui/main loop, so any long calculations will make the app unresponsive. The main thread has a queue of operations it is executing -- touch events add callbacks to this queue. So does on_main_thread.
The interpreter thread is basically a background thread. ui.in_background lets you run long calcs that would have been in th ui thread in the background thread instead. It too only runs one thing at a time, but can be interrupted by the main thread whenever events happen. The interpeter thread also has a queue of operations -- it starts with the current script being "played". ui.in_background is the way to append things to this background queue -- the name can be a bit misleading, since it is the background to the ui thread, but would be done after anything already in the interpreter queue.
Any other threads you create share time with the interpreter thread.
A way to think about this all is to imagine a hard loop.
First, sequentially run operations in the main queue, which are put there either by the OS ir by calling on_main_thread
Then, give a little time to each background thread. Operations are queued up into the first background thread by either pressing play, typing commands in the console, or by ui.in_background, while other background threads just run to completion.while True: for operation in main_queue: operation.run() for t in local_threads: t.run_for_5_msec()
-
@JonB , hey thanks for the time to write this explanation. I at least think I have a lot better understanding now. Eg, observing how Pythonista starts up. You are right about the ui.in_background being misleading (at least for people like me). But your explanation, really helps to comprehend it.
It's funny for some reason even though there is a lot of evidence to the contrary, I would have thought the the interpreter thread was the main thread. Just some how logically my brain went that way. So its great to get some clarity on that, even though its been starring me in the face.
The only question I have, are the threads you create yourself handled 100% the same as using the ui.in_background decorator, in that they are just added as another task to the interpreter thread or is that considered an independent thread that the interpreter thread has no knowledge about.But again, thanks for the great explaination. I think your post will be useful to a lot of people.
@mikael, thanks for posing the question.