Funkcje asynchroniczne i zadania

Język FSharp.Core 11 dodaje funkcje międzyoperacyjności zadań, przekształceń wyników i powiązanej współbieżności. Używaj ich z istniejącymi przepływami pracy asynchronicznych i wyrażeniami zadań; nie wymagają prototypu asynchronicznego środowiska uruchomieniowego.

Oczekiwanie na operacje .NET z poziomu asynchronicznego

Aby rozpocząć operację .NET z tokenem anulowania przepływu pracy asynchronicznego, przekaż fabrykę do :Async.StartTaskImmediate

open System.IO

let tryReadFile path =
    async {
        try
            let! contents =
                Async.StartTaskImmediate(fun ct -> File.ReadAllTextAsync(path, ct))
            return Some contents
        with :? FileNotFoundException ->
            return None
    }

Odczyt pliku odbiera token anulowania przepływu pracy. Brakujący plik zwraca wartość None; pomyślny odczyt zwraca wartość Some contents. Przepływ pracy czeka na ukończenie, w tym anulowanie i oczyszczanie.

Użyj Async.Await polecenia , jeśli masz już wartość podobną do zadania. Oba interfejsy API propagują pojedynczy podstawowy wyjątek bezpośrednio uszkodzonego zadania. Agregacja z wieloma wyjątkami wewnętrznymi pozostaje agregacja. Async.AwaitTask zachowuje istniejące zachowanie opakowujące wyjątki.

API Obsługiwane kształty argumentów/wyników
Async.Await Task<'T> -> Async<'T>; Task -> Async<unit>
Async.Await ValueTask<'T> -> Async<'T>; ValueTask -> Async<unit>
Async.Await Wbudowane przeciążenie przypominające zadanie przy użyciu GetAwaiter protokołu.
Async.StartTaskImmediate (CancellationToken -> Task<'T>) -> Async<'T>; funkcja niegeneryczna Task zwraca wartość Async<unit>.
Async.StartTaskImmediate Odpowiadające ValueTaskprzeciążenia fabryki , , ValueTask<'T>i podobne do zadań.
Task.startAsyncImmediate CancellationToken -> Async<'T> -> Task<'T>
Async.RunSynchronouslyImmediate Async<'T> * ?cancellationToken: CancellationToken -> 'T

Async.RunSynchronouslyImmediate rozpoczyna się od wątku wywołującego, w przeciwieństwie do Async.RunSynchronously wątku pierwszego planu i bloków na potrzeby ukończenia. Nie ma parametru limitu czasu. Preferuj asynchroniczną kompozycję interfejsu użytkownika i żądaj wątków.

Uwidacznianie asynchronicznego przepływu pracy jako zadania

Aby zaoferować powyższy przepływ pracy odczytu plików obiektom wywołującym opartym na zadaniach, zaakceptuj token anulowania i przekaż go za pośrednictwem polecenia Task.startAsyncImmediate:

let tryReadFileTask (path: string) (ct: CancellationToken) =
    tryReadFile path |> Task.startAsyncImmediate ct

Mostek zwraca Task<string option> i propaguje anulowanie. Rozpoczyna się na wątek wywołujący do pierwszego zawieszenia; późniejsze kontynuacje nie muszą używać tego wątku.

Powiązana współbieżność

Wartość Async<'T> opisuje odroczone prace. Zadanie zwykle reprezentuje operację, która została już uruchomiona. Aby ograniczyć użycie zasobów, opisz pracę zadania jako fabryki, a nie już uruchomione zadania:

open System.IO
open System.Threading

let readFiles (paths: seq<string>) (ct: CancellationToken) =
    paths
    |> Seq.map (fun path ->
        fun token -> File.ReadAllTextAsync(path, token))
    |> Task.parallelLimit 4 ct

Task.parallelLimit rozpoczyna się co najwyżej dodatni limit obliczeń jednocześnie i zwraca wyniki w kolejności danych wejściowych. Kolejność rozpoczęcia i kolejność ukończenia są w przeciwnym razie nieokreślone. W przypadku współbieżności większej niż jednej każda fabryka otrzymuje połączony token, który również sygnalizuje awarię elementu równorzędnego; w przypadku wykonywania sekwencyjnego otrzymuje podany token bezpośrednio.

W przypadku wystąpienia jednego błędu obliczeniowego żądanie anulowania jest wymagane dla jego elementów równorzędnych. Operacje muszą obserwować swoje tokeny do współpracy. Jeśli kilka obliczeń nie powiedzie się, jeden wyjątek jest propagowany; nie zależy od tego, która awaria zostanie wygrana.

Function Kształt podpisu
Task.parallelLimit int -> CancellationToken -> seq<CancellationToken -> Task<'T>> -> Task<'T[]>
Task.parallelDoLimit int -> CancellationToken -> seq<CancellationToken -> Task<unit>> -> Task<unit>
Task.sequential CancellationToken -> seq<CancellationToken -> Task<'T>> -> Task<'T[]>
Task.sequentialDo CancellationToken -> seq<CancellationToken -> Task<unit>> -> Task<unit>
Async.parallelLimit int -> seq<Async<'T>> -> Async<'T[]>
Async.parallelDoLimit int -> seq<Async<unit>> -> Async<unit>
Async.sequentialDo seq<Async<unit>> -> Async<unit>

Funkcje Async przepływają token anulowania otoczenia przepływu pracy. Istniejące Async.Parallel (w tym opcja limitu współbieżności) i Async.Sequential pozostają dostępne. Warianty Do uwidaczniają unit wynik tylko wtedy, gdy potrzebujesz tylko ukończenia. Limit współbieżności nie jest ograniczeniem szybkości: szybkie operacje mogą nadal generować wiele żądań na sekundę.

Przekształcanie wyników i odzyskiwanie po błędach

Moduły Microsoft.FSharp.Control.Async, Taski ValueTask uwidaczniają te kwalifikowane funkcje. M<'T> w kolumnie kształtu oznacza odpowiedni typ obliczeniowy, a nie skrót typu F#.

Kwalifikowane funkcje Shape
Async.result, Task.result, ValueTask.result 'T -> M<'T>
Async.map, Task.map, ValueTask.map ('T -> 'U) -> M<'T> -> M<'U>
Async.bind, Task.bind, ValueTask.bind ('T -> M<'U>) -> M<'T> -> M<'U>
Async.ignore<'T>, Task.ignore<'T>, ValueTask.ignore<'T> M<'T> -> M<unit>; wymagany jest jawny argument typu wejściowego.
Async.catchWith, Task.catchWith, ValueTask.catchWith (exn -> 'T) -> M<'T> -> M<'T>
Async.catch, Task.catch, ValueTask.catch M<'T> -> M<Result<'T, exn>>
Async.empty, Task.empty, ValueTask.empty M<unit>
Task.ofValueTask ValueTask<'T> -> Task<'T>
ValueTask.ofTask Task<'T> -> ValueTask<'T>

Na przykład zamapuj zawartość pliku na długość, a następnie reprezentuje błąd odczytu jako :Result

open System.IO
open System.Threading

let fileLength (path: string) (ct: CancellationToken) =
    File.ReadAllTextAsync(path, ct)
    |> Task.map String.length
    |> Task.catch

Plik zawierający Report 1 polecenie daje Ok 8. Brakujący plik zawiera element ErrorFileNotFoundException. Aby odrzucić pomyślną wartość, a nie przekształcić ją, określ typ danych wejściowych podczas wywoływania elementu ignore:

let completion: System.Threading.Tasks.Task<unit> =
    Task.result 42 |> Task.ignore<int>

catchWith dostarcza wartość zastępczą dla niepowodzenia nieuwołania; nie uruchamia innego obliczenia asynchronicznego. catch zwraca wartość Ok lub Error. Anulowanie nie jest konwertowane na wartość błędu ani przekazywane do procedury obsługi odzyskiwania. Task.empty jest elementem , a Task<unit>nie ogólnym Task.CompletedTask.

W przeciwieństwie do małych liter istniejąca Async.catchAsync.Catch zwraca wartość Choice. Odzyskiwanie obsługuje błędy dostarczonego obliczenia, a nie wyjątki synchroniczne zgłaszane przed wywołaniem. Asynchronizuj pozostaje odroczony; zadania pozostają chętne.

ValueTask i dostępność biblioteki

Interfejsy API valueTask wymagają netstandard2.1 kompilacji FSharp.Core 11 lub net10.0 . Zobacz Wybór miejsca docelowego rdzenia.

Obserwuj kontrakt ValueTask z pojedynczym użyciem: zużywaj każdą wartość raz, a nie przez oddzielne oczekiwania lub potoki. W przypadku powtórzeń czekaj wywołaj Task.ofValueTask raz i ponownie użyj tylko wynikowego zadania.

Informacje dodatkowe