Multi-threading is no easy task to wrap your head around, and there are plenty of pit-falls when it comes to using it in in Touch. Below we have three simple examples of seeing how that works. It’s not the most thrilling topic in the world, until it’s something that you need – desperately – then it might just save your project.
You can grab the whole repo here: touchdesigner-multi-threading
Three Examples
text_pyThreads
A simple example of creating a function that runs in another thread. As noted in the forum reference post it’s important to consider what operations can potentially create race conditions in Touch. The suggested consideration here is to avoid the use of any operations that will interact with a touch Object. Looking more closely at this example we can see here that we’re using only Pythonic approaches to editing an external file. In our first example we use a simple text file approach to ensure that we have the simplest possible exploration of a concept.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import threading | |
import os | |
path_to_file = "temp-files/" | |
file_name = "attempt1" | |
def workFucnt1(path_to_file, file_name): | |
ouptput_file = path_to_file + file_name + '.txt' | |
working_file = open(ouptput_file, 'w') | |
working_file.write("Start Working Function") | |
for number in range(100000): | |
working_file.write("\nFor loop {} entry".format(number)) | |
working_file.write('\nEnding working Function') | |
working_file.close() | |
return | |
# un-comment this line below to see the process block touch | |
# workFucnt1(path_to_file, file_name) | |
# un-comment the bottom two lines to see the process | |
# execute without blocking Touch | |
myThread = threading.Thread(target=workFucnt1, args=(path_to_file, file_name,)) | |
myThread.start() |
text_pyThreads_openCV
In the opeCV example we look at how one might consider taking the approach of working with image processing through the openCV library. While this example only creates a random red circle, with a little imagination we might see how this would be useful for doing an external image processing pass – finding image features, identifying colors, etc. Running this as a for loop makes it easy to see how this process can block TouchDesigner’s main thread, and why it would be useful to have a means of executing this function in a way that minimizes impact on the running project.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# import libraries | |
import cv2 | |
import numpy | |
import threading | |
import os | |
import random | |
path_to_file = "temp-files/imgs/" | |
file_name = "attempt{file}.png" | |
def workFucnt1(path_to_file, file_name): | |
for each in range(500): | |
# creating a circle with openCV | |
# set up numpy array | |
img = numpy.zeros( (512,512,3), numpy.uint8 ) | |
# format the file path | |
fileName = file_name.format(file=each) | |
randSeed = each | |
path = path_to_file + fileName | |
xPos = int( tdu.rand(randSeed) * 512 ) | |
yPos = int( tdu.rand(randSeed + 1) * 512) | |
# draw a circle with openCV | |
circle = cv2.circle(img, (xPos, yPos), 63, (0,0,255), –1) | |
# save the resulting image | |
cv2.imwrite(path, circle) | |
return | |
# un-comment this line below to see the process block touch | |
# workFucnt1(path_to_file, file_name) | |
# un-comment the bottom two lines to see the process | |
# execute without blocking Touch | |
myThread = threading.Thread(target=workFucnt1, args=(path_to_file, file_name,)) | |
myThread.start() |
text_pyThreads_queue
While the example pyThreads_opneCV
is an excellent start, that doesn’t help us if we need to know when an outside operation has completed. The use of Queue helps resolve this issue. A Queue object can be placed into storage and act as an interchange between threads. It’s marked as a thread safe operation, and in our case is used to help track when we’re working function is “Processing” or “Ready”. You’ll notice that the complications here are that we have a less than ideal need to use an execute DAT to run a frame start script to check for our completed status each frame. This is less than ideal but a reasonable solution for an otherwise blocking operation. Notice that the execute DAT will disable the operation of the frame start script after it’s “Ready”. This kind of approach helps to ensure that the execute DAT only runs the frame start script when necessary and not every frame.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# import libraries | |
import threading | |
import os | |
import queue | |
path_to_file = "temp-files/" | |
file_name = "attempt2" | |
target = op('table1') | |
frameStart = op('execute1') | |
myQ = queue.Queue() | |
target.store('myQ', myQ) | |
def workFucnt1(path_to_file, file_name, myQ): | |
myQ.put("Processing") | |
ouptput_file = path_to_file + file_name + '.txt' | |
working_file = open(ouptput_file, 'w') | |
working_file.write("Start Working Function") | |
for number in range(100000): | |
working_file.write("\nFor loop {} entry".format(number)) | |
working_file.write('\nEnding working Function') | |
working_file.close() | |
myQ.put("Ready") | |
return | |
frameStart.par.framestart = 1 | |
# un-comment this line below to see the process block touch | |
# workFucnt1(path_to_file, file_name, myQ) | |
# un-comment the bottom two lines to see the process | |
# execute without blocking Touch | |
myThread = threading.Thread(target=workFucnt1, args=(path_to_file, file_name, myQ,)) | |
myThread.start() |
Forum Example
Forum Example 1 python_threading_sample.toe
An example from the forum used to sort out the essential pieces of working with multiple threads, queues, and how to approach this issue without crashing Touch. Many thanks to the original authors for their work helping to shed some light on this murkey part of working in TouchDesigner.