Asynchronous Task Management with Python's asyncio
Background tasks are essential for building responsive and efficient applications. They allow you to perform time-consuming operations (like network requests, database queries, or complex calculations) without blocking the main thread, ensuring your application remains interactive. This challenge will guide you in implementing background tasks using Python's asyncio library.
Problem Description
You are tasked with creating a system that can schedule and execute asynchronous tasks. The system should allow you to define tasks that run in the background and provide a mechanism to monitor their status. The core functionality involves defining a task (represented by a coroutine function), scheduling it to run asynchronously, and optionally retrieving its result when it completes. Consider error handling and task cancellation as important aspects of a robust background task system.
Key Requirements:
- Task Definition: A way to define a task as an asynchronous function (coroutine).
- Task Scheduling: A mechanism to schedule the task for execution in the background without blocking the main thread.
- Task Monitoring: A way to check the status of a task (e.g., pending, running, completed, cancelled, failed).
- Result Retrieval: A way to retrieve the result of a completed task.
- Error Handling: Gracefully handle exceptions that occur during task execution.
- Task Cancellation: Provide a mechanism to cancel a running task.
Expected Behavior:
The system should allow you to:
- Define an asynchronous task.
- Schedule the task for background execution.
- Check the status of the task.
- Retrieve the result of the task when it completes successfully.
- Handle exceptions that occur during task execution.
- Cancel a task that is currently running.
Edge Cases to Consider:
- Tasks that take a very long time to complete.
- Tasks that raise exceptions.
- Attempting to retrieve the result of a task that has not completed.
- Attempting to cancel a task that has already completed or has not started.
- Multiple tasks being scheduled concurrently.
Examples
Example 1:
Input:
task_func = async def my_task():
await asyncio.sleep(2)
return "Task completed!"
scheduler = BackgroundTaskScheduler()
task = scheduler.create_task(task_func)
asyncio.sleep(1) # Let the task start
print(f"Task status: {task.status()}")
await asyncio.sleep(3) # Wait for the task to complete
print(f"Task result: {task.get_result()}")
Output:
Task status: running
Task result: Task completed!
Explanation: A simple task that sleeps for 2 seconds and returns a string is created and scheduled. The status is checked while running, and the result is retrieved after completion.
Example 2:
Input:
task_func = async def failing_task():
await asyncio.sleep(1)
raise ValueError("Something went wrong!")
scheduler = BackgroundTaskScheduler()
task = scheduler.create_task(task_func)
asyncio.sleep(2)
print(f"Task status: {task.status()}")
try:
result = task.get_result()
except ValueError as e:
print(f"Task failed: {e}")
Output:
Task status: failed
Task failed: Something went wrong!
Explanation: A task that raises a ValueError is created. The status is checked, and the exception is caught when attempting to retrieve the result.
Example 3: (Cancellation)
Input:
task_func = async def long_running_task():
try:
await asyncio.sleep(5)
return "Long task completed"
except asyncio.CancelledError:
print("Task was cancelled")
return None
scheduler = BackgroundTaskScheduler()
task = scheduler.create_task(task_func)
asyncio.sleep(2)
task.cancel()
asyncio.sleep(1)
print(f"Task status: {task.status()}")
result = task.get_result() # Should not raise an exception, returns None
print(f"Task result: {result}")
Output:
Task status: cancelled
Task result: None
Explanation: A long-running task is created. It is cancelled after 2 seconds. The task status is checked, and the result is retrieved (which should be None). The task's exception handler prints "Task was cancelled".
Constraints
- The solution must use Python's
asynciolibrary. - The
create_taskmethod should return an object that allows monitoring the task's status and retrieving its result. - The task scheduler should be able to handle multiple tasks concurrently.
- The
get_resultmethod should raise an appropriate exception if the task has not completed or has failed. - The
cancelmethod should attempt to cancel the task gracefully. - The solution should be reasonably efficient and avoid unnecessary overhead.
- The code should be well-documented and easy to understand.
Notes
- Consider using
asyncio.create_taskinternally to schedule the tasks. - You can use a dictionary or other data structure to keep track of the tasks and their statuses.
- Think about how to handle exceptions that occur within the tasks.
try...exceptblocks within the task coroutine are crucial for graceful handling. - The
asyncio.CancelledErrorexception is raised when a task is cancelled. Your task coroutines should be prepared to handle this exception. - The
get_resultmethod should wait for the task to complete if it is still running. Useawaitappropriately. - Focus on creating a clean and modular design. A class-based approach is recommended for the task scheduler.