How to .await tokio::task::JoinHandle which is wrapped in Arc<T>

Is it even possible?
I have the following code example:

fn call() -> JoinHandle<()> {
    println!("Something");
    task::spawn(dos())
}

async fn dos() {
    for i in 0..10 {
        time::sleep(Duration::from_millis(2000)).await;
        println!("in task {}", i);
    }
}


async fn run() {
    let jh = call();
    let jh_shared = Arc::new(jh);
    let jh_cloned = Arc::clone(&jh_shared);
    
    task::spawn(async move {
        time::sleep(Duration::from_secs(5)).await;
        jh_cloned.abort();
    }).await;

    let r = Arc::downgrade(&jh_shared);
    let raw = r.into_raw();
    unsafe { (*raw).await };   // <-- This here is invalid. Can I await wrapped JoinHandle in a different way?
}

So from my newbie code you can se that I want to be able to abort the running task in a separate task, and I need to have a handle on Future in the main thread too so I can await it of course, that is why I use Arc<T>.
Is there a way that could be done based on my code example or in a completely different way?
If it cannot be done the way I tried it I would like to know why?

You could try a channel like oneshot Rust Playground

This approach probably won’t work though in case an abort doesn’t happen, as we’re now waiting for the abort, not for the task to finish.

1 Like

I recommend that you abort it in some other way so that you do not have to share the JoinHandle.

2 Likes

Thanks! I missed that. A fix here: Rust Playground

tokio::select! {
    _ = t => println!("JoinHandle aborted"),
    recv = recv => {
        match recv {
            Ok(handle) => {
                match handle.await {
                    Ok(_) => println!("tasks done"),
                    Err(e) => {
                        if e.is_cancelled() {
                            println!("cancelled")
                        } else {
                            println!("awaited")
                        }
                    },
                };
            }
            Err(e) => println!("the sender dropped: {e}"),
        }
    }
};
Something
in task 0
in task 1
in task 2
in task 3
in task 4
signal to abort
tasks done
test in 50 millis done

Something
in task 0
in task 1
in task 2
in task 3
in task 4
signal to abort
JoinHandle aborted
test in 100 millis done

I'm new to Rust so I was out of ideas what else to do.

That is really nice advanced solution. I like that you wrapped data in JoinHandleAbort struct.
Combination with tokio::select! macro is powerful.
Thank you for this solution and to all others too.

Wait... I think select! is not proper here. We don't have to await the abort task... So this is the final solution I give: Rust Playground

If I'm wrong, feel free to correct me. I'm not an async Rust expert/veteran.


Edit: following the advice from @ndusart and using timeout: Rust Playground

1 Like

Why spawning a new task if it is only to be able to abort it if it didn't end after a timeout ? It would be simpler to just select! between the long running future and sleep (or just wrap the future with tokio::time::timeout).

Still, if the task is required, it is simpler to wrap the JoinHandle in a tokio::time:: Timeout and await it like if it was running in same task but just abort and await the handle when it returns (whether timed out or not).

2 Likes

Yes I get it, I think. tokio::select! pseudo randomly chooses which Future to poll. So even when receiver gets signal to abort there is no guarantee when it will be polled.

Still I like your solution, now modified.
tokio::time::timeout is simpler and more straight forward but in this case I need to be able to abort task (or not), in dynamic time, not statically set it when to abort up front.

Thank you again and to all for smart suggestions.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.