Repository navigation
TaskEx: parallelLimit #143
Description
Activity
- changed the title
[-]TaskShims: parallelThrottled[/-][+]TaskEx: parallelThrottled[/+]on Jan 10, 2023 - added 2 commits that reference this issue
on Jan 23, 2023 - addedfeature requestNew feature or enhancement requestNew feature or enhancement requesttopic: task-exRelated to the proposed new TaskEx library, which should get its own oss havenRelated to the proposed new TaskEx library, which should get its own oss haven
on Oct 29, 2023 github-actions commented
on Mar 10, 2026 on Mar 10, 2026 – with GitHub ActionsContributorMore actions🤖 This is an automated response from Repo Assist.
Thanks for the detailed write-up — this is a recurring pain point in F# async code.
A few observations on the proposed
Async.parallelLimitdesign:API shape
The proposed thin wrapper:
module Async = let parallelLimit maxDegreeOfParallelism computations = Async.Parallel(computations, maxDegreeOfParallelism = maxDegreeOfParallelism)
…would be a one-liner shim, but the real value is in the pipelining story. Since
Async.Parallelrequires an explicit argument name to passmaxDegreeOfParallelism, user code currently looks like:computations |> fun cs -> Async.Parallel(cs, maxDegreeOfParallelism = 8)
A curried helper fixes this cleanly.
Task-based equivalent
Given this library's trajectory toward
task {…}andIAsyncEnumerable, aTask-based parallel throttle might have broader impact. There's noTask.Parallelequivalent that takes a seq ofunit -> Task<'T>with a degree-of-parallelism parameter. ATaskSeq-aware version could look like:// consume a task seq with bounded parallelism TaskSeq.parallelThrottle : int -> TaskSeq(unit -) Task<'T>> -> IAsyncEnumerable<'T>
Though that's arguably a different feature from the
Async.Parallelwrapper being proposed here.Stack-overflow risk
The issue mentions the F# 13165 stack-overflow bug in
Async.Parallelwith throttle + early cancellation. That bug was fixed in FSharp.Core 6.0.6. Since this library currently pinsFSharp.Coreto 6.0.1 for minimum-version compatibility (#129 area), users on older FSharp.Core versions would still be exposed unless the shim adds aminVersionguard or docs note.Relationship to #141/#142
This issue is downstream of the broader
TaskExscope in #139. If #141 (properAsync.ofTask/AwaitTask) and #142 (startImmediateAsTask) land first, they'd establish the conventions (naming, module placement, cancellation token handling) thatparallelLimitshould follow.Happy to draft a minimal implementation once the naming conventions are settled — or to open a separate issue for a
Task-parallel throttle if that's considered in scope.Generated by Repo Assist · ◷
To install this agentic workflow, run
gh aw add githubnext/agentics/workflows/repo-assist.md@346204513ecfa08b81566450d7d599556807389fClosed via dotnet/fsharp#20294
Replaces #129. TaskEx top level issue: #139
Async.Parallel's optional degree of parallelism parameter was added late in the game, but is critical - dumping an arbitrary unbounded number of work items onto the threadpool is not something that should be easy and/or the default thing to do without due consideration for how that will work under stess.There are some other shortcomings, which frequently lead to various bespoke helpers proliferating:
fun computations -> Async.Parallel(computations, maxDegreeOfParallelism=dop)etc) (note this is not the case forAsync.Sequential)Async.Paralleland direct consumption within an app might be useful)Current proposed APIs (will be updated inline based on any discussion below):
NOTES:
Async<unit>tasks. Having to use|> Async.Ignore<unit[]>is ugly for that (and most people probably do|> Async.Ignore, which prevents the compiler from helping you if your computations start to return values where they previously returnedunit)Task, considering cancellation tokens (see TaskEx: Async.startImmediateAsTask #142) and unwrappingAggregateExceptions (see TaskEx: AwaitTaskCorrect / Task.toAsync / Async.ofTask #141). Providing an equivalent of this that works well withtaskexpressions should likely be prototyped alongside any permanent API for this. Example impl. Also, perhaps aTask.sequentialmight make senseThrottledin the name is pretty well established in multiple internal library suites, and in posts such as https://www.compositional-it.com/news-blog/improved-asynchronous-support-in-f-4-7