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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
86
doc/python_api/rst/info_gotchas_threading.rst
Normal file
86
doc/python_api/rst/info_gotchas_threading.rst
Normal 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.
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user