Resilience
Calls to other systems fail: networks drop packets, services restart, databases time out. Since effects are values that only run when asked to, running one again is just a matter of asking again, and Orsak comes with functions for retrying, backing off, logging failures, timing out, and keeping background work alive. The functions in this guide come from two places:
- the
Effectmodule inOrsak, with the general-purpose ones, such asEffect.retryWhileandEffect.timeout; - the
Effectmodule inOrsak.Resilience(.NET 8 and later), with retrying, backoff with jitter, and error logging.
Opening both makes all of them available as Effect.*.
This page is a script: it runs with dotnet fsi docs/resilience.fsx once Orsak is built, and the output shown is
from such a run. Timings vary between runs, as the backoff is randomized.
A flaky dependency
The examples use an inventory service, described by an interface in the usual way, with a provider interface and a module creating the effect:
open System
open System.Diagnostics
open System.Threading
open System.Threading.Tasks
open Microsoft.Extensions.Logging
open Orsak
open Orsak.Resilience
type StockError =
| Unavailable
| UnknownProduct of string
| TimedOut
type IInventory =
abstract GetStock: product: string -> Task<Result<int, StockError>>
type IInventoryProvider =
abstract Inventory: IInventory
module Inventory =
let getStock product =
Effect.Create(fun (p: #IInventoryProvider) -> p.Inventory.GetStock product)
The implementation used here is unavailable for its first failures calls, and then answers. It prints each call,
with the time since it was created, and can be slowed down to show timeouts:
type FlakyInventory(failures: int, ?latency: TimeSpan) =
let clock = Stopwatch.StartNew()
let mutable calls = 0
interface IInventory with
member _.GetStock product = task {
calls <- calls + 1
printfn $"%5d{clock.ElapsedMilliseconds} ms call {calls}"
match latency with
| Some latency -> do! Task.Delay latency
| None -> ()
if product <> "orsak-mug" then return Error(UnknownProduct product)
elif calls <= failures then return Error Unavailable
else return Ok 42
}
The environment provides the inventory, and implements ILoggerFactory, which Effect.logError uses. A real
application would use the logger factory from its host; this one writes to the console.
type Env(inventory: IInventory) =
let logging: ILoggerFactory = new ConsoleLoggerFactory()
interface IInventoryProvider with
member _.Inventory = inventory
interface ILoggerFactory with
member _.CreateLogger(category) = logging.CreateLogger(category)
member _.AddProvider(provider) = logging.AddProvider(provider)
member _.Dispose() = ()
/// Runs an effect with an inventory, and prints its result.
let runWith (inventory: IInventory) (effect: Effect<Env, 'a, 'e>) =
let result = effect |> Effect.run (new Env(inventory))
printfn $" result: %A{result.AsTask().Result}"
Without any resilience, the first failure is the result:
Inventory.getStock "orsak-mug" |> runWith (FlakyInventory(failures = 2))
|
Retrying
Effect.retryTimes runs the effect again when it fails, up to the given number of times after the first attempt,
and fails with the last error if every attempt does:
Inventory.getStock "orsak-mug"
|> Effect.retryTimes 3L
|> runWith (FlakyInventory(failures = 2))
|
Not every error is worth retrying: an unknown product will stay unknown. Effect.retryWhile retries for as long as
the error passes a test, and fails with the first error that doesn't:
let isTransient error =
match error with
| Unavailable
| TimedOut -> true
| UnknownProduct _ -> false
Inventory.getStock "orsak-cup"
|> Effect.retryWhile isTransient
|> runWith (FlakyInventory(failures = 2))
|
Effect.retryWhile has no limit, so it suits errors that are known to pass. Combine it with Effect.retryTimes to
also bound the attempts.
Backing off
Retrying immediately puts more load on a service that is already struggling. Effect.addDelayOnError waits before
returning a failure, so that the next attempt comes later, and each consecutive failure waits longer: the delay grows
exponentially from the given base delay, with random jitter, so that many clients don't retry in lockstep. A success
resets it. The delays are in seconds:
Inventory.getStock "orsak-mug"
|> Effect.addDelayOnError 0.05<s>
|> Effect.retryTimes 5L
|> runWith (FlakyInventory(failures = 4))
|
Effect.addDelayOnErrorWithMax also caps the delay. The delays use the environment's ITimeProvider, if it
implements one, so that tests can run them on a fake clock, and likewise its IRandomProvider for the jitter and its
ICancellationProvider to cancel a delay.
Logging failures
A retried failure is invisible from the outside, unless it is logged. Effect.logError logs the error when the
effect fails, with the environment's ILoggerFactory, and fails with the same error. Placed before the retry, it logs
every failed attempt:
Inventory.getStock "orsak-mug"
|> Effect.logError (fun logging error ->
logging.CreateLogger("Inventory").LogWarning("Stock lookup failed: {Error}", error))
|> Effect.addDelayOnError 0.05<s>
|> Effect.retryTimes 5L
|> runWith (FlakyInventory(failures = 2))
|
Timing out
Effect.timeout fails with the given error when the effect takes longer than allowed. The effect itself keeps
running in the background, as tasks can't be stopped from the outside; pass a cancellation token to the work to stop
it. A timeout is a transient error, so it combines with retrying. Here every call is too slow, so the attempts are
bounded with Effect.retryTimes; Effect.retryWhile isTransient would retry forever:
Inventory.getStock "orsak-mug"
|> Effect.timeout (TimeSpan.FromMilliseconds 100.) TimedOut
|> Effect.retryTimes 2L
|> runWith (FlakyInventory(failures = 0, latency = TimeSpan.FromMilliseconds 300.))
|
Background work
A background worker, such as a queue consumer, should keep running: when there is no work, it should wait before
looking again, rather than spin, and when it fails, it should carry on. The forever computation expression marks an
effect that isn't meant to complete, and the functions below build one from an effect that does one unit of work.
The queue, and an effect that processes one message, returning whether there was one:
type IQueue =
abstract TryReceive: unit -> Task<string option>
type IQueueProvider =
abstract Queue: IQueue
module Queue =
let tryReceive () =
Effect.Create(fun (p: #IQueueProvider) -> p.Queue.TryReceive())
let clock = Stopwatch.StartNew()
let processNext () = eff {
match! Queue.tryReceive () with
| Some message ->
printfn $"%5d{clock.ElapsedMilliseconds} ms processed {message}"
return true
| None ->
printfn $"%5d{clock.ElapsedMilliseconds} ms queue empty"
return false
}
Effect.addDelayWithMax_ waits after the effect returns false, with the same growing delay as above, capped at
the maximum, and resets it when the effect returns true: the worker checks an idle queue less and less often, and
picks up speed again as soon as there is work. Effect.retryForever keeps it going when an attempt fails, and
Effect.repeatUntilCancellation repeats it until the token is cancelled:
let worker (token: CancellationToken) =
processNext ()
|> Effect.addDelayWithMax_ 0.02<s> 0.3<s>
|> Effect.retryForever
|> Effect.repeatUntilCancellation token
The result is an effect of Never, a type without values: it can only end through cancellation, or an exception.
The Forever pattern matches its result, without a case for success or failure:
let messages = Collections.Concurrent.ConcurrentQueue [ "order-1"; "order-2"; "order-3" ]
type QueueEnv() =
interface IQueueProvider with
member _.Queue =
{ new IQueue with
member _.TryReceive() =
match messages.TryDequeue() with
| true, message -> Task.FromResult(Some message)
| false, _ -> Task.FromResult None
}
let stopAfter = new CancellationTokenSource(TimeSpan.FromSeconds 1.0)
match (worker stopAfter.Token |> Effect.run (QueueEnv())).AsTask().Result with
| Forever -> printfn " worker stopped"
|
In an ASP.NET Core application, AddEffectWorker from Orsak.AspNetCore hosts such a worker as a background service.
val string: value: 'T -> string
--------------------
type string = String
type Task = interface IAsyncResult interface IDisposable new: action: Action -> unit + 7 overloads member ConfigureAwait: continueOnCapturedContext: bool -> ConfiguredTaskAwaitable + 1 overload member ContinueWith: continuationAction: Action<Task,obj> * state: obj -> Task + 19 overloads member Dispose: unit -> unit member GetAwaiter: unit -> TaskAwaiter member RunSynchronously: unit -> unit + 1 overload member Start: unit -> unit + 1 overload member Wait: unit -> unit + 5 overloads ...
<summary>Represents an asynchronous operation.</summary>
--------------------
type Task<'TResult> = inherit Task new: ``function`` : Func<obj,'TResult> * state: obj -> unit + 7 overloads member ConfigureAwait: continueOnCapturedContext: bool -> ConfiguredTaskAwaitable<'TResult> + 1 overload member ContinueWith: continuationAction: Action<Task<'TResult>,obj> * state: obj -> Task + 19 overloads member GetAwaiter: unit -> TaskAwaiter<'TResult> member WaitAsync: cancellationToken: CancellationToken -> Task<'TResult> + 4 overloads member Result: 'TResult static member Factory: TaskFactory<'TResult>
<summary>Represents an asynchronous operation that can return a value.</summary>
<typeparam name="TResult">The type of the result produced by this <see cref="T:System.Threading.Tasks.Task`1" />.</typeparam>
--------------------
Task(action: Action) : Task
Task(action: Action, cancellationToken: CancellationToken) : Task
Task(action: Action, creationOptions: TaskCreationOptions) : Task
Task(action: Action<obj>, state: obj) : Task
Task(action: Action, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : Task
Task(action: Action<obj>, state: obj, cancellationToken: CancellationToken) : Task
Task(action: Action<obj>, state: obj, creationOptions: TaskCreationOptions) : Task
Task(action: Action<obj>, state: obj, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : Task
--------------------
Task(``function`` : Func<'TResult>) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj) : Task<'TResult>
Task(``function`` : Func<'TResult>, cancellationToken: CancellationToken) : Task<'TResult>
Task(``function`` : Func<'TResult>, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj, cancellationToken: CancellationToken) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : Func<'TResult>, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : Task<'TResult>
Task(``function`` : Func<obj,'TResult>, state: obj, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : Task<'TResult>
module Result from Microsoft.FSharp.Core
--------------------
type Result<'T,'TError> = | Ok of ResultValue: 'T | Error of ErrorValue: 'TError
val int: value: 'T -> int (requires member op_Explicit)
--------------------
type int = int32
--------------------
type int<'Measure> = int
union case Effect.Effect: EffectDelegate<'r,'a,'e> -> Effect<'r,'a,'e>
--------------------
module Effect from Orsak.Resilience
<summary> Resilience functions for effects: retrying with backoff, logging errors, and running effects perpetually. </summary>
--------------------
module Effect from Orsak
<summary> Functions for running, combining, recovering and repeating effects. </summary>
--------------------
type Effect = static member Create: f: ('a -> Task<Result<'b,'e>>) -> Effect<'a,'b,'e> + 2 overloads static member Error: error: 'e -> Effect<'a,'b,'e>
<summary> Creates effects. <c>Effect.Create</c> turns a function of an environment into an effect, and is how effects are made from the interfaces that describe side effects. The function may return a plain value, a <c>Result</c>, a <c>Task</c>, a <c>ValueTask</c> or an <c>Async</c>, of a value or of a <c>Result</c>; returning a <c>Result</c> lets the effect fail. </summary>
<example> The usual pattern: an interface describing the side effect, a provider interface exposing it, and a function creating the effect, which requires any environment that implements the provider (<c>#IConsoleProvider</c>). <code lang="fsharp"> type IConsole = abstract ReadLine: unit -> string abstract WriteLine: string -> unit type IConsoleProvider = abstract Console: IConsole module Console = let readLine () = Effect.Create(fun (p: #IConsoleProvider) -> p.Console.ReadLine()) let writeLine line = Effect.Create(fun (p: #IConsoleProvider) -> p.Console.WriteLine line) </code> The Orsak.Myriad generators can write the functions of such a module from the interface. </example>
--------------------
type Effect<'r,'a,'e> = | Effect of EffectDelegate<'r,'a,'e> member Run: r: 'r -> AsyncResult<'a,'e> member RunOrFail: r: 'r -> Task<'a> static member (<*>)<'b,'a (requires member (+))> : f: Effect<'r,('b -> 'a),'a0> * e: Effect<'r,'b,'a0> -> Effect<'r,'a,'a0> (requires member (+)) static member (>>=) : h: Effect<'r,'a,'e> * f: ('a -> Effect<'r,'b,'e>) -> Effect<'r,'b,'e> static member Map: h: Effect<'r,'a,'e> * f: ('a -> 'b) -> Effect<'r,'b,'e> static member Return<'a,'b,'c> : a: 'a0 -> Effect<'b,'a0,'c> static member ap<'r,'a,'b,'e> : applicative: Effect<'r,('b -> 'a),'e> -> e: Effect<'r,'b,'e> -> Effect<'r,'a,'e>
<summary> Describes an effect that can either succeed with <typeparamref name="'a" />, or fail with <typeparamref name="'e" />. The effect is 'cold', and only starts when run with an <typeparamref name="'r" />. </summary>
<remarks> Effects are usually written with the <c>eff</c> computation expression, and created from interfaces that describe side effects with <c>Effect.Create</c>. The environment <typeparamref name="'r" /> is inferred from what the effect uses, and supplied when the effect is run. </remarks>
<example><code lang="fsharp"> let greet () = eff { let! name = Console.readLine () do! Console.writeLine $"Hello {name}" } // at the composition root, with an environment providing the console effects: let! result = greet () |> Effect.run env </code></example>
<typeparam name="'r"> The environment required to run the effect </typeparam>
<typeparam name="'a"> The resulting type when the effect runs successfully </typeparam>
<typeparam name="'e"> The resulting type when the effect fails</typeparam>
static member Effect.Create: f: ('a -> Async<'b>) -> Effect<'a,'b,'a0>
static member Effect.Create: f: ('a -> Result<'b,'e>) -> Effect<'a,'b,'e>
static member Effect.Create: f: ('a -> Task<'b>) -> Effect<'a,'b,'a0>
static member Effect.Create: f: ('a -> ValueTask<'b>) -> Effect<'a,'b,'a0>
static member Effect.Create: f: ('a -> Async<Result<'b,'e>>) -> Effect<'a,'b,'e>
static member Effect.Create: f: ('a -> ValueTask<Result<'b,'e>>) -> Effect<'a,'b,'e>
static member Effect.Create: f: ('a -> Task<Result<'b,'e>>) -> Effect<'a,'b,'e>
type FlakyInventory = interface IInventory new: failures: int * ?latency: TimeSpan -> FlakyInventory
--------------------
new: failures: int * ?latency: TimeSpan -> FlakyInventory
type TimeSpan = new: hours: int * minutes: int * seconds: int -> unit + 4 overloads member Add: ts: TimeSpan -> TimeSpan member CompareTo: value: obj -> int + 1 overload member Divide: divisor: float -> TimeSpan + 1 overload member Duration: unit -> TimeSpan member Equals: value: obj -> bool + 2 overloads member GetHashCode: unit -> int member Multiply: factor: float -> TimeSpan member Negate: unit -> TimeSpan member Subtract: ts: TimeSpan -> TimeSpan ...
<summary>Represents a time interval.</summary>
--------------------
TimeSpan ()
TimeSpan(ticks: int64) : TimeSpan
TimeSpan(hours: int, minutes: int, seconds: int) : TimeSpan
TimeSpan(days: int, hours: int, minutes: int, seconds: int) : TimeSpan
TimeSpan(days: int, hours: int, minutes: int, seconds: int, milliseconds: int) : TimeSpan
TimeSpan(days: int, hours: int, minutes: int, seconds: int, milliseconds: int, microseconds: int) : TimeSpan
type Stopwatch = new: unit -> unit member Reset: unit -> unit member Restart: unit -> unit member Start: unit -> unit member Stop: unit -> unit member ToString: unit -> string static member GetElapsedTime: startingTimestamp: int64 -> TimeSpan + 1 overload static member GetTimestamp: unit -> int64 static member StartNew: unit -> Stopwatch static val Frequency: int64 ...
<summary>Provides a set of methods and properties that you can use to accurately measure elapsed time.</summary>
--------------------
Stopwatch() : Stopwatch
<summary>Gets the total elapsed time measured by the current instance, in milliseconds.</summary>
<returns>A read-only long integer representing the total number of milliseconds measured by the current instance.</returns>
Task.Delay(millisecondsDelay: int) : Task
Task.Delay(delay: TimeSpan, timeProvider: TimeProvider) : Task
Task.Delay(delay: TimeSpan, cancellationToken: CancellationToken) : Task
Task.Delay(millisecondsDelay: int, cancellationToken: CancellationToken) : Task
Task.Delay(delay: TimeSpan, timeProvider: TimeProvider, cancellationToken: CancellationToken) : Task
type ConsoleLoggerFactory = interface ILoggerFactory new: unit -> ConsoleLoggerFactory
A console logger that writes synchronously, so its lines appear in order with the rest of the output.
--------------------
new: unit -> ConsoleLoggerFactory
<summary> Represents a type used to configure the logging system and create instances of <see cref="T:Microsoft.Extensions.Logging.ILogger" /> from the registered <see cref="T:Microsoft.Extensions.Logging.ILoggerProvider" />s. </summary>
type ILogger = override BeginScope<'TState> : state: 'TState -> IDisposable override IsEnabled: logLevel: LogLevel -> bool override Log<'TState> : logLevel: LogLevel * eventId: EventId * state: 'TState * ``exception`` : exn * formatter: Func<'TState,exn,string> -> unit
<summary> Represents a type used to perform logging. </summary>
<remarks>Aggregates most logging patterns to a single method.</remarks>
--------------------
type ILogger<'TCategoryName> = inherit ILogger
<summary> A generic interface for logging where the category name is derived from the specified <typeparamref name="TCategoryName" /> type name. Generally used to enable activation of a named <see cref="T:Microsoft.Extensions.Logging.ILogger" /> from dependency injection. </summary>
<typeparam name="TCategoryName">The type whose name is used for the logger category name.</typeparam>
<summary>Provides a mechanism for releasing unmanaged resources.</summary>
type Env = interface ILoggerFactory interface IInventoryProvider new: inventory: IInventory -> Env
--------------------
new: inventory: IInventory -> Env
(extension) ILoggerFactory.CreateLogger(``type`` : Type) : ILogger
ILoggerFactory.CreateLogger(categoryName: string) : ILogger
Runs an effect with an inventory, and prints its result.
<summary> Starts the effect. </summary>
<typeparam name="'r"> The environment required to run the effect </typeparam>
<typeparam name="'a"> The resulting type when the effect runs successfully </typeparam>
<typeparam name="'e"> The resulting type when the effect fails</typeparam>
<param name="env">The environment needed to start the effect</param>
<param name="e">The effect to run</param>
<example><code lang="fsharp"> task { match! placeOrder order |> Effect.run env with | Ok id -> printfn $"Placed {id}" | Error err -> printfn $"Failed: {err}" } </code></example>
val retryTimes: times: int64 -> effect: Effect<'a,'a0,'b> -> Effect<'a,'a0,'b>
<summary> If the provided effect returns an error, tries to re-run it up to <paramref name="times" /> times. </summary>
<param name="times"></param>
<param name="effect"></param>
--------------------
val retryTimes: times: int -> e: Effect<'r,'a,'e> -> Effect<'r,'a,'e>
<summary> Runs the effect again when it fails, up to <paramref name="times" /> times after the first attempt. It fails with the last error if every attempt fails. </summary>
<param name="times">How many times to retry</param>
<param name="e">The effect</param>
<example><code lang="fsharp"> sendEmail message |> Effect.retryTimes 3 </code></example>
<summary> Runs the effect again every time it fails with an error for which <paramref name="cond" /> is true. It fails with the first error for which <paramref name="cond" /> is false. </summary>
<param name="cond">Whether to retry after a given error</param>
<param name="e">The effect</param>
<example><code lang="fsharp"> fetchPrices () |> Effect.retryWhile (fun err -> err = Timeout) </code></example>
<summary> Takes a base delay and an effect, and returns an effect with a delay added at the end, if the provided effect returned with an error. If the effect is re-run, the delay is increased each time the original effect returns an error. If the effect returns Ok, no delay is added, and the calculated delay is reset to 0. </summary>
<param name="baseDelay"></param>
<param name="effect"></param>
<summary>Seconds, the unit of the delays taken by the functions in <c>Orsak.Resilience</c>.</summary>
<summary> Logs the error when the effect fails, with <paramref name="log" />, and then fails with the same error. The environment has to be an <see cref="T:Microsoft.Extensions.Logging.ILoggerFactory" />. </summary>
<param name="log">Logs an error, given the environment's logger factory</param>
<param name="effect">The effect</param>
<example><code lang="fsharp"> importPrices () |> Effect.logError (fun factory err -> factory.CreateLogger("Prices").LogError("Import failed: {Error}", err)) </code></example>
<summary> Configures an effect to fail after a given amount of time, unless it has already succeeded. The original effect still executes. </summary>
<param name="ts">How long to wait for the effect</param>
<param name="onTimeout">The error to fail with when the effect doesn't finish in time</param>
<param name="eff">The effect</param>
<example><code lang="fsharp"> lookupAddress postcode |> Effect.timeout (TimeSpan.FromSeconds 2.0) AddressLookupTimedOut </code></example>
TimeSpan.FromMilliseconds(value: float) : TimeSpan
TimeSpan.FromMilliseconds(milliseconds: int64, microseconds: int64) : TimeSpan
<summary> The computation expression for writing effects. Binding (<c>let!</c>, <c>do!</c>, <c>return!</c>) works with other effects, and with <c>Task</c>, <c>ValueTask</c>, <c>Async</c> and <c>Result</c>, so .NET APIs can be used directly. It supports <c>for</c>, <c>while</c>, <c>use</c>, <c>try/with</c> and <c>try/finally</c>, and <c>and!</c> runs effects concurrently. A failed effect stops the rest of the expression, with its error. </summary>
<example><code lang="fsharp"> let placeOrder (order: Order) = eff { let! id = GuidGenerator.genGuid () let! now = Time.utcNow () do! Orders.save { order with Id = id; Placed = now } let! customer = Customers.load order.CustomerId and! stock = Stock.reserve order.Lines return id } </code></example>
type CancellationToken = new: canceled: bool -> unit member Equals: other: obj -> bool + 1 overload member GetHashCode: unit -> int member Register: callback: Action -> CancellationTokenRegistration + 4 overloads member ThrowIfCancellationRequested: unit -> unit member UnsafeRegister: callback: Action<obj,CancellationToken> * state: obj -> CancellationTokenRegistration + 1 overload static member (<>) : left: CancellationToken * right: CancellationToken -> bool static member (=) : left: CancellationToken * right: CancellationToken -> bool member CanBeCanceled: bool member IsCancellationRequested: bool ...
<summary>Propagates notification that operations should be canceled.</summary>
--------------------
CancellationToken ()
CancellationToken(canceled: bool) : CancellationToken
<summary> Takes a base delay and an effect, and returns an effect with a delay added at the end, if the provided effect returned false. If the effect is re-run, the delay is increased each time the original effect returns false. If the effect returns true, no delay is added, and the calculated delay is reset to 0. The delay till will not be longer than <paramref name="maxDelay" /> Then the bool is discarded and unit is returned. </summary>
<param name="baseDelay"></param>
<param name="maxDelay"></param>
<param name="effect"></param>
<summary> Perpetually returns the provided effect, in case of error </summary>
<param name="effect"></param>
val repeatUntilCancellation: token: CancellationToken -> effect: Effect<'a,unit,'b> -> Effect<'a,Never,'b>
<summary> Re-runs the provided effect until the provided token signals cancellation. If the provided effect fails, it returns with an error. </summary>
<param name="token"></param>
<param name="effect"></param>
--------------------
val repeatUntilCancellation: token: CancellationToken -> e: Effect<'a,unit,'b> -> Effect<'a,unit,'b>
<summary> Runs the effect repeatedly until <paramref name="token" /> is cancelled, which is checked before every run. It stops at the first failure. </summary>
<param name="token">The token that stops the repetition</param>
<param name="e">The effect</param>
<example><code lang="fsharp"> processNextMessage () |> Effect.repeatUntilCancellation stoppingToken </code></example>
namespace System.Collections
--------------------
namespace Microsoft.FSharp.Collections
type ConcurrentQueue<'T> = interface IProducerConsumerCollection<'T> interface seq<'T> interface IEnumerable interface ICollection interface IReadOnlyCollection<'T> new: unit -> unit + 1 overload member Clear: unit -> unit member CopyTo: array: 'T array * index: int -> unit member Enqueue: item: 'T -> unit member GetEnumerator: unit -> IEnumerator<'T> ...
<summary>Represents a thread-safe first in-first out (FIFO) collection.</summary>
<typeparam name="T">The type of the elements contained in the queue.</typeparam>
--------------------
Collections.Concurrent.ConcurrentQueue() : Collections.Concurrent.ConcurrentQueue<'T>
Collections.Concurrent.ConcurrentQueue(collection: 'T seq) : Collections.Concurrent.ConcurrentQueue<'T>
type QueueEnv = interface IQueueProvider new: unit -> QueueEnv
--------------------
new: unit -> QueueEnv
type CancellationTokenSource = interface IDisposable new: unit -> unit + 3 overloads member Cancel: unit -> unit + 1 overload member CancelAfter: millisecondsDelay: int -> unit + 1 overload member CancelAsync: unit -> Task member Dispose: unit -> unit member TryReset: unit -> bool static member CreateLinkedTokenSource: token: CancellationToken -> CancellationTokenSource + 3 overloads member IsCancellationRequested: bool member Token: CancellationToken
<summary>Signals to a <see cref="T:System.Threading.CancellationToken" /> that it should be canceled.</summary>
--------------------
CancellationTokenSource() : CancellationTokenSource
CancellationTokenSource(millisecondsDelay: int) : CancellationTokenSource
CancellationTokenSource(delay: TimeSpan) : CancellationTokenSource
CancellationTokenSource(delay: TimeSpan, timeProvider: TimeProvider) : CancellationTokenSource
TimeSpan.FromSeconds(value: float) : TimeSpan
TimeSpan.FromSeconds(seconds: int64, ?milliseconds: int64, ?microseconds: int64) : TimeSpan
<summary>Gets the <see cref="T:System.Threading.CancellationToken" /> associated with this <see cref="T:System.Threading.CancellationTokenSource" />.</summary>
<exception cref="T:System.ObjectDisposedException">The token source has been disposed.</exception>
<returns>The <see cref="T:System.Threading.CancellationToken" /> associated with this <see cref="T:System.Threading.CancellationTokenSource" />.</returns>
<summary> Active pattern for an effect that should run as long as the application is running. </summary>
<param name="_result">The result, which can only be reached if the effect ended through an exception</param>
Orsak