taskSeq stops after the first await (Task.Yield/Task.Delay between yields) when the consuming project is built in Debug; Release works (TaskSeq 1.1.1, .NET SDK 10.0.400)
还没有人认领这个 Issue。
评估
- 难度
- 4/5
- 预计耗时
- 3-5 天
- 新手友好度
- 45/100
- Issue 类型
- 缺陷
- 描述清晰度
- 基本清楚
- 活跃度
- 活跃
- 技术栈
- fsharp
- 领域
- backend
调研方向
从 Program.fs 中的 taskSeq 表达式开始,比较报告中列出的 Debug 和 Release 编译器标志下生成的行为。跟踪 manualEnumeration 经过 GetAsyncEnumerator 和 MoveNextAsync 的过程,然后添加一个回归测试,覆盖 yield 之间的 awaits 以及 for 循环的形态。完成的标准是 Debug 枚举返回每个预期项,且没有异常或竞态条件。
由索引模型根据 Issue 内容生成。
描述
Summary
With FSharp.Control.TaskSeq 1.1.1, a taskSeq that awaits between yields (do! Task.Yield() or do! Task.Delay n) stops after the first await when the consuming project is compiled in the Debug configuration. The same code enumerates every item in Release. TaskSeq.toListAsync, a manual GetAsyncEnumerator / MoveNextAsync loop and an ASP.NET Core response writer consuming the IAsyncEnumerable are all affected. A taskSeq without awaits between yields is fine.
Impact: dotnet test builds Debug by default, so tests that stream through a taskSeq silently observe only the first item. That is how I ran into it (an xunit test of a chunked HTTP writer that takes an IAsyncEnumerable).
Repro
Repro.fsproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="Program.fs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FSharp.Control.TaskSeq" Version="1.1.1" />
</ItemGroup>
</Project>
Program.fs
open System.Threading.Tasks
open FSharp.Control
let awaitBetweenYields () =
taskSeq {
yield 1
do! Task.Yield()
yield 2
do! Task.Delay 5
yield 3
}
let delayOnly () =
taskSeq {
yield 1
do! Task.Delay 5
yield 2
}
let forLoopWithDelay () =
taskSeq {
for i in 1..3 do
do! Task.Delay 1
yield i
}
let noAwaits () =
taskSeq {
yield 1
yield 2
yield 3
}
let manualEnumeration (source: taskSeq<int>) =
task {
let e = source.GetAsyncEnumerator()
let results = ResizeArray()
while! e.MoveNextAsync() do
results.Add e.Current
return List.ofSeq results
}
printfn "awaitBetweenYields toListAsync : %A" (awaitBetweenYields() |> TaskSeq.toListAsync).Result
printfn "awaitBetweenYields manual loop : %A" (manualEnumeration(awaitBetweenYields())).Result
printfn "delayOnly toListAsync : %A" (delayOnly() |> TaskSeq.toListAsync).Result
printfn "forLoopWithDelay toListAsync : %A" (forLoopWithDelay() |> TaskSeq.toListAsync).Result
printfn "noAwaits toListAsync : %A" (noAwaits() |> TaskSeq.toListAsync).Result
Output:
$ dotnet run -c Debug
awaitBetweenYields toListAsync : [1]
awaitBetweenYields manual loop : [1]
delayOnly toListAsync : [1]
forLoopWithDelay toListAsync : []
noAwaits toListAsync : [1; 2; 3]
$ dotnet run -c Release
awaitBetweenYields toListAsync : [1; 2; 3]
awaitBetweenYields manual loop : [1; 2; 3]
delayOnly toListAsync : [1; 2]
forLoopWithDelay toListAsync : [1; 2; 3]
noAwaits toListAsync : [1; 2; 3]
No exception is thrown; MoveNextAsync simply returns false after the first await.
Environment
- Windows 11 (10.0.26200), x64
- .NET SDK 10.0.400,
net10.0, FSharp.Core 10.1.400 (assembly 10.1.0.0, file version 10.104.26.38015) - Also reproduces with .NET SDK 11.0.100-preview.6,
net11.0, FSharp.Core 11.0.0.0 (file version 11.1.126.36018) - FSharp.Control.TaskSeq 1.1.1
What I could narrow down
- The
awaitBetweenYieldsresult is deterministic (3 of 3 runs each: Debug[1], Release[1; 2; 3]). Theforshape flips between[]and[1]across Debug runs, so a race looks likely. - It is not the runtime or the dependencies:
bin/Debugandbin/Releasecontain byte-identicalFSharp.Core.dllandFSharp.Control.TaskSeq.dll, thedeps.jsonfiles are identical and theruntimeconfig.jsonfiles differ only byMetadataUpdater.IsSupported. Copying the Release-compiledRepro.dllinto the Debug output folder works; copying the Debug-compiledRepro.dllinto the Release output folder fails. So it depends on how the consuming assembly (the one containing thetaskSeq { }block) is compiled. - The full
fscargument diff between the two configurations is only: Debug--define:DEBUG --optimize- --tailcalls- -gversus Release--define:RELEASE --optimize+. - Building Release with the Debug flag set (
dotnet run -c Release -p:Optimize=false -p:Tailcalls=false -p:DebugSymbols=true -p:DebugType=portable -p:DefineConstants=DEBUG) reproduces the bug. Building Debug with the Release flag set (dotnet run -c Debug -p:Optimize=true -p:Tailcalls=true -p:DebugSymbols=false -p:DebugType=none -p:DefineConstants=RELEASE) makes it work. - Flipping a single flag was not enough for me: Debug with
-p:Optimize=true -p:Tailcalls=true(still-g) fails, Release with-p:Optimize=false -p:Tailcalls=false(no-g) works, and Debug with-p:DebugSymbols=false -p:DebugType=none(still--optimize-) fails. - The failing binaries carry
DebuggableAttribute(IsJITOptimizerDisabled = true); the working ones do not.
Happy to provide anything else that helps.
- 主要语言
- F#
- 星标
- 111
- 派生
- 13
- 平均合并
- 7 小时 59 分钟
- 30 天内合并 PR
- 3
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
fsprojects/FSharp.Control.TaskSeq 的其他 Issue
-
agentic-workflows automation repo-assist
难度 2/5 1-3 小时 新手友好度 70/100
-
agentic-workflows automation repo-assist
难度 1/5 1-3 小时 新手友好度 35/100
fsprojects/FSharp.Control.TaskSeq#476 · 1 条评论 ·
-
automation enhancement repo-assist
难度 5/5 一周以上 新手友好度 10/100
-
enhancement needs triage
难度 5/5 一周以上 新手友好度 45/100
-
automation enhancement repo-assist
难度 5/5 一周以上 新手友好度 15/100
查看 fsprojects/FSharp.Control.TaskSeq 的全部 Issue
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 75/100
-
难度 2/5 1-3 小时 新手友好度 80/100
microsoft/magentic-ui#588 ·
-
bug
难度 2/5 1-3 小时 新手友好度 70/100
alexgorbatchev/simple-ptt#3 · 1 条评论 ·
-
难度 2/5 1-3 小时 新手友好度 75/100
-
难度 2/5 1-3 小时 新手友好度 75/100
CorrelAid/formtransform#44 ·