Comments (7)
Hi,
Thanks so much for reporting this issue!
My first observation is that this issue does not present itself if I build in release configuration, or in debug configuration with "generate tail calls" enabled under project properties -> build. So that's one workaround.
I will take a look and see if I can fix it without tail calls.
from taskbuilder.fs.
This should be resolved by b804b6f .
Let me know if it works for you so I can close this.
from taskbuilder.fs.
Wow, great turnaround. That does indeed fix the issue.
One more question: is this intended to support the recursive CE style as well? If so, there's a similar problem there. For example, if I again rewrite the readFile
function from above ...
let readFile path = task {
let buffer = Array.zeroCreate bufferSize
use file = File.OpenRead(path)
let rec loop n = task {
if n < 10000 then
let! countRead = file.ReadAsync(buffer, 0, buffer.Length)
return! loop (n + 1)
else
return ()
}
return! loop 0
}
... this also gives me a stack overflow (in release mode, BTW).
from taskbuilder.fs.
Another good catch -- I'll take a look at this one tomorrow.
from taskbuilder.fs.
After mulling this over for a couple days, I don't think it can be done. At least, not with TaskBuilder returning a Task<'a>
. It could probably be made to work if TaskBuilder returned its internal Step
monad and let you convert that to a task separately, but I think keeping it simple for translation from C# -> F# is too important to do that. So I have chosen to document the behavior in the README instead of change it. I am keeping the refactoring I did while working on this though, since it does at least improve the situation a little.
My reasoning for why it can't work:
If you have some async work followed by a tail call into another task, and you don't know anything about the tail position task other than that it is some Task<'a>
, then what you have must be a Task<Task<'a>>
.
This is now what the async state machines in TaskBuilder.fs use. The built-in way to convert a Task<Task<'a>>
to a Task<'a>
is task.Unwrap()
, which again, is what TaskBuilder.fs uses now. However, using this at best grows memory usage proportional to the number of iterations (on .NET), and at worst still stack overflows (on Mono). This is because Unwrap
, despite its name, does return a wrapper Task to represent its computation and so you end up with a wrapper around a wrapper around a wrapper ... etc. When the innermost wrapper completes, it all unwinds (and may or may not stack overflow in the process), but till then you are at least eating a bunch of heap memory for all those wrappers.
from taskbuilder.fs.
Interesting. The Task builder in FSharpx.Extras supports this usage, but according to some casual benchmarks of mine, that builder is significantly slower than this one (and generates a ton more garbage). That suggests to me that they have accepted some of the negative performance trade-offs you allude to in your last paragraph.
I can live with a more imperative coding style to get closer to C#-level performance, so I appreciate your contribution. I do hope that one day somebody figures out how to support the idiomatic style with good performance, though.
from taskbuilder.fs.
Unfortunately, I think the FSharpx.Extras implementation suffers even worse. Because they always use Task.ContinueWith(...).Unwrap()
for Bind
instead of creating a state machine, there is no way to write a correct long-running loop using that builder.
With FSharpx, both of the below code samples will eventually fail with an OutOfMemoryException
. You can watch the memory usage grow into the gigabytes over the course of a minute or two. That memory is being taken up by nested tasks that legitimately cannot be collected (wrappers around wrappers around wrappers).
recursive:
let tailRec () =
task {
do! Task.FromResult(())
return! tailRec ()
}
iterative:
let whileLoop() =
task {
while true do
do! Task.FromResult(())
}
With TaskBuilder.fs, the recursive version fails with a StackOverflowException
(although if you change Task.FromResult
to Task.Yield
you'll get an OutOfMemoryException
just like the one in FSharpx).
However, in the current TaskBuilder.fs, the iterative version has very stable memory usage. It does allocate as it runs, but the allocations can be collected so quickly that you basically don't see memory usage change after the first few seconds of runtime.
I should probably report the while loop behavior as a bug to FSharpx. It might be awkward for them to fix while keeping the same public API, since their builder lets you specify TaskContinuationOptions
, which only make sense when using Task.ContinueWith
for bind...
from taskbuilder.fs.
Related Issues (20)
- Please elaborate a bit on "Tail calls are not optimized" HOT 2
- Async overload bind HOT 8
- Issue with plain Task and do! HOT 1
- TaskBuilder update breaks my tasks HOT 30
- TaskResultBuilder ? HOT 3
- TaskBuilder 2 release HOT 5
- Could not load type 'BindI' from assembly 'TaskBuilder.fs HOT 3
- Caching bool tasks HOT 2
- add support of specifying TaskScheduler HOT 4
- The type 'Threading.Tasks.Task' is not compatible with the type 'Threading.Tasks.Task<'a>' HOT 7
- Using ValueTask instead of Task HOT 1
- Intended way to call async and ignore result HOT 2
- Possible incorporation into FSHarp.Core HOT 9
- Easy way to get result synchronously? HOT 5
- Support for netstandard2.0 HOT 11
- Code is not sufficiently generic
- Question on ContextInsensitive tasks HOT 41
- Non-generic Tasks HOT 6
- What is the purpose of this if it exists in FSharpx.Extras ? HOT 3
- Dispose is not called in task CE HOT 3
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from taskbuilder.fs.