Concurrent Task Spawning with Error Handling in Rust
This challenge focuses on implementing a system for spawning multiple tasks concurrently in Rust, utilizing threads and channels for communication. The goal is to demonstrate proficiency in Rust's concurrency features, including thread management, channel-based communication, and robust error handling. This is a fundamental skill for building responsive and efficient applications.
Problem Description
You are tasked with creating a function spawn_tasks that takes a vector of functions (closures) as input. Each function represents a task that needs to be executed concurrently. The spawn_tasks function should spawn a new thread for each task, passing a channel to each thread. Each task should execute its function and send a result (or an error message if the function panics) through the channel. The main thread should then collect the results from the channels and store them in a vector.
Key Requirements:
- Concurrency: Tasks must be executed concurrently using threads.
- Channel Communication: Use channels to communicate results (or errors) from the spawned threads back to the main thread.
- Error Handling: If a task panics, the spawned thread should send an error message through the channel instead of the result. The main thread should collect and report these errors.
- Result Collection: The main thread must collect all results (and errors) from the channels and return them in a vector.
- Thread Safety: Ensure that the code is thread-safe and avoids data races.
Expected Behavior:
The spawn_tasks function should return a Result<Vec<String>, Vec<String>>. The Vec<String> in the Ok variant contains the results from each task, in the same order as the input tasks. The Vec<String> in the Err variant contains error messages from any tasks that panicked, also in the same order as the input tasks.
Edge Cases to Consider:
- Empty input vector: Should return an empty
Ok(Vec::<String>()). - Tasks panicking: Handle panics gracefully and report errors.
- Channel closure: Ensure channels are properly closed after use.
- Large number of tasks: Consider potential resource limitations.
Examples
Example 1:
Input: [task1: || Ok("Result 1".to_string()), task2: || Ok("Result 2".to_string())]
Output: Ok(["Result 1", "Result 2"])
Explanation: Two tasks are spawned, each returning a successful result. The main thread collects these results in the order they were provided.
Example 2:
Input: [task1: || Ok("Result 1".to_string()), task2: || panic!("Task 2 panicked")]
Output: Err(["Task 2 panicked"])
Explanation: The first task returns a successful result. The second task panics, and the spawned thread sends the panic message through the channel. The main thread collects the error message.
Example 3:
Input: []
Output: Ok([])
Explanation: An empty vector of tasks is provided. The function should return an empty vector of results.
Constraints
- The input vector can contain up to 100 tasks.
- Each task's result string should be no longer than 256 characters.
- Error messages from panics should be captured as strings.
- The solution should be reasonably efficient; avoid unnecessary allocations or blocking operations.
- Use
std::sync::mpscfor channel communication.
Notes
- Consider using
std::thread::spawnto create new threads. - The
Resulttype returned by each task isResult<String, String>. TheOkvariant contains the result string, and theErrvariant contains an error message. - Think about how to handle the lifetime of the closures passed as tasks. They need to be able to access any necessary data.
- Remember to properly close the channels to prevent deadlocks.
- The order of results/errors in the returned vector is crucial. It must match the order of the input tasks.