In principle yes, though it becomes annoying when your use-case evolves or you have to miss out on otherwise obvious optimization opportunities because they'd seriously complicate your program.
E.g., Your program is mostly sequential but in one step, you'd be able to do a bunch of requests in parallel.
I think paradigms like async/await are a step ahead to give you the best of both worlds here: You can write your programs as if your requests block, but it still uses async IO behind the scenes - and you can drop the pretense of blocking when it makes sense at any time.
Are you thinking of “green”/M:N threading (as found e.g. in Go)?
Async/await (as found e.g. in Python) is precisely what hinders the style you describe: If your brand new I/O routine is “async colored” to take advantage of non-blocking syscalls, you can’t easily call it from your regular “sync colored” code without “spilling the color” all around, i.e. considerable refactoring.
My context is that most of my recent years work was in high performance systems that handle thousands of streams concurrently and only have a limited amount of cores and a rather limited amount of processing for each io request have so the context switching cost becomes a high percentage compared to actual work done.
But I'm specifically talking about those that need sophisticated strategies to deal with multiplexed I/O (which is a topic of this article you're commenting on).
I've never understood this choice myself. It has always seemed unhelpful given that the read/write returns an error anyhow. Why is it a helpful default?
Basically it's a very sane robustness choice and one of the great ideas of classic unix. It's just surprising the first time you stumble over it.
The actual runtime difference for me was 40 mins -> 10 mins
"The way it works is simple, it uses blocking IO, but each blocking call is made in its own thread. Now depending on the implementation, it either takes a callback, or uses a polling model, like returning a Future."
I think I know what you're saying, but in practice this is exactly backwards.
Usually you're doing I/O for some practical reason and want to do simple, well-defined, typically sequential processing on the results. Which is to say blocking is your friend and you shouldn't be mucking with parallel I/O paradigms if you can at all avoid it.