How can I access methods in self
from an async task?
You'll run into lifetime troubles when passing self
as a reference to Self::a
and moving that reference to a tokio task, because tasks need to be 'static
. If Self::a
can't take ownership of self
you could use shared ownership instead and pass self
wrapped in an Arc
. Example.
thanks jofas, is this a common pattern? Or say is it idiomatic? Are there any other potential pitfalls with this?
This is a common pattern. If you want to immutably share memory between tasks, Arc
is the way to go. The only pitfall I can think of with shared ownership would be reference cycles. Reference cycles don't get dropped, leading to leaked memory.
Just ran into another issue with this, because now I can't call the same methods multiple times anymore. I also wrapped the Struct inside Arc in the constructor, because I don't want expect users of my library to do this themselves. Is it possible? How can one avoid moving Self?
To prevent moving, pass a reference:
pub async fn a(self: &Arc<Self>) {
// ^
oh right, that's obvious now. =)
In that particular case a is not async function, so you can make it sync and call block_on
, as it allowes to use non-static references. Make sure ti not call it in any async function - it means main should become sync, and Tokio runtime created manually
And I will note that it is not conventional to have something as object oriented as your examples mixed with async. Those two paradigms generally play poorly together
I would be glad to write Rust in a non OOP way, but rather data oriented. But this example stems from a prototype to create a renewable websocket client. It should hold connections that renew itself every 24 hours before the server disconnects them. So I ended up with some async tasks that run in a loop to automatically create new connections.
Now these tasks, needed some state from Self and call a method in Self self.resubscribe
, which caused the above issues.
The methods have a reason to be async in the project I'm working on, this is just a simplified example.
Keep in mind that code is async even without spawn
. Futures can run concurrently if you just .await
them. In 99% of situations you shouldn't need spawn
. You should directly .await
async functions as much as possible.
If you need to await many futures, you still don't need spawn
. There's futures::join!
, futures::join_all
, Stream::from_iter().collect()
and a few other options.
spawn()
is only needed if:
- you want the spawned task to continue running on its own, even after the caller that created the task has been cancelled and will never see the result.
- the future in the task is large and complex enough that it needs to be run on another thread in a multi-threaded runtime. This usually only needs to be run at some very high level, e.g. once per request in a web server.
- the call stack of the async calls is already very deep, usually with many
Box<dyn Future>
callers, to the point that the cost of polling starts showing quadratic behavior. This is a pathological case that doesn't happen in most programs.
Otherwise, spawn
is just slowing down async code. A direct .await
is usually much cheaper and optimizes better than awaiting a spawned task. Spawning tasks unnecessarily will slow your code down in addition to creating very strict demands about thread-safety.
hello, these are some valuable tips for me. The reason I'm using an extra spawn, is because it runs a loop that would otherwise block the caller.
Well, it really depends on what's in the loop. Checkout rumqttc - Rust architecture. It's a mqtt client, very similar to websocket. You can look at other mqtt implementations. no_std
usually have better architecture, as there usually are no allocations involved and all resources are very explicitly passed back and forth between library and your code, or some platform specific stuff, so it can't usually use any spawn
at all, because it cannot assume tokio
or even Linux.