How to run multiple async fn without await?

I'm not trying to get any result value from it, but just benefit from the async syntax.

async fn hi(){

  some.await?
  task.await?

  //  catch some error if needed, but don't return anything
}

hi() // run this without await
hi() // run this without await
hi() // run this without await
hi() // run this without await
1 Like

Async functions must be run inside some runtime. The probably easiest way to go is to add the dependency on tokio and then annotate main function with #[tokio::main] - this way runtime will be created for you by the attribute, and main can be treated as async itself, including having awaits inside.

2 Likes

i'm using async_std already, but i'm forced to add await or poll to it.

Yes, that is exactly the idea. In Rust, Futures are lazy. They need to be explicitly awaited to make them run (to completion) or polled to make them run (perhaps not to completion).

2 Likes

and how do you poll them?

A Future has a method poll, which quite literally does what it's name suggests. However, directly calling poll requires you to jump through some hoops, because of the types of the parameters of poll (notice, the Pin on the self and the Context argument). The async book has a chapter showing how to do it.

You can use futures::join! to run multiple futures concurrently.

async fn four_hi() {
  futures::join!(hi(), hi(), hi(), hi());
}
2 Likes

If you want to poll the futures concurrently, there is futures::join! (as @ndusart mentioned), tokio::join!, or, in nightly Rust std::future::join!. I think these all do the same, but not entirely sure.

Sometimes you want to cancel futures once one future has completed. For that, you could use futures::select! or tokio::select!.

When you want enable truly parallel execution (i.e. on different CPUs), you will need to use a spawn function of the runtime you're using, e.g. tokio::spawn. You can then (edit: if/when you want to retrieve the result) pass the resulting future to await, join!, or select!, whichever suits you best.

Spawn is almost certainly what you want here. It isn't guaranteed to run on another thread, but it's likely to. Join or select are for when you need to process the results, but you can use the JoinHandle returned from spawn for that too, with a small inefficiency.

but I don't want to use the await syntax. It has to run the async function like it was any other normal function.

In javascript I can call any async function and it retuns a promise. It is up to me if I use await or not.
There must be something similar.

Futures in Rust work a lot different than promises in JavaScript. There are similarities, but they are still very different.

In Rust you must await a future if you want it to do anything, or you must give it to some other funciton that will poll the future.

Polling futures manually is not like calling .then() on a promise in JavaScript, it is a much more complicated process that is done by the async executor, and is like what the JavaScript engine does for you with JavaScript promises.

If you simply want to get a value out of a Rust future, you pretty much use .await or a combiner like join, or a spawner like spawn.


Is there a reason you don't want to use .await?

I think polling is what I'm looking for, but It's hard to know how to start.
There are just some functions that have some async syntax inside, but I don't really need to await them.
Rather just some error hadling inside that async, but the caller doesn't really have to worry about the result.

Ah, OK, I think I get it. In that case you can let async_std do the polling for you:

use async_std::task::block_on;
async fn hi(){

  some.await?
  task.await?

  //  catch some error if needed, but don't return anything
}

block_on(hi()) // run this without await
block_on(hi()) // run this without await
block_on(hi()) // run this without await
block_on(hi()) // run this without await

That will do the polling for you, while blocking the current thread until it is done, every time you call block_on().

It should not block, this still runs them one at a time.

in js it would be

async function hi(){
 
await x;
}

hi()
hi()
hi()
// ^ all promises got initialized at the same time; yet no value is returned

If you want to run multiple futures concurrently, without blocking on them, that is what your async runtime's spawn() function is for. Something has to poll the futures to cause them to make progress, and spawn() is telling the runtime to do that for you without further interaction.

    task::spawn(hi());
    task::spawn(hi());
    task::spawn(hi());

tried this, but nothing happening.


    task::spawn(init(1)).await;
    task::spawn(init(2)).await;
    task::spawn(init(3)).await;
    task::spawn(init(4)).await;
    task::spawn(init(5)).await;
    task::spawn(init(6)).await;
    task::spawn(init(7)).await;

this will still block, no difference.

the result is sequential in order:

>>> Ping { server_time: 1671563071614 }, 1
>>> Ping { server_time: 1671563072115 }, 2
>>> Ping { server_time: 1671563072470 }, 3
>>> Ping { server_time: 1671563072809 }, 4
>>> Ping { server_time: 1671563073205 }, 5
>>> Ping { server_time: 1671563073544 }, 6
>>> Ping { server_time: 1671563073934 }, 7

I don't want it to be sequential.

Does your program exit immediately after spawning the tasks?

If your main thread exits, then the threads that are doing the pinging will actually be killed before they can finish, so nothing would happen.

omg, genius. yes that was the issue =D

1 Like

I tried this

    task::spawn(init(1));
    task::spawn(init(2));
    task::spawn(init(3));
    task::spawn(init(4));
    task::spawn(init(5));
    task::spawn(init(6));
    task::spawn(init(7));

    task::spawn(init(100)).await;
    task::spawn(init(100)).await;
    task::spawn(init(100)).await;
    task::spawn(init(100)).await;
    task::spawn(init(100)).await;
    task::spawn(init(100)).await;

and now because of the awaits below. the others above run to completion!
Omg i thought rust async is a nightmare.

>>> Ping { server_time: 1671563256986 }, 5
>>> Ping { server_time: 1671563256989 }, 4
>>> Ping { server_time: 1671563256983 }, 1
>>> Ping { server_time: 1671563256989 }, 6
>>> Ping { server_time: 1671563256988 }, 7
>>> Ping { server_time: 1671563256987 }, 3
>>> Ping { server_time: 1671563256989 }, 100
>>> Ping { server_time: 1671563256984 }, 2
>>> Ping { server_time: 1671563257498 }, 100
>>> Ping { server_time: 1671563257859 }, 100
>>> Ping { server_time: 1671563258242 }, 100
>>> Ping { server_time: 1671563258600 }, 100
>>> Ping { server_time: 1671563259665 }, 100
    task::spawn(init(1)).await;
    task::spawn(init(2)).await;
    task::spawn(init(3)).await;
    task::spawn(init(4)).await;
    task::spawn(init(5)).await;
    task::spawn(init(6)).await;
    task::spawn(init(7)).await;

Instead of this, you can put the join handles in a collection, and then await them. That way, all the spawning happens before you wait, and you can wait whenever you want.

let handles: Vec<_> = (1..=7).map(|i| task::spawn(init(i))).collect();

// do anything else you want between these — the tasks will be running

// Wait for all the tasks to finish
for handle in handles {
    handle.await;
}

Or, you can run them concurrently without actually spawning, with join_all:

use futures::future::join_all;

join_all((1..=7).map(|i| task::spawn(init(i)))).await;

This waits until they all finish, but lets them all run simultaneously rather than in sequence. This is appropriate for getting the work done quickly but waiting for it; spawn is appropriate for when you don't care when it finishes and the program will continue running.

There are lots of options here, but the right one depends on exactly what you want to happen.

(The reason your spawns didn't finish without any awaits is analogous to how in JS, all your code stops running if the user closes the web page; everything stops when main() returns in a Rust program.)

3 Likes