Skip to content

TaskEx: parallelLimit #143

Description

@bartelink

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:

  • pipelining is painful, necessitating an explicit argument name (e.g. fun computations -> Async.Parallel(computations, maxDegreeOfParallelism=dop) etc) (note this is not the case for Async.Sequential)
  • before v FSharp.Core v 6.0.6, [there was a stack overflow bug that can tear down the process](// Async.Parallel stack overflow on cancellation of ~2000 uncompleted computations dotnet/fsharp#13165) if >1200 items are started with a throttle and cancellation is triggered quickly (so having a layer between Async.Parallel and direct consumption within an app might be useful)

Current proposed APIs (will be updated inline based on any discussion below):

module Async =
    let parallelLimit maxDegreeOfParallelism computations =
        Async.Parallel(computations, maxDegreeOfParallelism = maxDegreeOfParallelism)

NOTES:

Activity

  1. changed the title [-]TaskShims: parallelThrottled[/-] [+]TaskEx: parallelThrottled[/+] on Jan 10, 2023
  2. changed the title [-]TaskEx: parallelThrottled[/-] [+]TaskEx: parallelLimit[/+] on Jan 18, 2023
  3. added
    feature requestNew feature or enhancement request
    topic: task-exRelated to the proposed new TaskEx library, which should get its own oss haven
    on Oct 29, 2023
  4. added this to the vFuture milestone on Mar 18, 2024
  5. github-actions commented on Mar 10, 2026

    @github-actions
    Contributor

    🤖 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.parallelLimit design:

    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.Parallel requires an explicit argument name to pass maxDegreeOfParallelism, 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 {…} and IAsyncEnumerable, a Task-based parallel throttle might have broader impact. There's no Task.Parallel equivalent that takes a seq of unit -> Task<'T> with a degree-of-parallelism parameter. A TaskSeq-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.Parallel wrapper being proposed here.

    Stack-overflow risk

    The issue mentions the F# 13165 stack-overflow bug in Async.Parallel with throttle + early cancellation. That bug was fixed in FSharp.Core 6.0.6. Since this library currently pins FSharp.Core to 6.0.1 for minimum-version compatibility (#129 area), users on older FSharp.Core versions would still be exposed unless the shim adds a minVersion guard or docs note.

    Relationship to #141/#142

    This issue is downstream of the broader TaskEx scope in #139. If #141 (proper Async.ofTask / AwaitTask) and #142 (startImmediateAsTask) land first, they'd establish the conventions (naming, module placement, cancellation token handling) that parallelLimit should 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@346204513ecfa08b81566450d7d599556807389f
    
  6. bartelink commented on Sep 9, 2026

    @bartelink
    MemberAuthor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    feature requestNew feature or enhancement requesttopic: task-exRelated to the proposed new TaskEx library, which should get its own oss haven

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions