Skip to content

Support withCancellation #167

Description

@xperiandri

When you call .WithCancellation(cancellationToken) on IAsyncEnumerable<'T> you get System.Runtime.CompilerServices. ConfiguredCancelableAsyncEnumerable<'T> which is not currently supported.

So that this code does not work

    static member ToFlatListAsync<'Source>(source: IQueryable<'Source>, [<Optional>] cancellationToken: CancellationToken) = task {
        let builder = ImmutableArray.CreateBuilder<'Source>()
        do!
            source.AsAsyncEnumerable().WithCancellation(cancellationToken)
            |> TaskSeq.iterAsync (fun x -> builder.Add(x))

        return builder.ToImmutable();
    }

Activity

  1. abelbraaksma commented on Aug 3, 2023

    @abelbraaksma
    Member

    This is actually very interesting. Reading up on the design decisions for WithCancellation, as Don Syme and myself were about creating our own solution, but it is, obviously, better to use dotnet's own methods, if they are applicable.

    Just checked, this method is part of NetStandard 2.1, so I can actually add it. Another method in those extensions that is very interesting is ToBlockingEnumerable. This is a .NET 7+ extension, but I might take its code for the blocking operations I have, to ensure similar behavior.

  2. abelbraaksma commented on Aug 3, 2023

    @abelbraaksma
    Member

    Related discussion: #133

  3. abelbraaksma commented on Oct 29, 2023

    @abelbraaksma
    Member

    This turns out to be much harder to support than I thought. But one of the soon-to-be-made changes is going to be a move to static methods, so we can support adding a cancellation token in each call immediately, removing the need for calling WithCancellation, but that means threading an overload through the whole system.

    To support this, we either need to overload every single method such that it accepts that type, or we have to go SRTP (and/or with an SRTP overload).

    Instead, I may consider something like ofWithCancellation (ugly method name, I know), which would itself return an IAsyncEnumerable but with a collected cancellation token. This is, however, not ideal, as the token should be given to GetEnumerator(token). Hmm, I'll think a bit more about this.

    The reason this is non-trivial is because ConfiguredCancelableAsyncEnumerable<T> is a struct and does not implement any useful interface. I'm also a little confused as to why MS chose to internally effectively call ConfigureAwait(true), which, from library code, is considered bad practice in general (and can lead to deadlocks).

  4. xperiandri commented on Oct 29, 2023

    @xperiandri
    Author

    Maybe raise an issues on .NET repo about the use of ConfiguredCancelableAsyncEnumerable<T>?

  5. abelbraaksma commented on Nov 4, 2023

    @abelbraaksma
    Member

    That likely wouldn't help. It is a struct for a reason. While it keeps a reference to the IAsyncEnumerable<_> internally, it makes the overhead of making your enumerable cancelable really small. There's no need for a newobj IL instruction.

  6. added this to the vNext milestone on Mar 18, 2024
  7. github-actions commented on Mar 8, 2026

    @github-actions
  8. dsyme commented on Mar 14, 2026

    @dsyme
    Contributor

    /repo-assist Add this withCancellation in a PR

  9. changed the title [-]Support `ConfiguredCancelableAsyncEnumerable<'T>`[/-] [+]Support withCancellation[/+] on Mar 14, 2026
  10. added a commit that references this issue on Mar 14, 2026
    597d11d
  11. github-actions commented on Mar 14, 2026

    @github-actions
    Contributor

    Pull request created: #339

    Generated by Repo Assist

  12. github-actions commented on Mar 14, 2026

    @github-actions
    Contributor

    🤖 Repo Assist here — following @dsyme's request, I've implemented TaskSeq.withCancellation and opened a draft PR.

    The implementation is a lightweight wrapper that passes the supplied CancellationToken directly to GetAsyncEnumerator, mirroring .WithCancellation() semantics without requiring any interaction with ConfiguredCancelableAsyncEnumerable(T):

    // Usage — replaces .WithCancellation(cancellationToken):
    source.AsAsyncEnumerable()
    |> TaskSeq.withCancellation cancellationToken
    |> TaskSeq.iterAsync (fun x -> builder.Add(x))

    The PR includes:

    • TaskSeq.withCancellation : CancellationToken -> TaskSeq<'T> -> TaskSeq<'T> in the public API
    • 10 tests covering null-check, token threading, pre/mid-iteration cancellation, and combinator composition
    • All 4711 existing tests still pass

    Generated by Repo Assist for issue #167 · ◷

    To install this agentic workflow, run

    gh aw add githubnext/agentics/workflows/repo-assist.md@346204513ecfa08b81566450d7d599556807389f
    
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: surface areaAdds functions to the public surface area

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions