I read the tokio documentation and I wonder what is the best approach for encapsulating costly synchronous I/O in a future.
With the reactor framework, we get the advantage of a green threading model: a few OS threads handle a lot of concurrent tasks through an executor.
The future model of tokio is demand driven, which means the future itself will poll its internal state to provide informations about its completion; allowing backpressure and cancellation capabilities. As far as I understand, the polling phase of the future must be non-blocking to work well.
The I/O I want to encapsulate can be seen as a long atomic and costly operation. Ideally, an independent task would perform the I/O and the associated future would poll the I/O thread for the completion status.
The two only options I see are:
- Include the blocking I/O in the
poll
function of the future. - spawn an OS thread to perform the I/O and use the future mechanism to poll its state, as shown in the documentation
As I understand it, neither solution is optimal and don't get the full advantage of the green-threading model (first is not advised in documentation and second don't pass through the executor provided by reactor framework). Is there another solution?
Yes, this is what Tokio recommends and what crates like futures-cpupool and tokio-threadpool were created for. Note that this is not restricted to I/O, but is valid for any long-running synchronous task!
In this case, you schedule a closure to run in the pool. The pool itself performs the work to check to see if the blocking closure is completed yet and fulfills the
Future
trait.Note that this is not an efficient way of sleeping, it's just a placeholder for some blocking operation. If you actually need to sleep, use something like futures-timer or tokio-timer.
You can see that the total time is only 3 seconds:
Likewise, you can use tokio-threadpool for the same result:
That's correct - because you don't have something that is asynchronous! You are trying to combine two different methodologies and there has to be an ugly bit somewhere to translate between them.
I'm not sure what you mean here. There's only one executor in the example above; the one implicitly created by
wait
. The thread pool has some internal logic that checks to see if a thread is done, but that should only be triggered when the user's executorpoll
s it.