This bug report, especially comment https://developer.blender.org/T62074#632298, is relevant. Passing in the window and screen context seems to solve the crash. I.e. this works for me:
import threading
import bpy
from random import randint
def addSimpleCube(ctx):
print(threading.current_thread().name, "executing add simple cube function")
bpy.ops.mesh.primitive_cube_add(ctx, location=(randint(-10,10),randint(-10,10),randint(-10,10)))
def execute_queued_functions():
window = bpy.context.window_manager.windows[0]
ctx = {'window': window, 'screen': window.screen}
print(threading.current_thread().name, "timer exec")
addSimpleCube(ctx)
return 1.0
bpy.app.timers.register(execute_queued_functions)