If you really want you could even convert the graph (fully or partially) to a runtime representation.
The core of the proposals are not about specific classes or functions other than some default algorithm implementations.
Instead they are mostly about defining concepts and protocols so that independent async domains can coexist and iteroperate in the same application.
This is similar to the STL where the most important part are the various iterator concepts and the algorithm interfaces, not the actual algo implementations and the containers.
I would find that to be great, but I don't understand how.
In e.g.
while(i) {
co_await sch.schedule(std::chrono::milliseconds(100));
co_await w.write(--i);
}
how can you get a meaningful "graph" object out of that,
e.g.[wait 100] -> [write 9] -> [wait 100] -> [write 8] -> ...
without actually "running" the code first to go through the co_await, which are the only way to acquire the information of what is to run ?
for instance, let's say that I want to compute "statically" (not in the "at compile time" sense but in the "after knowing the inputs to the program and before actually running the scheduler") how many [write] calls there are to schedule (assuming no cycles / infinite loops).
I don't think coroutines provide (yet) enough introspection to break them down in the same way.
Why it would make sense?
In my experience, dedicated threads only makes sense in very specific use cases. Like dealing with APIs which have thread affinity, or when you want OS to prioritize stuff. Generally, throwing tasks into a shared thread pool is the way to go.
well, yes, those are things that happen fairly often. e.g. there's a lot of work on optimizing code for various arm big.LITTLE things, where you have some algorithm that you want to make sure runs on the "big" cores.
I think for most use cases runtime scheduling is better than compile-time one. Because other processes sharing the hardware, and because tasks take unpredictable time, depends on almost everything including ambient temperature. Not to mention compilers don’t generally know count of cores, outside of a few niches like embedded, iOS and consoles games.
I think that it would be useful to consider the existing (very very very large) body of research on dataflow PL and APIs; e.g. check this paper from 1982 which talks about dataflow, coroutines, for handling file operations:
- https://scholarship.claremont.edu/cgi/viewcontent.cgi?articl...
More recent papers in the field:
- https://hal.archives-ouvertes.fr/hal-02865894/document
- https://library.oapen.org/bitstream/handle/20.500.12657/3772...
and older general overviews of the field:
- http://www.cs.ucf.edu/~dcm/Teaching/COT4810-Spring2011/Liter...
- http://210.47.10.86:8032/200009-3/65492.pdf#page=141
My main concern with this design, even if the code looks quite neat, is that the actual dataflow graph is not reified, but exists "invisibly" as part of the program source through the way co_await calls are sequenced.
This means that it is not possible to do as an user, a static analysis and scheduling of the tasks : it relies on the compiler to perform this. But, this problem is in my experience quite often very use-case-specific and ridden with informations compilers do not possess. Not having the way tasks are interdependent reified in an actual user-manipulable "graph" object means that users won't have the possibility, to, say, perform a topological sort or flow propagation on their async graph in order to extract information on it that could be necessary to perform a domain-specific optimization of said graph (for instance if we know that a set of tasks are dependent on each other in ways that aren't visible on the source code, but are known to the programmers, it would make sense to schedule them on a queue in their own thread).