Search code examples
rustrust-tokio

When should you use tokio::join!() over tokio::spawn()?


Let's say I want to download two web pages concurrently with Tokio...

Either I could implement this with tokio::spawn():

async fn v1() {
    let t1 = tokio::spawn(reqwest::get("https://example.com"));
    let t2 = tokio::spawn(reqwest::get("https://example.org"));
    let (r1, r2) = (t1.await.unwrap(), t2.await.unwrap());
    println!("example.com = {}", r1.unwrap().status());
    println!("example.org = {}", r2.unwrap().status());
}

Or I could implement this with tokio::join!():

async fn v2() {
    let t1 = reqwest::get("https://example.com");
    let t2 = reqwest::get("https://example.org");
    let (r1, r2) = tokio::join!(t1, t2);
    println!("example.com = {}", r1.unwrap().status());
    println!("example.org = {}", r2.unwrap().status());
}

In both cases, the two requests are happening concurrently. However, in the second case, the two requests are running in the same task and therefore on the same thread.

So, my questions are:

  • Is there an advantage to tokio::join!() over tokio::spawn()?
  • If so, in which scenarios? (it doesn't have to do anything with downloading web pages)

I'm guessing there's a very small overhead to spawning a new task, but is that it?


Solution

  • I would typically look at this from the other angle; why would I use tokio::spawn over tokio::join? Spawning a new task has more constraints than joining two futures, the 'static requirement can be very annoying and as such is not my go-to choice.

    In addition to the cost of spawning the task, that I would guess is fairly marginal, there is also the cost of signaling the original task when its done. That I would also guess is marginal but you'd have to measure them in your environment and async workloads to see if they actually have an impact or not.

    But you're right, the biggest boon to using two tasks is that they have the opportunity to work in parallel, not just concurrently. But on the other hand, async is most suited to I/O-bound workloads where there is lots of waiting and, depending on your workload, is probably unlikely that this lack of parallelism would have much impact.

    All in all, tokio::join is a nicer and more flexible to use and I doubt the technical difference would make an impact on performance. But as always: measure!