FunctionExecutor is a robust, multi-threaded, non-blocking task execution utility for Python. It allows you to queue functions for asynchronous execution, making it ideal for scenarios where you need to run multiple tasks concurrently without blocking the main thread.
- Persistent Availability: Once initialized, it remains active and ready to accept tasks throughout your program's lifetime.
- Non-blocking Execution: Add tasks to the queue without waiting for them to complete.
- Multi-threaded: Executes tasks concurrently using a pool of worker threads.
- Automatic Thread Management: No need to manually start or stop threads.
- Flexible Queue Size: Option to set a maximum queue size to prevent memory issues.
- Error Handling: Catches and logs exceptions without stopping execution of other tasks.
- NOTE: The executor doesn't return task results directly.
Copy the FunctionExecutor class into your project. Ensure you have Python 3.6 or later installed.
from function_executor import FunctionExecutor
def my_task(name):
return f"Task {name} completed"
# Create a global executor with 3 worker threads and a max queue size of 10
executor = FunctionExecutor(num_workers=3, max_queue_size=10)
# Add tasks to the queue
for i in range(5):
executor.add_function(my_task, f"Task-{i}")
# The code here runs immediately, not waiting for tasks to completeThe FunctionExecutor remains active and can accept new tasks at any time, even after previous tasks have completed:
# This can be called anytime, anywhere in your code
executor.add_function(task1, "arg1")
# Much later in your program, you can still use the same executor
executor.add_function(task2, "arg2")If you need to wait for all current tasks to complete:
executor.wait_for_completion(timeout=60) # Wait up to 60 seconds- All tasks are added to a single shared queue.
- Multiple worker threads continually attempt to fetch tasks from this shared queue.
- The first available thread that successfully retrieves a task executes it.
- This "work-stealing" model ensures automatic workload balancing across threads.
Note: The order of task execution is not guaranteed to match the order of task addition.
Calling shutdown() is optional and depends on your use case:
- If resource management is critical: Call
shutdown()explicitly when you're done. - If resource consumption is not a concern: You can omit calling
shutdown(). Resources will be cleaned up when your program exits.
# Optional: Shutdown the executor when you're done
executor.shutdown()When shutdown() is called:
- No new tasks can be added.
- The executor waits for ongoing tasks to complete.
- Worker threads are terminated.
- Resources are released.
- Initialize FunctionExecutor as a global variable for persistent availability.
- Set an appropriate
max_queue_sizeto prevent memory issues in long-running applications. - Implement proper error handling within your tasks.
- Use
wait_for_completion()if you need to ensure all tasks are finished before proceeding. - Call
shutdown()explicitly if you need immediate resource release, particularly in long-running applications. - If resource management is not critical and your program is short-lived, you can omit calling
shutdown().
- Tasks are executed in parallel, so be mindful of thread safety in your task implementations.
- The executor doesn't return task results directly. Use other mechanisms to handle task outputs if necessary.
- Task execution order is not guaranteed to match the order of task addition.
- The executor does not provide built-in task prioritization.
Feel free to submit issues or pull requests if you have suggestions for improvements or find any bugs.
This project is open-source and available under the MIT License.