Using Python's concurrent.futures module you can have it dispatch work to a thread pool then have it execute code when complete without blocking main.
There are a lot of horrible things in threading though. It is hard.
Also, noticed this which offers an alternative implementation using asyncio (native python 3.4+) which you should definately check out.
I've sketched a really poor implementation of this below which has a number of short comings and has some 'fill in the blank' sections for your actual GPIO / SPI code and the saving data sections. But it's runnable (unless i mangled the formatting). If you need this to run constantly so you get many more IR readings than SPI ones then you'll have to do something a bit more complicated, probably with queues, so the worker keeps running jobs rather than just doing it once per cycle. I couldn't quite tell if thats the intention though.
from concurrent import futures
from time import sleep, time
import multiprocessing
# number of threads in pool goes here, it'll queue work if you have jobs > workers
tpe = futures.ThreadPoolExecutor(max_workers=multiprocessing.cpu_count())
def do_spi_work():
sleep(1.1)
return "done SPI work!"
def do_ir_work():
sleep(0.3)
return "done IR work!"
def handle(result):
# you'd put your saving routine in here note this is still the
# worker thread - files, sqlite, mdb might not be happy with multi threaded access
status = result.result()
print("i just got a callback that said {}".format(status))
def read_button():
# do your button read code here, something like..
# result = GPIO.input(4)
result = True
return result
def check_break(count):
#some condition
return count > 5
count = 0 # this is just for check_break, this code only loops 5 times
# 'main' starts here
while(True):
if read_button():
start_time = time() #do your timing stuff here as its fast
#these won't block
tpe.submit(do_spi_work).add_done_callback(handle)
tpe.submit(do_ir_work).add_done_callback(handle)
# if you need both threads to talk to each other then its more fun...
# [insert horror here]
sleep_time = 1000 * (time() - start_time) #then your sleeping code to block here.
sleep(sleep_time) # blocks
# and something to check if you want to quit this loop you'd make check_break less terrible.
# you might want to wrap this in a try/catch to clean up GPIO when the program is killed too.
if check_break(count):
break
count += 1
else:
sleep(1) # some sort of back off to prevent it looping too fast
If you just want to push the SPI read to churn in the background while blocking on each (fast) GPIO read in the main loop you can probably do this without the futures just use normal Python threading stuff whilst looping the SPI read and save routine in the background. You will need some mechanism to allow the thread to end. My example uses a global state variable for brevity.
from threading import Thread
from time import time, sleep
def spi_worker():
global state
while (not state):
result = read_spi()
save_spi(result)
def read_spi():
sleep(0.5)
return "spi result"
def save_spi(result):
print('SPI: i just saved {}'.format(result))
def read_gpio():
sleep(0.1)
return "GPIO result"
def save_gpio(result):
print("GPIO: i just saved {}".format(result))
state = False
def check():
global state
return state
t = Thread(target=spi_worker)
t.start()
count = 0
while (not check()):
start_time = time()
#do work on main
result = read_gpio()
save_gpio(result)
#nap
sleep_time = (time() - start_time)
sleep(sleep_time)
if (count > 50):
state = False
break;
count += 1
Again, this is probably riddled with threading issues but should give you an idea of how it could be done.