Py Docs: Gotcha chapter on threading

Update the Python API documentation about crashes with multi-threaded
code.

- Move the "threading gotcha" into a file of its own. That way it's
  immediately clear from looking at the Gotcha table of contents that
  threading is not supported.
- Simplify the example code, so that it doesn't access `bpy`.
  Apparently the problem is much wider than just multi-threaded access
  to `bpy`, and involves _all_ Python threads, regardless of what they
  do / access.
- Add some more explanation and move some text from the bottom to the
  top, so that the first-read part (when reading top to bottom) has
  most of the information.

Pull Request: https://projects.blender.org/blender/blender/pulls/139279
This commit is contained in:
Sybren A. Stüvel
2025-05-22 16:23:23 +02:00
parent 881199d92c
commit 58d0b0e4b4
4 changed files with 88 additions and 74 deletions

View File

@@ -11,6 +11,7 @@ that can be troublesome and avoid practices that are known to cause instability.
:maxdepth: 1
info_gotchas_crashes.rst
info_gotchas_threading.rst
info_gotchas_internal_data_and_python_objects.rst
info_gotchas_operators.rst
info_gotchas_meshes.rst

View File

@@ -2,80 +2,6 @@
Troubleshooting Errors & Crashes
********************************
Strange Errors when Using the 'Threading' Module
================================================
Python threading with Blender only works properly when the threads finish up before the script does,
for example by using ``threading.join()``.
Here is an example of threading supported by Blender:
.. code-block:: python
import threading
import time
def prod():
print(threading.current_thread().name, "Starting")
# Do something vaguely useful.
import bpy
from mathutils import Vector
from random import random
prod_vec = Vector((random() - 0.5, random() - 0.5, random() - 0.5))
print("Prodding", prod_vec)
bpy.data.objects["Cube"].location += prod_vec
time.sleep(random() + 1.0)
# Finish.
print(threading.current_thread().name, "Exiting")
threads = [threading.Thread(name="Prod %d" % i, target=prod) for i in range(10)]
print("Starting threads...")
for t in threads:
t.start()
print("Waiting for threads to finish...")
for t in threads:
t.join()
This an example of a timer which runs many times a second
and moves the default cube continuously while Blender runs **(Unsupported)**.
.. code-block:: python
def func():
print("Running...")
import bpy
bpy.data.objects['Cube'].location.x += 0.05
def my_timer():
from threading import Timer
t = Timer(0.1, my_timer)
t.start()
func()
my_timer()
Use cases like the one above which leave the thread running once the script finishes
may seem to work for a while but end up causing random crashes or errors in Blender's own drawing code.
So far, no work has been done to make Blender's Python integration thread safe,
so until it's properly supported, it's best not make use of this.
.. note::
Python threads only allow concurrency and won't speed up your scripts on multiprocessor systems,
the ``subprocess`` and ``multiprocess`` modules can be used with Blender to make use of multiple CPUs too.
.. _troubleshooting_crashes:
Help! My script crashes Blender

View File

@@ -0,0 +1,86 @@
********************************
Python Threads are Not Supported
********************************
In short: Python threads cause Blender to crash in hard to diagnose ways. For
example, a crash can occur while rendering with Cycles, with Python drivers,
while a background thread is used to download some file.
So far, no work has been done to make Blender's Python integration thread safe,
so until it's properly supported, it's best not make use of this.
Note that some modules in the Python standard library may use threads as well.
An example is the `multiprocessing.Queue <https://docs.python.org/3/library/multiprocessing.html#multiprocessing.Queue>`_
class.
Python threading with Blender only works properly when the threads finish up
before the script does, for example by using ``threading.join()``. In other
words, they can only be used while the main Blender thread is blocked from
running.
Alternative Approaches
======================
For running Python code independently of Blender, it is recommended to use the
`multiprocessing <https://docs.python.org/3/library/multiprocessing.html>`_ module.
Code Examples
=============
Here is an example of threading supported by Blender:
.. code-block:: python
import threading
import requests
urls = [
"http://localhost:8000/file-1.blend",
"http://localhost:8000/file-2.blend",
"http://localhost:8000/file-3.blend",
]
def download(url: str) -> None:
name = threading.current_thread().name
print("{}: Starting".format(name))
response = requests.get(url)
print("{}: Request status code {}".format(name, response.status_code))
threads = [
threading.Thread(
name="thread-{}".format(index),
target=lambda: download(url),
)
for index, url in enumerate(urls)
]
print("Starting threads...")
for t in threads:
t.start()
print("Waiting for threads to finish...")
for t in threads:
t.join()
print("Threads all done, now Blender can continue")
This an example of an **unsupported** case, where a timer which runs many times
a second:
.. code-block:: python
from threading import Timer
def my_timer():
t = Timer(0.1, my_timer)
t.setDaemon(True)
t.start()
print("Running...")
my_timer()
Use cases like the one above, which leave the thread running once the script
finishes, may seem to work for a while, but end up causing random crashes or
errors in Blender's own drawing code.

View File

@@ -420,6 +420,7 @@ INFO_DOCS_OTHER = (
"info_advanced_blender_as_bpy.rst",
# Included by: `info_gotcha.rst`.
"info_gotchas_crashes.rst",
"info_gotchas_threading.rst",
"info_gotchas_internal_data_and_python_objects.rst",
"info_gotchas_operators.rst",
"info_gotchas_meshes.rst",