Hacker News new | past | comments | ask | show | jobs | submit login

"the syntax for kicking off dozens of IO requests and collecting all the results is trivial."

Please show me this trivial code, assuming I want to process 12 requests in parallel at most (and always processing 12 at the same time until there's non left to process).






Something like this, depending on how you want your input and output to be supplied.

    public async IAsyncEnumerable<HttpResponseMessage> SendTwelveRequestsAtATimeAsync(IAsyncEnumerable<HttpRequestMessage> requests)
    {
        HttpClient client = new();
        List<HttpRequestMessage> requestsBatch = [];

        await foreach (var req in requests)
        {
            if (requestsBatch.Count < 12)
            {
                requestsBatch.Add(req);
            }
            else
            {
                foreach (var res in await Task.WhenAll(requestsBatch.Select(client.SendAsync)))
                {
                    yield return res;
                }

                requestsBatch = [];
            }
        }
    }

I would not call this trivial. :)

I'd argue that it is a lot easier than doing it with threads!

Promises are a primitive, and async and await keywords in JavaScript is just syntactic sugar around promises (it is sugar around similar constructs in other languages). A promise being just a long running task that will return a result eventually. Being able to grab a promise as an object and pass it around is super useful at times, and it is something I end up using a lot in my JS/TS code.

Because it is a language primitive that is also expressed in the type system, more complex systems can safely be built up around it, in the same way that it is easier to build safe(r) complex systems up around threads in languages that have threads as a primitive. (Rust being a great example here of bringing threads into the language, Java being another early example, though their early attempts were not perfect since we've learned a lot since 1995!)

Async/await and promises are a great example of a technology that makes doing easy stuff easy, and makes hard stuff possible.

tl;dr people need to stop complaining that other multitasking/threading paradigm looks different than their preferred one, each has plusses and minuses and one isn't "better" than others, they just serve different purposes.


In C# you'd use a Channel for this, which is pretty easy to use. But of course that is built on top of async/await, with those alone it is far from trivial to implement your specific case.

The rust version of this has an inherent complexity that is very similar to your requirement. Processing at most X requests at once is the job of a Semaphore, until there's none left to process is the job of join_set.

    let mut tasks = JoinSet::new();
    let semaphore = Arc::new(Semaphore::new(12));
    for i in 0..100 {
        let semaphore = semaphore.clone();
        tasks.spawn(async move {
            let _permit = semaphore.acquire().await.unwrap();
            sleep(Duration::from_secs(1)).await;
            i * i // simulate some result
        });
    }
    
    while let Some(result) = tasks.join_next_with_id().await {
        let (id, result) = result?;
        println!("Task {id} result: {result}");
    }
That allows you to collect the results as they come in and align it to the various ids. If you only care about the final result and nothing why things are processing,

    let results = tasks.join_all();
The task is written inline there, but could as easily be an async function

    for i in 0..100 {
        tasks.spawn(process_request(i, semaphore.clone()));
    }



Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: