diff --git a/release-notes.txt b/release-notes.txt index 8b5c0802..1b5a945f 100644 --- a/release-notes.txt +++ b/release-notes.txt @@ -2,6 +2,7 @@ Release notes: 0.3.0 (unreleased) + - internal renames, improved doc comments, signature files for complex types, hide internal-only types, #111. - adds support for static TaskLike, allowing the same let! and do! overloads that F# task supports, fixes #110. - implements 'do!' for non-generic Task like with Task.Delay, fixes #43. - adds support for 'for .. in ..' with task sequences in F# tasks and async, #75, #93 and #99 (with help from @theangrybyrd). diff --git a/src/FSharp.Control.TaskSeq/AssemblyInfo.fs b/src/FSharp.Control.TaskSeq/AssemblyInfo.fs new file mode 100644 index 00000000..d8bda820 --- /dev/null +++ b/src/FSharp.Control.TaskSeq/AssemblyInfo.fs @@ -0,0 +1,8 @@ +namespace TaskSeq.Tests + +open System.Runtime.CompilerServices + +// ensure the test project has access to the internal types +[] + +do () diff --git a/src/FSharp.Control.TaskSeq/DebugUtils.fs b/src/FSharp.Control.TaskSeq/DebugUtils.fs new file mode 100644 index 00000000..aab74e3a --- /dev/null +++ b/src/FSharp.Control.TaskSeq/DebugUtils.fs @@ -0,0 +1,55 @@ +namespace FSharp.Control + +open System.Threading.Tasks +open System +open System.Diagnostics +open System.Threading + +type Debug = + + [] + static val mutable private verbose: bool option + + /// Setting from environment variable TASKSEQ_LOG_VERBOSE, which, + /// when set, enables (very) verbose printing of flow and state + static member private getVerboseSetting() = + match Debug.verbose with + | None -> + let verboseEnv = + try + match Environment.GetEnvironmentVariable "TASKSEQ_LOG_VERBOSE" with + | null -> false + | x -> + match x.ToLowerInvariant().Trim() with + | "1" + | "true" + | "on" + | "yes" -> true + | _ -> false + + with _ -> + false + + Debug.verbose <- Some verboseEnv + verboseEnv + + | Some setting -> setting + + /// Private helper to log to stdout in DEBUG builds only + [] + static member private print value = + match Debug.getVerboseSetting () with + | false -> () + | true -> + // don't use ksprintf here, because the compiler does not remove all allocations due to + // the way PrintfFormat types are compiled, even if we set the Conditional attribute. + let ct = Thread.CurrentThread + printfn "%i (%b): %s" ct.ManagedThreadId ct.IsThreadPoolThread value + + /// Log to stdout in DEBUG builds only + [] + static member logInfo(str) = Debug.print str + + /// Log to stdout in DEBUG builds only + [] + static member logInfo(str, data) = Debug.print $"%s{str}{data}" diff --git a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj index 2564a319..7ec1202e 100644 --- a/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj +++ b/src/FSharp.Control.TaskSeq/FSharp.Control.TaskSeq.fsproj @@ -26,6 +26,15 @@ Generates optimized IL code through the new resumable state machines, and comes snupkg + + + + + + + True + + @@ -33,7 +42,11 @@ Generates optimized IL code through the new resumable state machines, and comes \ + + + + diff --git a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs index ea154804..bdf5c725 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fs @@ -19,8 +19,6 @@ open FSharp.Control [] module Internal = // cannot be marked with 'internal' scope - /// Setting from environment variable TASKSEQ_LOG_VERBOSE, which, - /// when set, enables (very) verbose printing of flow and state let initVerbose () = try match Environment.GetEnvironmentVariable "TASKSEQ_LOG_VERBOSE" with @@ -37,10 +35,8 @@ module Internal = // cannot be marked with 'internal' scope false - /// Call MoveNext on an IAsyncStateMachine by reference let inline moveNextRef (x: byref<'T> when 'T :> IAsyncStateMachine) = x.MoveNext() - // F# requires that we implement interfaces even on an abstract class let inline raiseNotImpl () = NotImplementedException "Abstract Class: method or property not implemented" |> raise @@ -79,7 +75,7 @@ type TaskSeqStateMachineData<'T>() = /// A reference to 'self', because otherwise we can't use byref in the resumable code. [] - val mutable boxedSelf: TaskSeq<'T> + val mutable boxedSelf: TaskSeqBase<'T> member data.PushDispose(disposer: unit -> Task) = if isNull data.disposalStack then @@ -91,7 +87,7 @@ type TaskSeqStateMachineData<'T>() = if not (isNull data.disposalStack) then data.disposalStack.RemoveAt(data.disposalStack.Count - 1) -and [] TaskSeq<'T>() = +and [] TaskSeqBase<'T>() = abstract MoveNextAsyncResult: unit -> ValueTask @@ -119,7 +115,7 @@ and [] TaskSeq<'T>() = and [] TaskSeq<'Machine, 'T when 'Machine :> IAsyncStateMachine and 'Machine :> IResumableStateMachine>>() = - inherit TaskSeq<'T>() + inherit TaskSeqBase<'T>() let initialThreadId = Environment.CurrentManagedThreadId /// Shadows the initial machine, just after it is initialized by the F# compiler-generated state. @@ -304,16 +300,16 @@ and [] TaskSeq<'Machine, 'T // assume it's a possibly new, not yet supported case, treat as default ValueTask.ofIValueTaskSource this version -and TaskSeqCode<'T> = ResumableCode, unit> +and ResumableTSC<'T> = ResumableCode, unit> and TaskSeqStateMachine<'T> = ResumableStateMachine> and TaskSeqResumptionFunc<'T> = ResumptionFunc> and TaskSeqResumptionDynamicInfo<'T> = ResumptionDynamicInfo> type TaskSeqBuilder() = - member inline _.Delay(f: unit -> TaskSeqCode<'T>) : TaskSeqCode<'T> = TaskSeqCode<'T>(fun sm -> f().Invoke(&sm)) + member inline _.Delay(f: unit -> ResumableTSC<'T>) : ResumableTSC<'T> = ResumableTSC<'T>(fun sm -> f().Invoke(&sm)) - member inline _.Run(code: TaskSeqCode<'T>) : IAsyncEnumerable<'T> = + member inline _.Run(code: ResumableTSC<'T>) : IAsyncEnumerable<'T> = if __useResumableCode then // This is the static implementation. A new struct type is created. __stateMachine, IAsyncEnumerable<'T>> @@ -386,11 +382,11 @@ type TaskSeqBuilder() = |> raise - member inline _.Zero() : TaskSeqCode<'T> = + member inline _.Zero() : ResumableTSC<'T> = Debug.logInfo "at Zero()" ResumableCode.Zero() - member inline _.Combine(task1: TaskSeqCode<'T>, task2: TaskSeqCode<'T>) : TaskSeqCode<'T> = + member inline _.Combine(task1: ResumableTSC<'T>, task2: ResumableTSC<'T>) : ResumableTSC<'T> = Debug.logInfo "at Combine(.., ..)" ResumableCode.Combine(task1, task2) @@ -399,8 +395,8 @@ type TaskSeqBuilder() = member inline _.WhileAsync ( [] condition: unit -> ValueTask, - body: TaskSeqCode<'T> - ) : TaskSeqCode<'T> = + body: ResumableTSC<'T> + ) : ResumableTSC<'T> = let mutable condition_res = true ResumableCode.While( @@ -436,17 +432,17 @@ type TaskSeqBuilder() = false) ) - member inline b.While([] condition: unit -> bool, body: TaskSeqCode<'T>) : TaskSeqCode<'T> = + member inline b.While([] condition: unit -> bool, body: ResumableTSC<'T>) : ResumableTSC<'T> = Debug.logInfo "at While(...)" ResumableCode.While(condition, body) - member inline _.TryWith(body: TaskSeqCode<'T>, catch: exn -> TaskSeqCode<'T>) : TaskSeqCode<'T> = + member inline _.TryWith(body: ResumableTSC<'T>, catch: exn -> ResumableTSC<'T>) : ResumableTSC<'T> = ResumableCode.TryWith(body, catch) - member inline _.TryFinallyAsync(body: TaskSeqCode<'T>, compensation: unit -> Task) : TaskSeqCode<'T> = + member inline _.TryFinallyAsync(body: ResumableTSC<'T>, compensation: unit -> Task) : ResumableTSC<'T> = ResumableCode.TryFinallyAsync( - TaskSeqCode<'T>(fun sm -> + ResumableTSC<'T>(fun sm -> sm.Data.PushDispose(fun () -> compensation ()) body.Invoke(&sm)), @@ -467,9 +463,9 @@ type TaskSeqBuilder() = __stack_condition_fin) ) - member inline _.TryFinally(body: TaskSeqCode<'T>, compensation: unit -> unit) : TaskSeqCode<'T> = + member inline _.TryFinally(body: ResumableTSC<'T>, compensation: unit -> unit) : ResumableTSC<'T> = ResumableCode.TryFinally( - TaskSeqCode<'T>(fun sm -> + ResumableTSC<'T>(fun sm -> sm.Data.PushDispose(fun () -> compensation () Task.CompletedTask) @@ -482,7 +478,7 @@ type TaskSeqBuilder() = true) ) - member inline this.Using(disp: #IAsyncDisposable, body: #IAsyncDisposable -> TaskSeqCode<'T>) : TaskSeqCode<'T> = + member inline this.Using(disp: #IAsyncDisposable, body: #IAsyncDisposable -> ResumableTSC<'T>) : ResumableTSC<'T> = // A using statement is just a try/finally with the finally block disposing if non-null. this.TryFinallyAsync( @@ -494,8 +490,8 @@ type TaskSeqBuilder() = Task.CompletedTask) ) - member inline _.Yield(v: 'T) : TaskSeqCode<'T> = - TaskSeqCode<'T>(fun sm -> + member inline _.Yield(v: 'T) : ResumableTSC<'T> = + ResumableTSC<'T>(fun sm -> // This will yield with __stack_fin = false // This will resume with __stack_fin = true Debug.logInfo "at Yield" @@ -546,10 +542,10 @@ module LowPriority = and ^Awaiter: (member GetResult: unit -> 'TResult1)> ( task: ^TaskLike, - continuation: ('TResult1 -> TaskSeqCode<'TResult2>) - ) : TaskSeqCode<'TResult2> = + continuation: ('TResult1 -> ResumableTSC<'TResult2>) + ) : ResumableTSC<'TResult2> = - TaskSeqCode<'TResult2>(fun sm -> + ResumableTSC<'TResult2>(fun sm -> let mutable awaiter = (^TaskLike: (member GetAwaiter: unit -> ^Awaiter) (task)) let mutable __stack_fin = true @@ -581,7 +577,7 @@ module LowPriority = module MediumPriority = type TaskSeqBuilder with - member inline this.Using(disp: #IDisposable, body: #IDisposable -> TaskSeqCode<'T>) : TaskSeqCode<'T> = + member inline this.Using(disp: #IDisposable, body: #IDisposable -> ResumableTSC<'T>) : ResumableTSC<'T> = // A using statement is just a try/finally with the finally block disposing if non-null. this.TryFinally( @@ -592,7 +588,7 @@ module MediumPriority = disp.Dispose()) ) - member inline this.For(sequence: seq<'TElement>, body: 'TElement -> TaskSeqCode<'T>) : TaskSeqCode<'T> = + member inline this.For(sequence: seq<'TElement>, body: 'TElement -> ResumableTSC<'T>) : ResumableTSC<'T> = // A for loop is just a using statement on the sequence's enumerator... this.Using( sequence.GetEnumerator(), @@ -600,14 +596,14 @@ module MediumPriority = (fun e -> this.While((fun () -> e.MoveNext()), (fun sm -> (body e.Current).Invoke(&sm)))) ) - member inline this.YieldFrom(source: seq<'T>) : TaskSeqCode<'T> = this.For(source, (fun v -> this.Yield(v))) + member inline this.YieldFrom(source: seq<'T>) : ResumableTSC<'T> = this.For(source, (fun v -> this.Yield(v))) member inline this.For ( source: #IAsyncEnumerable<'TElement>, - body: 'TElement -> TaskSeqCode<'T> - ) : TaskSeqCode<'T> = - TaskSeqCode<'T>(fun sm -> + body: 'TElement -> ResumableTSC<'T> + ) : ResumableTSC<'T> = + ResumableTSC<'T>(fun sm -> this .Using( source.GetAsyncEnumerator(sm.Data.cancellationToken), @@ -616,7 +612,7 @@ module MediumPriority = ) .Invoke(&sm)) - member inline this.YieldFrom(source: IAsyncEnumerable<'T>) : TaskSeqCode<'T> = + member inline this.YieldFrom(source: IAsyncEnumerable<'T>) : ResumableTSC<'T> = this.For(source, (fun v -> this.Yield(v))) [] @@ -633,8 +629,8 @@ module HighPriority = // - In contrast, ValueTask<_> *does have* GetResult() -> 'TResult // - Conclusion: we do not need an extra overload anymore for ValueTask // - member inline _.Bind(task: Task<'TResult1>, continuation: ('TResult1 -> TaskSeqCode<'T>)) : TaskSeqCode<'T> = - TaskSeqCode<'T>(fun sm -> + member inline _.Bind(task: Task<'TResult1>, continuation: ('TResult1 -> ResumableTSC<'T>)) : ResumableTSC<'T> = + ResumableTSC<'T>(fun sm -> let mutable awaiter = task.GetAwaiter() let mutable __stack_fin = true @@ -660,3 +656,8 @@ module HighPriority = sm.Data.awaiter <- awaiter sm.Data.current <- ValueNone false) + +[] +module TaskSeqBuilder = + /// Builds an asynchronous task sequence based on IAsyncEnumerable<'T> using computation expression syntax. + let taskSeq = TaskSeqBuilder() diff --git a/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fsi b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fsi new file mode 100644 index 00000000..4f115910 --- /dev/null +++ b/src/FSharp.Control.TaskSeq/TaskSeqBuilder.fsi @@ -0,0 +1,193 @@ +namespace FSharp.Control + +open System +open System.Threading +open System.Threading.Tasks +open System.Threading.Tasks.Sources +open System.Runtime.CompilerServices +open System.Collections.Generic + +open FSharp.Core.CompilerServices + +[] +module Internal = + + /// + /// Setting from environment variable , which, + /// when set, enables (very) verbose printing of flow and state + /// + val initVerbose: unit -> bool + + /// Call MoveNext on an IAsyncStateMachine by reference + val inline moveNextRef: x: byref<#IAsyncStateMachine> -> unit + + /// F# requires that we implement interfaces even on an abstract class. + val inline raiseNotImpl: unit -> 'a + +/// +/// Result of any computation expression, alias for . +/// +type taskSeq<'T> = IAsyncEnumerable<'T> + +/// TaskSeqCode type alias of ResumableCode delegate type, specially recognized by the F# compiler +and ResumableTSC<'T> = ResumableCode, unit> + +/// +/// Contains the state data for the computation expression builder. +/// For use in this library only. Required by the method. +/// +and TaskSeqStateMachine<'T> = ResumableStateMachine> + +/// +/// Contains the state data for the computation expression builder. +/// For use in this library only. Required by the method. +/// +and [] TaskSeqStateMachineData<'T> = + + new: unit -> TaskSeqStateMachineData<'T> + + [] + val mutable cancellationToken: CancellationToken + + /// Keeps track of the objects that need to be disposed off on IAsyncDispose. + [] + val mutable disposalStack: ResizeArray<(unit -> Task)> + + [] + val mutable awaiter: ICriticalNotifyCompletion + + [] + val mutable promiseOfValueOrEnd: ManualResetValueTaskSourceCore + + /// Helper struct providing methods for awaiting 'next' in async iteration scenarios. + [] + val mutable builder: AsyncIteratorMethodBuilder + + /// Whether or not a full iteration through the IAsyncEnumerator has completed + [] + val mutable completed: bool + + /// Used by the AsyncEnumerator interface to return the Current value when + /// IAsyncEnumerator.Current is called + [] + val mutable current: ValueOption<'T> + + /// A reference to 'self', because otherwise we can't use byref in the resumable code. + [] + val mutable boxedSelf: TaskSeqBase<'T> + + member PopDispose: unit -> unit + + member PushDispose: disposer: (unit -> Task) -> unit + +/// +/// Abstract base class for . +/// For use by this library only, should not be used directly in user code. Its operation depends highly on resumable state. +/// +and [] TaskSeqBase<'T> = + interface IValueTaskSource + interface IValueTaskSource + interface IAsyncStateMachine + interface IAsyncEnumerable<'T> + interface IAsyncEnumerator<'T> + + new: unit -> TaskSeqBase<'T> + + abstract MoveNextAsyncResult: unit -> ValueTask + +/// +/// Main implementation of generic and related interfaces, +/// which forms the meat of the logic behind computation expresssions. +/// For use by this library only, should not be used directly in user code. Its operation depends highly on resumable state. +/// +and [] TaskSeq<'Machine, 'T + when 'Machine :> IAsyncStateMachine and 'Machine :> IResumableStateMachine>> = + inherit TaskSeqBase<'T> + interface IAsyncEnumerator<'T> + interface IAsyncEnumerable<'T> + interface IAsyncStateMachine + interface IValueTaskSource + interface IValueTaskSource + + new: unit -> TaskSeq<'Machine, 'T> + + [] + val mutable _initialMachine: 'Machine + + /// Keeps the active state machine. + [] + val mutable _machine: 'Machine + + //new: unit -> TaskSeq<'Machine, 'T> + member InitMachineData: ct: CancellationToken * machine: byref<'Machine> -> unit + override MoveNextAsyncResult: unit -> ValueTask + +/// +/// Main builder class for the computation expression. +/// +[] +type TaskSeqBuilder = + + member inline Combine: task1: ResumableTSC<'T> * task2: ResumableTSC<'T> -> ResumableTSC<'T> + member inline Delay: f: (unit -> ResumableTSC<'T>) -> ResumableTSC<'T> + member inline Run: code: ResumableTSC<'T> -> taskSeq<'T> + member inline TryFinally: body: ResumableTSC<'T> * compensation: (unit -> unit) -> ResumableTSC<'T> + member inline TryFinallyAsync: body: ResumableTSC<'T> * compensation: (unit -> Task) -> ResumableTSC<'T> + member inline TryWith: body: ResumableTSC<'T> * catch: (exn -> ResumableTSC<'T>) -> ResumableTSC<'T> + member inline Using: disp: 'a * body: ('a -> ResumableTSC<'T>) -> ResumableTSC<'T> when 'a :> IAsyncDisposable + member inline While: condition: (unit -> bool) * body: ResumableTSC<'T> -> ResumableTSC<'T> + /// Used by `For`. F# currently doesn't support `while!`, so this cannot be called directly from the CE + member inline WhileAsync: condition: (unit -> ValueTask) * body: ResumableTSC<'T> -> ResumableTSC<'T> + member inline Yield: v: 'T -> ResumableTSC<'T> + member inline Zero: unit -> ResumableTSC<'T> + +[] +module TaskSeqBuilder = + + /// + /// Builds an asynchronous task sequence based on using computation expression syntax. + /// + val taskSeq: TaskSeqBuilder + +/// +/// Contains low priority extension methods for the main builder class for the computation expression. +/// The , and modules are not meant to be +/// accessed directly from user code. They solely serve to disambiguate overload resolution inside the computation expression. +/// +[] +module LowPriority = + type TaskSeqBuilder with + + [] + member inline Bind< ^TaskLike, 'TResult1, 'TResult2, ^Awaiter, 'TOverall> : + task: ^TaskLike * continuation: ('TResult1 -> ResumableTSC<'TResult2>) -> ResumableTSC<'TResult2> + when ^TaskLike: (member GetAwaiter: unit -> ^Awaiter) + and ^Awaiter :> ICriticalNotifyCompletion + and ^Awaiter: (member get_IsCompleted: unit -> bool) + and ^Awaiter: (member GetResult: unit -> 'TResult1) + +/// +/// Contains low priority extension methods for the main builder class for the computation expression. +/// The , and modules are not meant to be +/// accessed directly from user code. They solely serve to disambiguate overload resolution inside the computation expression. +/// +[] +module MediumPriority = + type TaskSeqBuilder with + + member inline Using: disp: 'a * body: ('a -> ResumableTSC<'T>) -> ResumableTSC<'T> when 'a :> IDisposable + member inline For: sequence: seq<'TElement> * body: ('TElement -> ResumableTSC<'T>) -> ResumableTSC<'T> + member inline YieldFrom: source: seq<'T> -> ResumableTSC<'T> + member inline For: source: #taskSeq<'TElement> * body: ('TElement -> ResumableTSC<'T>) -> ResumableTSC<'T> + member inline YieldFrom: source: taskSeq<'T> -> ResumableTSC<'T> + +/// +/// Contains low priority extension methods for the main builder class for the computation expression. +/// The , and modules are not meant to be +/// accessed directly from user code. They solely serve to disambiguate overload resolution inside the computation expression. +/// +[] +module HighPriority = + type TaskSeqBuilder with + + member inline Bind: task: Task<'TResult1> * continuation: ('TResult1 -> ResumableTSC<'T>) -> ResumableTSC<'T> diff --git a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs index deeaefbd..8f7446ef 100644 --- a/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs +++ b/src/FSharp.Control.TaskSeq/TaskSeqInternal.fs @@ -5,41 +5,36 @@ open System.Collections.Generic open System.Threading open System.Threading.Tasks -[] -module ExtraTaskSeqOperators = - /// A TaskSeq workflow for IAsyncEnumerable<'T> types. - let taskSeq = TaskSeqBuilder() - [] -type AsyncEnumStatus = +type internal AsyncEnumStatus = | BeforeAll | WithCurrent | AfterAll [] -type Action<'T, 'U, 'TaskU when 'TaskU :> Task<'U>> = +type internal Action<'T, 'U, 'TaskU when 'TaskU :> Task<'U>> = | CountableAction of countable_action: (int -> 'T -> 'U) | SimpleAction of simple_action: ('T -> 'U) | AsyncCountableAction of async_countable_action: (int -> 'T -> 'TaskU) | AsyncSimpleAction of async_simple_action: ('T -> 'TaskU) [] -type FolderAction<'T, 'State, 'TaskState when 'TaskState :> Task<'State>> = +type internal FolderAction<'T, 'State, 'TaskState when 'TaskState :> Task<'State>> = | FolderAction of state_action: ('State -> 'T -> 'State) | AsyncFolderAction of async_state_action: ('State -> 'T -> 'TaskState) [] -type ChooserAction<'T, 'U, 'TaskOption when 'TaskOption :> Task<'U option>> = +type internal ChooserAction<'T, 'U, 'TaskOption when 'TaskOption :> Task<'U option>> = | TryPick of try_pick: ('T -> 'U option) | TryPickAsync of async_try_pick: ('T -> 'TaskOption) [] -type PredicateAction<'T, 'TaskBool when 'TaskBool :> Task> = +type internal PredicateAction<'T, 'TaskBool when 'TaskBool :> Task> = | Predicate of try_filter: ('T -> bool) | PredicateAsync of async_try_filter: ('T -> 'TaskBool) [] -type InitAction<'T, 'TaskT when 'TaskT :> Task<'T>> = +type internal InitAction<'T, 'TaskT when 'TaskT :> Task<'T>> = | InitAction of init_item: (int -> 'T) | InitActionAsync of async_init_item: (int -> 'TaskT) diff --git a/src/FSharp.Control.TaskSeq/Utils.fs b/src/FSharp.Control.TaskSeq/Utils.fs index 5b35fcaa..63274ed4 100644 --- a/src/FSharp.Control.TaskSeq/Utils.fs +++ b/src/FSharp.Control.TaskSeq/Utils.fs @@ -115,52 +115,3 @@ module Async = /// Bind an Async<'T> let inline bind binder (task: Async<'T>) : Async<'U> = ExtraTopLevelOperators.async { return! binder task } - -type Debug = - - [] - static val mutable private verbose: bool option - - /// Setting from environment variable TASKSEQ_LOG_VERBOSE, which, - /// when set, enables (very) verbose printing of flow and state - static member private getVerboseSetting() = - match Debug.verbose with - | None -> - let verboseEnv = - try - match Environment.GetEnvironmentVariable "TASKSEQ_LOG_VERBOSE" with - | null -> false - | x -> - match x.ToLowerInvariant().Trim() with - | "1" - | "true" - | "on" - | "yes" -> true - | _ -> false - - with _ -> - false - - Debug.verbose <- Some verboseEnv - verboseEnv - - | Some setting -> setting - - /// Private helper to log to stdout in DEBUG builds only - [] - static member private print value = - match Debug.getVerboseSetting () with - | false -> () - | true -> - // don't use ksprintf here, because the compiler does not remove all allocations due to - // the way PrintfFormat types are compiled, even if we set the Conditional attribute. - let ct = Thread.CurrentThread - printfn "%i (%b): %s" ct.ManagedThreadId ct.IsThreadPoolThread value - - /// Log to stdout in DEBUG builds only - [] - static member logInfo(str) = Debug.print str - - /// Log to stdout in DEBUG builds only - [] - static member logInfo(str, data) = Debug.print $"%s{str}{data}" diff --git a/src/FSharp.Control.TaskSeq/Utils.fsi b/src/FSharp.Control.TaskSeq/Utils.fsi new file mode 100644 index 00000000..219d8e0a --- /dev/null +++ b/src/FSharp.Control.TaskSeq/Utils.fsi @@ -0,0 +1,87 @@ +namespace FSharp.Control + +open System.Diagnostics +open System.Threading.Tasks +open System.Threading.Tasks.Sources + +[] +module ValueTaskExtensions = + type System.Threading.Tasks.ValueTask with + + /// (Extension member) Gets a task that has already completed successfully. + static member inline CompletedTask: System.Threading.Tasks.ValueTask + +module ValueTask = + + /// A successfully completed ValueTask of boolean that has the value false. + val False: ValueTask + + /// A successfully completed ValueTask of boolean that has the value true. + val True: ValueTask + + /// Creates a ValueTask with the supplied result of the successful operation. + val inline FromResult: x: 'T -> ValueTask<'T> + + /// Creates a ValueTask with an IValueTaskSource representing the operation + val inline ofIValueTaskSource: taskSource: IValueTaskSource -> version: int16 -> ValueTask + + /// Creates a ValueTask form a Task<'T> + val inline ofTask: task: Task<'T> -> ValueTask<'T> + + /// Ignore a ValueTask<'T>, returns a non-generic ValueTask. + val inline ignore: vtask: ValueTask<'T> -> ValueTask + +module Task = + + /// Convert an Async<'T> into a Task<'T> + val inline ofAsync: async: Async<'T> -> Task<'T> + + /// Convert a unit-task into a Task + val inline ofTask: task': Task -> Task + + /// Convert a non-task function into a task-returning function + val inline apply: func: ('a -> 'b) -> ('a -> Task<'b>) + + /// Convert a Task<'T> into an Async<'T> + val inline toAsync: task: Task<'T> -> Async<'T> + + /// Convert a Task<'T> into a ValueTask<'T> + val inline toValueTask: task: Task<'T> -> ValueTask<'T> + + /// + /// Convert a ValueTask<'T> to a Task<'T>. To use a non-generic ValueTask, + /// consider using: . + /// + val inline ofValueTask: valueTask: ValueTask<'T> -> Task<'T> + + /// Convert a Task<'T> into a non-generic Task, ignoring the result + val inline ignore: task: Task<'T> -> Task + + /// Map a Task<'T> + val inline map: mapper: ('T -> 'U) -> task: Task<'T> -> Task<'U> + + /// Bind a Task<'T> + val inline bind: binder: ('T -> #Task<'U>) -> task: Task<'T> -> Task<'U> + + /// Create a task from a value + val inline fromResult: value: 'U -> Task<'U> + +module Async = + + /// Convert an Task<'T> into an Async<'T> + val inline ofTask: task: Task<'T> -> Async<'T> + + /// Convert a unit-task into an Async + val inline ofUnitTask: task: Task -> Async + + /// Convert a Task<'T> into an Async<'T> + val inline toTask: async: Async<'T> -> Task<'T> + + /// Convert an Async<'T> into an Async, ignoring the result + val inline ignore: async': Async<'T> -> Async + + /// Map an Async<'T> + val inline map: mapper: ('T -> 'U) -> async: Async<'T> -> Async<'U> + + /// Bind an Async<'T> + val inline bind: binder: (Async<'T> -> Async<'U>) -> task: Async<'T> -> Async<'U>