Am I doing Async and Blocking right?

I'm trying to implement a library
I want it to work with both blocking and async contexts.

Is this how I should go about doing it?

use tokio::runtime::Runtime;

//async functon
pub async fn foo() -> Output {
     bar().await
}

pub mod blocking{
    use super::*;
    
    //blocking function
    pub fn foo() -> Output{
        let mut rt = Runtime::new().unwrap();
        rt.block_on(bar())
    }
}

struct Output {
    //
}
async fn bar() -> Output {
    // the actual logic
}

I also have the option to make bar non-async. But using that from tokio runtime caused that block_on error, probably because my library uses reqwest

Or I could make separate async fn bar() and fn bar() separately but that would repeat a lot of code.

How should I approach it?

Sorry if I'm approaching this completely wrong, completely new to doing this.

I can’t comment on the code organization much, as I haven’t done enough async coding to get a good feel for it. If I needed a library for one of my non-async projects, though, I’d think twice about pulling in a (presumably) heavyweight dependency like tokio.

That's one of my concerns as well. I'm only using the "rt-core" feature of tokio which is just 1 of like over 15 of tokio's features, but still I'm concerned if I should be using tokio here.

It was the only way (to my current knowledge) to use an async fn bar() from the fn foo()

You might want to look at the code base for reqwest. It offers an async and a blocking api. I believe that the blocking api don't require an async runtime to run. I only looked at the blocking code briefly a while back but from what I remember it runs async futures on an extremely simple blocking async executor. Maybe there is a small library that can do this for you instead of implementing it yourself like reqwest has.

1 Like

I think @asafigan’s recommendation was to look at how reqwest structures its code, because it is doing the same thing you want to: providing both a sync and async api.

Oh sorry my bad.

As far as I have seen it does what I did. make a runtime and call block on with it. But I'll look into it in more detail I suppose

I haven't done this myself, but I would try Handle::try_current to see if a runtime already exists, and create a new one on error. This way you could (hopefully) avoid creating a new runtime if one already exists, avoiding the runtime inside runtime error.

I believe this is correct and it does use tokio under the hood even in the blocking API.

1 Like

that seems interesting, looking into it. I'm not sure if I can use it in this case though. If I omit tokio completely from my end and use the blocking client of reqwest, reqwest would be creating one runtime, and the user of my lib would be creating another.... thus the error. I don't think I can intervene anywhere, unless I am misunderstanding what try_current does.

If you're going to be creating/getting a runtime anyway, I don't think there's any reason to use a blocking client, is there? I guess it might be due to something not visible in this example, but I think you should be able to structure your code so the blocking client isn't needed. I'm not much of an async expert myself, unfortunately, so I might be mistaken

Well I'm making a library, I need to let the user choose either or, Like what reqwest itself is doing. I can't tell the user to not use async if he needs to.

Right now I just decided to refactor most of my code to make both separately, and reuse as much code as possible across them. I'll share the thing here after I'm done.

tokio is a mandatory dependency of reqwest though, right? Even if you only use the blocking API you're depending on and using tokio in the background. I think it's a reasonable goal to be able to leave tokio out completely if the user doesn't need it, but you won't be able to do it while using reqwest, I don't think.

2 Likes

Link

There I put it up
Right now it uses both the blocking and async reqwest clients for the blocking and async functions

Not sure if it was worth it, as you've already said the lib needs tokio anyway for reqwest. Maybe I'll change it back later.

Generally the standard solution for making a blocking version of an api is to create a runtime and use block_on. It is typically recommended to provide something like reqwest's Client type to allow reuse of the runtime object.

I think using Tokio for the blocking api is perfectly fine, as Tokio is very light-weight if you only enable the basic scheduler. Note that you should construct the runtime through the builder to make sure that you are constructing a basic scheduler even if the user depends on the threaded scheduler feature of Tokio through some other dependency.

Don't bother using Handle::try_current. That method will pretty much only succeed inside async code, and blocking inside async code is a big no-no. If the user has a runtime, they can use your async api.

1 Like

https://docs.rs/tokio/0.1.22/tokio/runtime/struct.Builder.html

So basically use that instead of Runtime::new() ? alright will give that a go then.

Well, I recommend using the Builder from Tokio v0.2, but otherwise yes.

1 Like

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.