Skip to content

πŸ“° Weekly Research Report β€” FSharp.Control.AsyncSeq & Async Streaming Ecosystem (Feb 2026)Β #215

@github-actions

Description

@github-actions

πŸ—žοΈ Weekly Research Report

FSharp.Control.AsyncSeq & the Async Streaming Ecosystem β€” February 2026


πŸ“¦ Repository Pulse

The repo has been active with a flurry of agentic CI automation added this week: an AI co-maintainer workflow (running every 4 hours) and a daily performance-improver agent were both merged. The latest stable release is v3.3.1, which delivered:

  • A new mapAsyncUnorderedParallel function for improved parallel throughput
  • Significant performance wins on iterAsync, iteriAsync, collect, mapAsync, and unfoldAsync
  • Fixed a memory leak in append (issue Memory leak when unfoldingΒ #35)
  • BenchmarkDotNet infrastructure to track performance systematically

The recently merged PR #204 renamed bufferByCount β†’ chunkBySize, aligning with .NET naming conventions (Seq.chunkBySize).

Open PR to watch: #214 β€” Expose ofAsyncEnum/toAsyncEnum on netstandard2.0. The root cause was a mismatch between the .fsi signature file (gating behind NETSTANDARD2_1) and the .fs implementation (which already supports NETSTANDARD || NET). A one-line .fsi fix could resolve the long-standing issue #173.


πŸ”₯ Hot Issues & Community Feedback

Issue Topic Status
#159 Deprecate FSharp.Control.IAsyncEnumerable in favor of BCL type Open β€” significant
#205 tryFirst should return the fastest result (like Async.Choice) Open
#152 Deadlocks in Blazor WASM environment Open
#155 mapAsyncParallel semantics and alternatives Open
#156 Request: chunkBy β€” group adjacent elements sharing the same key Open
#122 iterAsyncParallel may fail to cancel Open

The IAsyncEnumerable naming collision (issue #159) is particularly subtle: because this library defines its own FSharp.Control.IAsyncEnumerable<'T> with different semantics (using Async<'T option> for MoveNext rather than ValueTask(bool)), users who open both FSharp.Control.AsyncSeq and FSharp.Control.TaskSeq namespaces can get confusing type errors. The long-term answer is likely a major-version deprecation.

The tryFirst / Async.Choice idea (#205) is genuinely interesting: when elements are produced by parallel async computations, returning the first available rather than the first in order better matches the parallel mental model. The analogy to Async.Choice is apt.


🌍 Ecosystem & Competitive Landscape

FSharp.Control.TaskSeq (v0.4.0, March 2024)

The main sibling/competitor in the F# async-sequence space. TaskSeq implements IAsyncEnumerable<'T> from the .NET BCL using resumable state machines (the same engine as task { }), with ValueTask for performance. The choosing guide summarises the trade-offs:

AsyncSeq TaskSeq
Async model F# Async(T) .NET ValueTask(T)
BCL IAsyncEnumerable Custom (incompatible) βœ… Native
.NET target netstandard2.0+ netstandard2.1+
Fable support βœ… Yes ❌ No
await in CE let! with Async(T) let! with Task(T) or Async(T)

TaskSeq v0.4.0 added 25+ new functions (forall, skip, take, insertAt, min/max, etc.) and moved from module TaskSeq to type TaskSeq (static class) to allow overloads β€” a breaking but cleaner design.

Opportunity for AsyncSeq: Many of these missing functions (e.g. skip, take, insertAt, updateAt) are also absent from AsyncSeq. A function-parity project could significantly improve the library's ergonomics.

Rx.NET & AsyncRx.NET

The dotnet/reactive repository now maintains four libraries:

  • System.Reactive (Rx.NET): Push-based IObservable(T), mature and widely used.
  • AsyncRx.NET (experimental): IAsyncObservable(T) β€” think Rx.NET but with async/await-friendly subscriptions. Still in preview.
  • System.Linq.Async: Standard LINQ operators for IAsyncEnumerable(T).

The free ["Introduction to Rx.NET 2nd Edition"]((introtorx.com/redacted) eBook was updated for Rx.NET v6.0 and .NET 8 β€” a valuable free resource for anyone thinking about reactive patterns.

Conceptual note: AsyncSeq sits in an interesting middle ground between IEnumerable(T) (pull, sync) and IObservable(T) (push, async). The library already has ofObservableBuffered and toObservable, which is a nice bridge.


πŸš€ .NET & F# Platform News

  • .NET 11 Preview 1 was released in February 2026. .NET 10 (GA, 2025) is the current LTS.
  • F# on .NET 10 (Preview 7+):
    • and! support in TaskBuilder β€” enables parallel awaiting of independent tasks inside task { }. This is directly relevant to async computation patterns.
    • "Make unused bindings an error" β€” improves code quality enforcement.
    • Improvements to computation expression error reporting (better ranges for let!, use!, match!, return!).
  • Fable 4.29.0 (stable) / 5.0-alpha: Fable 5 is expanding Python support aggressively β€” Python 3.10+ match statements, async def for task { } expressions, and ABC base classes. AsyncSeq already has Fable support; monitoring Fable 5 compatibility will be important as the Python target matures.

πŸ’‘ New Ideas & Opportunities

1. AI/ML Pipeline Integration

With the explosion of LLM streaming APIs (SSE, chunked HTTP), AsyncSeq is a natural fit for consuming token streams from models like GPT or Claude. An AsyncSeq.ofHttpResponseStream or integration example with System.Net.Http.HttpClient response streams could attract a new user base. The library's pull-based model maps well to reading SSE event streams or paginated API results.

2. Back-Pressure and Rate Limiting

Issue #174 questions whether iterAsyncParallelThrottled belongs in this library. A more principled answer might be a small set of back-pressure combinators β€” throttle, debounce, buffer with overflow strategies β€” similar to what Akka Streams or Reactor provide. These patterns are increasingly relevant for event-driven microservices.

3. chunkBy β€” Grouping Adjacent Elements

Issue #156 requests a chunkBy : ('T -> 'Key) -> AsyncSeq<'T> -> AsyncSeq<'Key * AsyncSeq<'T>> style function for run-length grouping. This is distinct from chunkBySize β€” it groups adjacent elements with the same key (useful for log parsing, event stream partitioning). It's a low-hanging fruit with clear use cases.

4. Cancellation Improvements

Issue #122 notes that iterAsyncParallel and iterAsyncParallelThrottled may fail to cancel. With AI agents running long parallel pipelines, robust cancellation is more important than ever. A systematic audit of cancellation token propagation across all parallel combinators would be valuable.

5. Bridging AsyncSeq ↔ IAsyncEnumerable

As the BCL IAsyncEnumerable(T) becomes more prevalent (used by EF Core, Azure SDK, ML.NET, etc.), smooth interop is crucial. The ofAsyncEnum/toAsyncEnum PR (#214) is a step forward. A longer-term goal could be making AsyncSeq<'T> implement BCL IAsyncEnumerable<'T> directly (in a netstandard2.1+ target), while keeping the custom interface for netstandard2.0 and Fable.


🎭 Enjoyable Anecdote

The library's asyncSeq { } computation expression was one of F#'s early demonstrations that computation expressions could elegantly model lazy async pull sequences β€” years before .NET's IAsyncEnumerable existed. When .NET finally standardised async enumeration in 2019, F# developers had already been doing it ergonomically for years. The community's response was essentially: "Yes, we know, we've had this since 2012."

This head-start has a small ironic downside: the library's custom IAsyncEnumerable type predates and differs from the BCL type of the same name, leading to the naming collision tracked in issue #159. A classic case of being too early.


πŸ“š Related Research

  • "Asynchronous Sequences and Coroutines in F#" β€” Tomas Petricek's original design (2011-2013), which laid the conceptual groundwork for the library.
  • Reactive Streams specification (reactive-streams.org): a vendor-neutral standard for async stream back-pressure; comparing AsyncSeq semantics to this spec could reveal gaps and alignment opportunities.
  • "Project Loom" (Java): Virtual threads + structured concurrency in Java 21+ are solving similar problems in the JVM world. The StructuredTaskScope API (fork/join with cancellation) is conceptually close to what iterAsyncParallel does.
  • Kotlin Flow: Coroutine-based cold streams (similar mental model to AsyncSeq). Kotlin Flow's operators like flatMapMerge, buffer, and conflate could inspire future AsyncSeq additions.

AI-generated content by Agentic Weekly Researcher (Ruby), may contain mistakes.

Generated by Agentic Weekly Researcher (Ruby)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions