Repository navigation
TaskEx: ignore #140
Description
Activity
- addedfeature requestNew feature or enhancement requestNew feature or enhancement requesttopic: task-exRelated to the proposed new TaskEx library, which should get its own oss havenRelated to the proposed new TaskEx library, which should get its own oss haven
on Oct 29, 2023 github-actions commented
on Mar 18, 2026 on Mar 18, 2026 – with GitHub Actions · Hidden as outdatedshow commentMore actionsGood news — the
ignorefunctions this issue proposes are already implemented in the library! They live inUtils.fsand are exported viaUtils.fsi
I guess these nudges are slowly shuffling me toward getting
AwaitTaskCorrectintoFSharp.Core@T-Gro (or anyone else) Do you have a stance on getting these sorts of helpers into core? (ideally the overall picture of #139 is taken into account in any answer)
The suggestion is approved in principle, fsharp/fslang-suggestions#840 (comment) .
I do not like the name and usage, but I do agree with what it does internally.
Let me refresh the discussion and wait for proposals.@T-Gro thanks for the response
I'm well aware of the
AwaitTaskCorrectsuggestion and it's state - it's my pet hate but it's not like I couldn't fix it; I just haven't gotten around to itI was asking about your/a stance on things like providing a
Task.ignoreand ancillary functions as mapped out in #139.Summarizing the relevant points:
- this lib and many others have these sorts of helpers
- apps should not really be having to take dependencies on libs like this (esp if they don't use
IAsyncEnumerable) to cover basic needs (or doing stuff likelet! _ = taskThing in ()that should really be something likedo! taskThing |> Task.ignore). - Obviously we need to be sane about the amount of one-liners admitted into FSharp.Core
For some the answer is FsToolkit and/or IcedTasks.
My workaround hack is (as in the case in this library) to sprinkle them around in Internal modules like this: https://github.com/jet/propulsion/blob/master/src/Propulsion/Internal.fs#L160
The bottom line is that the helpers that live in FSharp.Control.TaskSeq nuget are pretty close to what I'd want/recommend, but they dilute the focus of this library, and don't solve the problem that we have N copies of these throughout the ecosystem that don't provide any useful variation - they just mean newcomers spend ages working out where all the little incidental helpers come from and/or why they exist.
Do you see a potential for any of these helpers to go live in FSharp.Core ? Does having a FSharp.TaskEx make more sense from your perspective (I'm not convinced personally as such a thing has huge potential to become a junk drawer unless it has a very strong maintainer group, along with general diamond dependency structure concerns)?
The current state of play is bad news for day to day real world F# consumption in writing/maintaining apps unless you:
- pull in Equinox+Propulsion, TaskSeq and/or FsToolkit (FSharpPlus and many others probably provide similar - these are just ones I've had lots of day to day experience with)
- are an experienced/senior dev that's bootstrapped on lots of nuances of Async/Task interop and tradeoffs that can reliably work out the one liner shims appropriate/necessary in the context of a given application suite.
Let me collect some data on the redefinitions and usage (under multiple names, possibly), to have a better understanding of what is needed by most.
Those can live in FSharp.Core, we should just better understand the real usage for each (i.e. not hide less significant API additons under the same umbrella).
I will transitively process all issues linked from your comment and see what I find at least in public GH repos.
Reacted by Ruben BartelinkProposed F# Task/Async API Additions — Verified GitHub Usage
Date: 2026-03-18
Sources: TaskSeq#139, #140, #141, #142, #143, fslang-suggestions#840, jet/propulsion Internal.fs, IcedTasks AsyncEx.fs, FsToolkit Async.fs+Task.fs
Methodology: 7 subagents opened actual source code on GitHub, verified declarations vs usages, deduplicated forks, classified each hitResults
# Proposed API Signature Hits Decl Repos Use Repos TaskSeq IcedTasks FsToolkit F#+ Source 1 Async.map('T→'U) → Async<'T> → Async<'U>720 25 25 ✅ uses ✅ ✅ Utils 2 Async.parallelLimit/Throttledint → Async<'T> seq → Async<'T[]>113 17 26 ❌ ❌ inline ❌ #143 3 Async.bind('T→Async<'U>) → Async<'T> → Async<'U>432 13 24 ✅ ❌ ✅ ❌ Utils 4 Task.ignoreTask<'T> → Task/Task<unit>140 8 22 ✅ ❌ ✅ ✅† #140 5 Task.map('T→'U) → Task<'T> → Task<'U>330 11 17 ✅ ❌ ✅ ✅ Utils 6 Task.fromResult/singleton'T → Task<'T>148 7 21 ✅ ❌ ✅ ❌ Utils 7 Async.ofTask(AwaitTaskCorrect)Task<'T> → Async<'T>90 10 14 ✅‡ ✅ ❌ ✅ #141/#840 8 Task.bind('T→Task<'U>) → Task<'T> → Task<'U>83 10 11 ✅ ❌ ✅ ✅ Utils 9 Async.singleton'T → Async<'T>88 5 16 ❌ ❌ ✅ ❌ FsToolkit 10 Task.ofAsync/Async.toTaskAsync<'T> → Task<'T>11 8 2 ✅ ❌ ❌ ❌ Utils 11 Task.ofUnitTask/ofUnitTask → Task<unit>15 4 5 ✅ ❌ ✅ ❌ Utils 12 Task.catchTask<'T> → Task<Choice<'T,exn>>14 3 5 ❌ ❌ ✅ ❌ FsToolkit 13 ValueTask.ignoreValueTask<'T> → ValueTask6 2 3 ✅ ❌ ❌ ✅† #140 14 Task.toAsyncTask<'T> → Async<'T>6 2 2 ✅ ❌ ❌ ❌ #141 15 Async.call(CT→Task<'T>) → Async<'T>10 2 2 ❌ ❌ ❌ ❌ Propulsion 16 Async.ofUnitTaskTask → Async<unit>4 1 2 ✅ ✅ ❌ ❌ #141 17 Async.startImmediateAsTaskCT → Async<'T> → Task<'T>17 2 1 ❌ ❌ ❌ ❌ #142 18 Async.ignore(lowercase)Async<'T> → Async<unit>0 0 0 ✅ ❌ ❌ ❌ #140 Legend
Symbol Meaning Hits GitHub code search file matches across ALL of GitHub (forks excluded) Decl Repos Unique non-fork repos that define the helper (verified by reading source) Use Repos Unique non-fork repos that call it (consuming, not defining) ✅‡ TaskSeq wraps Async.AwaitTaskwithout AggregateException fix✅† Present but different semantics (F#+: Task.ignore=Task→Task<unit>;ValueTask.ignore=ValueTask→ValueTask<unit>)inline Uses Async.Parallel(…, maxDegreeOfParallelism=N)inline, no named helperuses IcedTasks calls Async.mapinternally but does not declare itLibrary Coverage Summary
Library Coverage Notes TaskSeq 15/18 Most comprehensive. Async.ofTaskwrapsAsync.AwaitTaskwithout AggregateException fix. Missing:parallelLimit,Async.singleton,Task.catchFsToolkit 9/18 Dominant NuGet source for downstream usage. Missing: AwaitTaskCorrect, conversions, parallelLimit FSharpPlus 6/18 Has AwaitTaskCorrect (as Async.Await).Task.ignore/ValueTask.ignorehave inverted signaturesIcedTasks 2/18 Focused on CEs ( asyncEx,cancellableTask), not utility module functionsNotable Findings
Async.map(25 declaring repos) — the F# compiler itself (dotnet/fsharp) declares and uses this inlib.fs. The single most duplicated helper in the ecosystem.Async.parallelLimit(17 declaring repos) — two implementation patterns observed: simpleAsync.Parallelwrapper vsSemaphoreSlim-based throttling. Names vary:parallelLimit,parallelThrottled,ParallelWithThrottle.AwaitTaskCorrect(10 declaring repos) — approved-in-principle since 2022 but unimplemented. The canonical fssnip is still copy-pasted. 6 additional repos wrapAsync.AwaitTaskunder theAsync.ofTaskname without the AggregateException fix.Task.ignore— two semantic variants in the wild:Task<'T> → Task<unit>(FsToolkit, most common) vsTask<'T> → Task(simple upcast, less common).Async.ignore(lowercase) has zero independent implementations outside TaskSeq. Nobody wrapsAsync.Ignorein lowercase. Drop from proposal.Async.startImmediateAsTaskpipeable wrapper — only 2 repos:jet/propulsionandionide/FsAutoComplete.- No single library covers all 18 APIs. TaskSeq comes closest (15/18) but its
Async.ofTasklacks the AggregateException fix.
Top Declaring Repos
Repo APIs declared fsprojects/FSharp.Control.TaskSeq Task.ignore,Task.map,Task.bind,Task.fromResult,Task.ofAsync,Task.toAsync,Task.ofUnitTask,ValueTask.ignore,Async.ofTask‡,Async.ofUnitTask,Async.toTask,Async.ignore,Async.map,Async.bindjet/propulsion Task.ignore,Async.ofTask✅,Async.ofUnitTask,Async.call,Async.startImmediateAsTask,Async.executeAsTask,Async.parallelLimit,Task.parallelLimit,Task.catch,Task.ofUnitTaskdemystifyfp/FsToolkit.ErrorHandling Task.map,Task.bind,Task.ignore,Task.singleton,Task.ofUnit,Task.catch,Async.map,Async.bind,Async.singletonfsprojects/FSharpPlus Task.map,Task.bind,Task.ignore†,Async.map,Async.ofTask(asAsync.Await),ValueTask.ignore†Hm; general thoughts re the above:
- (unsurprisingly) plenty hallucinations in the summary, but (also not unsurprisingly) it's not useless
- I'd exclude
.map. Just because it's common doesn't make it a good thing to have in the box IMO Task.ofAsyncand some related ones are questionable as it makes it too easy not to flow cancellation etc- the
ofUnit* specialization to coverTaskvsTask<'T>feels icky so should be deferred/handled separately Async.ignore(with that casing) is not something to pooh-pooh - the fact that prod code usesAsync.Ignore,Async.Catch,Async.StartAsTask/StartImmediateAsTask(with and without threading cancellation, with or without actually meaningStartImmediateAsTaskbut usingStartAsTaskas it sounded like the right thing) was understandable 10 years ago but the myriad inconsistencies and pitfalls are why all these helpers have spawned and/or continue to proliferate in various ways with useless variation clouding things.
Before this can be converted into something for broader discussion, some high level stances would help:
- a stance on whether we err on the side of keeping warts in the interest of avoiding confusing duplication (e.g. add a
Task.ignorebut trust people will discover/understand thatAsync.Ignoreis cased that way for reasons and that's just the way it is) - a stance on whether we put things with pitfalls into the mix or not (Task.ofAsync without some way to surface that you probably want to flow CT in most cases)
- a stance on long excluded things like Async.map (I think that's one to keep off the table; its definitely highly improbable that it's not a deliberate omission that's had eons of airtime)
The elephant in the room re all of the above is that fleshing out an
Async.Awaitthat does not egregiously introduceAggregateExceptions probably will answer a lot of the above indirectly as a side-effect of getting it done.Reacted by Tomas Grosup- Avoid confusing duplication. If current state is really bad, I would rather do
Obsolete+better thing, but has to be justified. Mere casing does not meet the bar I am afraid. - Correctness by default, make pitfalls explicit (e.g. via
CancellationToken.None). Humans as well as agents will try to take shortcuts for sure, but at least it makes them visible. - If something is difficult to get right/has many tradeoffs, but people still reimplement their own versions - we are not winning anything by excluding it. If it is needed and used a lot more then the others, we should ship a sensible default for it IMO.
Reacted by Ruben Bartelink- Avoid confusing duplication. If current state is really bad, I would rather do
Thanks.
- Fair enough NOTE1
- Great. For avoidance of doubt, that rules out
Task.ofAsync - Sounds like a taking an
Async.map+Task.mapproposal around the houses could flesh out a lot of the pros/cons, leaving filling out the rest of the matrix a more constrained ask. NOTE2
NOTE1 but I HATE the Async.Ignore wart; thing is I can add an
inlineone-liner in my personal helpers. Its very easy for these warts to drive people to the conclusion that the answer is to just useTaskfor all the things and treatAsyncas some unfortunate historical path. i.e. if I can doTask.ignoreandValueTask.ignorebut Async is not in alignment.)NOTE2 But: If it's in the box, it becomes blessed. It will inevitably lead to clear 3 line
asyncortaskblocks becoming more terse piped equivalents, for better or for worse (it's clearly subjective)I personally think we're better off without it, but definitely not a hill I want to die in, especially if having some agreement means we have consistent Task/Async/ValueTask helper names going forward, and a pattern for FsToolkit, IcedTasks and/or other computation expressions to follow.
RE NOTE1: But wouldn't you then hate also other PascalCase'd members eventually, in places where a Module.function is expected by you?
1 more option in our toolbox I forgot about - keep the "worse" variant, do not mark it obsolete (so prevent warnings), but do[<EditorBrowsable(Never)>]so that it isn't offered for newly written code.1 more option in our toolbox I forgot about - keep the "worse" variant, do not mark it obsolete (so prevent warnings), but do
[<EditorBrowsable(Never)>]so that it isn't offered for newly written codeGood point - I think that might be a potential answer for
Async.AwaitsupplantingAsync.AwaitTask.Whether it has a role in dealing with
Async.IgnorevsTask.ignorevsTask.Ignoreis a longer discussion...RE NOTE1: But wouldn't you then hate also other PascalCase'd members eventually, in places where a Module.function is expected by you?
AIUI The pattern in this lib and in general is to have camelCase names (sometimes actually implemented as
members to facilitate overloading.Task.ignoreclearly follows that trend. If we were to be consistent with how Async lays it out, that should clearly be namedTask.Ignore.I was already referring to there being more instances by calling out
Async.Catchnot aligning with a proposedTask.catch.My main concern is with 'othering' Async (even though the ecosystem gaining consistent and predictable Task, TaskSeq, ValueTask functions across the board is a big win). So yes, in my world one would add
Async.ignore,.catchand more, and conceal the PascalCase ones from auto completion (especially ifTask,TaskSeqand N other things only have them as camelCase named functions). There's plenty code out there with direct consumption of Async.Ignore and I can appreciate that the original naming and alignment with the FDG signatures and policies are valid for many obvious reasons.I guess this does raise the point that there are multiple concerns at play in the
Asynccase that don't matter as much for the others:- consistent FDG compliant API suitable for use from C# and other .NET languages
- large installed base that can't and/or shouldn't be forced to switch
I guess if
Asyncremains othered for the moment, potentially shims can live in anAsyncExproviding anAsync.* API surface that makes it align withValueTask.* vsTask.*. Or you can flip that and say thatTask.Ignoreis the correct name/API design for consistency.
All of which brings us back to the start of the circle:
- do low value and/or arguably duplicative shims and sugar really belong in FSharp.Core, or would a dedicated lib in fsproject make more sense?
- who are the realistic maintainers of such an API set? if we can answer that, we'll know pretty quick whether a
Task.mapand anAsync.mapare one of the first functions that get implemented or not!
My initial assumption, loosely held, was that doing
Await, thenIgnore, thenStartImmediateAsTask(forcing CT to be supplied) and incrementally was the best approach.Your calling out of my wanting an
Async.ignorefor reasons is correct. It seems that high level direction/planning as to whether one liners and duplication can be all but ruled in or out on some basis needs to be the first step though - doesn't feel like something that one can do by voting and/or mapping out what the current patterns happen to be - there need to be high level principles (and to be fair there's significant clarity already, but duplicating functions, havingmapor not, are two examples where people are probably seeing/hearing what they want to at present!).20 remaining items
cc @gusty I'm thinking you've been around the houses on many of these things
If you have the time would appreciate some comments here re fsharp/fslang-suggestions#1466 and fsharp/fslang-suggestions#1467 - especially if there's stuff you really don't like, consider superfluous, or a minor tweak would improve it. Ditto naming improvements before we spread it wider (maybe via @sergey-tihon ?) would be more than welcome.
Is there anyone else that should be CC'd on this for a first round of getting rid of obvious cruft or extremely commonly helpful/needed small functions? @eiriktsarpalis
I removed that comment 👍 , thanks for fixing it.
Once you are ready with final touches on those two suggestions, we might invite people via Discord - this is for sure the most active F# social and people will come with opinions :).
re: startAsync vs runAsync -
I would pick startAsync as well (it's taking a computation and starting it into a hot task)@T-Gro Thanks
@T-Gro I don't have any further things I want to change in either suggestion at this point, but just want to finalize re startAsync/runAsync as for me your comments are inconclusive re the following:
type Async with // https://github.com/fsharp/fslang-suggestions/issues/1042 // NOTE name is so it appears in completion list near Async.RunSynchronously // Still open to suggestions to intuitively convey that this is RunSynchronouslyCorrect though - see #1042 member _.RunSynchronouslyImmediate(computation: Async<'T>): unit = // runs on this thread, without implicit hop to thread pool, so has a correct stack trace // NOTE name has Task in it to a) allude to Task.Run b) align with [Value]Task.runAsync member _.RunTask(createTask: CancellationToken -> Task): Async<unit> = ... ... and further overloads of RunTask ... module Task = // NOTE name aligns with Async.runTask, forces flowing of cancellation let runAsync (computation: Async<'T>): CancellationToken -> Task<'T> = // Async.StartImmediateAsTask... // TOCONSIDER more natural for use with piping - as per runAsync but with the arguments flipped // member _.Action1(arg, ct): Task<'T> = // async { do! x arg; return y } // |> Task.startAsync ct // member _.Action2(arg, ct): Task<'T> = Task.startAsync ct <| async { // do! x arg // return y } // member _.Action3(arg, ct): Task<'T> = // Task.startAsync ct <| async { // do! x arg // return y } //let startAsync (ct: CancellationToken) (computation: Async<'T>): Task<'T> = runAsync computation ct
IME it's very clear from lots of usage that
startAsyncis the more useful signature (in terms of arg order, even if the CT is logically the last part that should be being supplied from a partial application perspective).The questions to be decided are:
- is the fact the arg order is slightly illogical a concern (probably moot if nobody is going to use a technically more logical one)
- should both be present (
Task.runAsyncis the logical inverse ofAsync.runTask) - if only one is present, which one, and what name
- assuming its one function and the name is based off
start, should it beTask.start,Task.startImmediate,Task.startAsyncImmediateor something else given there's anAsync.RunSynchronouslyImmediatebeing added at the same time ?
It'd be great to have your thoughts on this so the first cut isnt leaving lots of stuff open (its not a problem if people come with other opinions; it's more about having something concrete so any improvement/alteration suggestions can be equally concrete)
IME unless you have IcedTasks/CancellableTask/async2 in your stack, async can still make sense for quite a few layers as you get cancellation token propagation without it being in your face, but ASP.NET and/or a C# top layer can speak Tasks and explicit cancellation token wiring?
What's in the box is important or people without the full array of tools mapped out in their heads will say "hm, well in C# and ASP.NET I pass a CancellationToken everywhere and end up just using bald task { and/or punting on correct cancellation token propagation like the average C# impl?
I'm not arguing it wouldn't be useful, there are plenty of times you have an outside
CancellationTokenand anAsync<_>and you want to fuse the two. ASP.NET is just the time that shows up regularly with this scenario. Other scenarios like in fsac also have this problem because a cancellation can come from an LSP client.I'm just saying that I don't have this scenario often anymore as I tend to use IcedTasks when possible.
Thanks @bartelink I added my thoughts to the discussion.
Regarding the replacements of
Async.StartImmediateAsTaskisn't it worth a separate issue for that (and revisiting the default cancellation token stuff), as you suggested long time ago?Other scenarios like in fsac also have this problem because a cancellation can come from an LSP client.
@TheAngryByrd For the needs of the fsac usage above, assuming what I think T-Gro is asking for, AIUI, this could be written as:
UnusedOpens.getUnusedOpens (tyRes.GetCheckResults, getSourceLine) |> Task.startAsync progress.CancellationToken |> Async.Await // assuming asyncEx does not Bind to `Task<'T>`
Of course I may be entirely missing the point ;)
I'm really just trying to bottom out on whether the Async.withCancellation suggestion can be covered or 90% covered by some combination of smaller helpers - i.e. if there is a real need an impl involving
TaskCompletionSourcegymnastics and it's remotely common, putting it in the box is on the table. But if we're providing aTask.startAsyncthat has a non-optionalCancellationTokenand the only awkwardness is whether anAsync.Awaitis required based on the specific bindings available at the call site then I'd be seeking to close that suggestion with a workaround of "this can be done succinctly viaTask.startAsync ct |> Async.Await"Hi @gusty and thanks for responding.
Thanks so much for the detailed response on 1466 - will respond over there regarding those.
I'm a bit more confused re your other points so apologies in advance for the wall of text below ;)
Regarding the replacements of Async.StartImmediateAsTask isn't it worth a separate issue for that (and revisiting the default cancellation token stuff)
I was aware of the discussion you linked (I've traversed all these tickets including all discussions in depth in recent times). In that case I was suggesting that there should be a separate tracking item wrt stuff that brings the
Async.DefaultCancellationTokeninto play. That's varioustype Asyncmembers that have a?cancellationTokenargument. The suggestions here are specifically about functions onmodule Asyncand/ormodule Task, and each of these have a non-optionalCancellationToken(the caller is forced to explicitly useCancellationToken.NoneorAsync.DefaultCancellationTokenor something else as appropriate)Within this context, fsharp/fslang-suggestions#1467 is intended to cover extensions to
type Async,module Asyncandmodule Taskwrt starting Asyncs, viz:Async.RunSynchronouslyImmediate(has a specific ticket Add Async.RunImmediate fsharp/fslang-suggestions#1042 but the names should align - ifImmediateis the term for 'no thread pool / SynchronizationContext induced hops')Async.RunTask(has a specific ticket AddAsync.AwaitTaskoverloads which helps with CancellationToken passing, and a new warning fsharp/fslang-suggestions#1284)Task.startAsync- one liner that forces a CT and callsAsync.StartImmediateAsTask
You are correct that there is no specific ticket re
[Value]Task.startAsync, but I think the questions in #140 (comment) can likely be resolved here before we start a long discussionIn other words, I'd love to have an outline agreement on the full set of signatures presented in 1467 before we consider to mint a Task.startAsync ticket
Similarly the parallelLimit/parallelDoLimit - if we can rule doing something like that in or out at this point, then a separate ticket can be done for completeness.
Regarding the replacements of
Async.StartImmediateAsTaskI'm not proposing to replace/change anything on
type Asyncas such (and I'm trying to keep things tractable and concrete by limiting myself to adding things to the library vs talking about adding warnings and/or having changes as such) . I did consider going down that road but as detailed in Appendix A of fsharp/fslang-suggestions#1466 IMO:- Async.AwaitTask cant/shouldn't be made BrowserVisible.Never as there's way too much code that's likely dependent on specifics of its quirks
- Async.Await needs to be on the type for overloading/discovery reasons
- I'm specifically suggesting the xmldoc for Async.Await and Async.AwaitTask will be doing most of the lifting (it's not inconceivable that something later suggests migrating off Async.AwaitTask)
- Async.Catch and Async.catch need to coexist even if ideally people switch to a catch, with a
Resultresult (which aligns withTask.catch - Async.RunSynchronously, Start, StartAsTask, StartWithContinuations, StartWithContinuationsUsingDispatchInfo, StartImmediateAsTask, StartImmediate all have a
?cancellationTokenthat defaults toAsync.DefaultCancellationToken, which is hard to discover and can be a source of leaks in that each resulting invocation subscribes to the default CT
So, what are you specifically suggesting we should make a ticket for? How many tickets? Are you suggesting they should be considered for the F# 11 timeframe?
Thanks for the clarification, I agree on having them in the same ticket.
I'm really just trying to bottom out on whether the Async.withCancellation suggestion can be covered or 90% covered by some combination of smaller helper
It probably can be handled by small helpers. Might be worth considering how this affects Fable. They don't support
taskand would need to shim this as transition fromasync -> task -> async. So having an "async native" approach might be more beneficial there.So having an "async native" approach might be more beneficial there.
I'm not feeling sure we have proven there's a common enough pattern/need.
It seems to me we have a variety of ways of starting tasks that admit a CT directly:- Async.RunSynchronously, Start, StartAsTask, StartWithContinuations, StartWithContinuationsUsingDispatchInfo, StartImmediateAsTask, StartImmediate
While layering in cancellation below the top level might make sense in some cases, it feels like that belongs in with StartChild (which has a timeout option and involves LinkedCancellationTokenSource etc).
In other words
a) I don't feel we have enough of a handle on it to include it in this apiset so won't be attempting to address the need in 1466/7 for F# 11 timeframe
b) I'm not convinced the ticket as it stands is helping matters even in the longer term - I suspect most people that think they want it can achieve their goals via the existing starting functions [and not having to descend into complex TaskCompletionSource juggling]Reacted by Jimmy Byrd@T-Gro sorry for the bombardment of atting but wanted to close out on #140 (comment)
I'm guessing that removing
Task.runAsyncand having a singlestartAsyncwith the CT arg first is your preference, but the question of includingImmediatein the name to:
a) convey the semantics
b) allow other such helpers to sit alongside either in the box or via augmentations from libraries or app preludesstill needs answering from my perspective?
I'm also wondering whether
Async.RunTaskshould then more logically be calledAsync.RunTaskImmediateto convey that it will be using StartImmediateAsTask and not doing a thread pool or SynchronizationContext hop ?cc @TheAngryByrd I'm guessing you might have thoughts on the naming here too
cc @gusty you may have less interest in this one but if you have any thoughts ...
@gusty also I expected you might object toemptyon similar grounds tosingleton(= async.Zero / Task.FromResult(()) / ValueTask(())) ? (I'm considering removing it in favor ofresult ()givenTaskbecomesTask<unit>across the board)Will update 1466 and 1467 with proceedings of this and fsharp/fslang-suggestions#1466 (comment) and pop a link into the discord and the slack when I'm done with the editing later and/or tomorrow am
I'm also wondering whether Async.RunTask should then more logically be called Async.RunTaskImmediate to convey that it will be using StartImmediateAsTask and not doing a thread pool or SynchronizationContext hop ?
Definitely yes.
RunTaskalone is a bit vague, given the different options available.
Same goes for including Immediate in the other function name.Reacted by Ruben BartelinkApplied naming updates and guesses on changes to fsharp/fslang-suggestions#1467
Reacted by Tomas GrosupI like the changes done.
It still holds that the more technically detailed explanation must sit in API docs, and there should be an unified way of explaining common terms (a shared glossary file perharps, if I decide to land
/// <include file="{share glossary xml}" path="/async-task-glossary/immediate"/>work which has a draft PR. Forcing use to use a shared glossary across Async/Task/ValueTask would ensure that same english words carry the same meaning across the API set ) :
start/run
immediate
result
catchAs a basic test, are we able to one-sentence explain the difference between
startandrun?Reacted by Ruben Bartelink@T-Gro A very good question
Glossary
Reasonably clear terms:
- Synchronously - blocking the current OS thread (not for general use in a tree of computation)
- Immediate - starts on current thread with no hops (which might be bad)
- Child - shares/propagates/links cancellation token; child runs concurrently
Less clear terms:
Run- kick off somewhere and give me a handle to it
- Alludes to Task.Run (all overloads return a Task)
- IMO Task.Run is a common well known thing for most .NET devs
Start- used in names of lots of Async things including MailboxProcessor
- anything Async can be started to run parallel or concurrently depending on lots of factors
- in general is intended to convey we're not doing stuff inline - we're kicking off parallel work here is the implication
AsTask- Because there was no
Taskin FSharp.Core historically, lots of things that arguably belong on that side of the fence (and would simplify the naming and documentation ofAsync.(Start|Run).*) are mingled in and need disambiguation
Survey
Async API Might ideally be called returns notes RunSynchronouslyAsync.RunBackgroundSynchronously'T blocks, thread hop if not thread pool thread RunSynchronouslyImmediate(NEW)fine? 'T blocks, no thread hop StartAsync.StartBackgroundunit(computation must beAsync<unit>)thread pool only StartAsTaskTask.startAsyncOptTask<'T>admits TaskCreationOptionsStartImmediateAsync.StartImmediateunit(computation must beAsync<unit>)starts immediate, then thread pool StartImmediateAsTaskTask.startAsyncImmediateTask<'T>starts immediate, then thread pool StartChildAsync.StartChildAsync<Async<'T>>Flows CT, admits optional timeout StartChildAsTaskTask.startAsyncChildOptAsync<Task<'T>>Flows CT, admits TaskCreationOptionsStartWithContinuationsfine? unit(computation must beAsync<unit>)RunTaskImmediate(NEW)Async.StartTaskImmediateAsync<'T>Async.CancellationToken >> Async.StartImmediateAsTask >> Async.AwaitConclusion
- Start is not a useful word anywhere than in
Async., where it's primary role is to identify things that instigate Async computations in various ways - Run is similarly meaningless and is also overloaded (RunSynchronously is actually very similar to Task.Run >> _.Wait() semantically), but people think it runs things inline like the proposed
RunSynchronouslyImmediatewill do [more of].
- In the above
RunTaskImmediateis the only one usingRunin the name, and, beingImmediateis actually not close enough toTask.Runsemantics so probablyStartTaskImmediateis a better name. AlsoStartreferencesStartChildAsTask/StartChildlike semantics RunorStartas they stand are not worth/possible to put in a glossary as they are meaningless and/or confusing. IfRunSynchronouslyImmediateis the only new function withRun, there's less need to talk about the difference
Hopefully this triggers someone to see a better naming scheme.
For now unless there are objections, I'm leaning toward changing the proposedI have updatedAsync.RunTaskImmediatetoAsync.StartTaskImmediateto take the start vs run 'debate' off the table.RunTaskImmediateto be namedStartTaskImmediatebased on the aboveReacted by Tomas Grosup
Replaces #128. TaskEx top level issue: #139
Async.Ignorehas always been ugly and undiscoverable. While I tend to make ignoring explicit by usinglet! _ = <async stuff I want to ignore result of>, it's commonly the last expression in a function, and having to dolet! _ = <thing I'm wrapping> in ()is too much.It is proposed that the
moduleassociated with any given builder should by convention have anignorefunction that correctly observes completion of the work, dropping the result, but propagating exceptions, if anyCurrent proposed APIs that are not currently in
FSharp.Core(will be updated inline based on discussion below):NOTES:
TaskSeq(and other libs such as IcedTasks contain various bespoke implementationsFSharp.Coreis the obvious home for them. (However that would raise the issue of whether they need to go into the 6.x release line in order to align with minimal dependencies for various common libraries)