Wyrażenia asynchroniczne

W tym artykule opisano obsługę języka F# dla wyrażeń asynchronicznych. Wyrażenia asynchroniczne zapewniają jeden ze sposobów asynchronicznego wykonywania obliczeń, czyli bez blokowania wykonywania innych zadań. Na przykład obliczenia asynchroniczne mogą służyć do pisania aplikacji, które mają interfejsy użytkownika, które pozostają dynamiczne dla użytkowników, gdy aplikacja wykonuje inną pracę. Model programowania Asynchroniczne przepływy pracy języka F# umożliwia pisanie programów funkcjonalnych przy jednoczesnym ukrywaniu szczegółów przejścia wątku w bibliotece.

Kod asynchroniczny można również tworzyć przy użyciu wyrażeń zadań, które bezpośrednio tworzą zadania platformy .NET. Używanie wyrażeń zadań jest preferowane w przypadku współpracy w szerokim zakresie z bibliotekami platformy .NET, które tworzą lub używają zadań platformy .NET. Podczas pisania większości kodu asynchronicznego w języku F# preferowane są wyrażenia asynchroniczne, ponieważ są bardziej zwięzłe, bardziej komponacyjne i unikaj pewnych zastrzeżeń związanych z zadaniami platformy .NET.

Składnia

async { expression }

Uwagi

W poprzedniej składni obliczenia reprezentowane przez expression program są konfigurowane do uruchamiania asynchronicznego, czyli bez blokowania bieżącego wątku obliczeniowego, gdy są wykonywane operacje asynchronicznego uśpienia, we/wy i inne operacje asynchroniczne. Obliczenia asynchroniczne są często uruchamiane w wątku w tle, podczas gdy wykonywanie jest kontynuowane w bieżącym wątku. Typ wyrażenia to Async<'T>, gdzie 'T jest typem zwracanym przez wyrażenie, gdy return słowo kluczowe jest używane.

Klasa Async udostępnia metody, które obsługują kilka scenariuszy. Ogólne podejście polega na tworzeniu Async obiektów reprezentujących obliczenia lub obliczenia, które mają być uruchamiane asynchronicznie, a następnie uruchamiać te obliczenia przy użyciu jednej z funkcji wyzwalających. Wyzwalanie, którego używasz, zależy od tego, czy chcesz użyć bieżącego wątku, wątku w tle, czy obiektu zadania platformy .NET. Aby na przykład uruchomić obliczenia asynchroniczne w bieżącym wątku, możesz użyć polecenia Async.StartImmediate. Po uruchomieniu obliczeń asynchronicznych z wątku interfejsu użytkownika nie należy blokować głównej pętli zdarzeń, która przetwarza akcje użytkownika, takie jak naciśnięcia klawiszy i działanie myszy, dzięki czemu aplikacja pozostaje elastyczna.

Powiązanie asynchroniczne przy użyciu let!

W wyrażeniu asynchronicznym niektóre wyrażenia i operacje są synchroniczne, a niektóre są asynchroniczne. W przypadku asynchronicznego wywoływania metody zamiast zwykłego let powiązania należy użyć metody let!. Efektem let! jest umożliwienie wykonywania w celu kontynuowania wykonywania na innych obliczeniach lub wątkach podczas wykonywania obliczeń. Po prawej stronie let! powiązania zostanie wznowione wykonywanie pozostałej części wyrażenia asynchronicznego.

Poniższy kod przedstawia różnicę między let i let!. Wiersz kodu, który używa let tylko tworzy asynchroniczne obliczenia jako obiekt, który można uruchomić później, używając na przykład Async.StartImmediate lub Async.RunSynchronously. Wiersz kodu, który używa let! , uruchamia obliczenia i wykonuje asynchroniczne oczekiwanie: wątek jest zawieszony, aż wynik będzie dostępny, w którym momencie wykonywanie będzie kontynuowane.

// let just stores the result as an asynchronous operation.
let (result1 : Async<byte[]>) = stream.AsyncRead(bufferSize)
// let! completes the asynchronous operation and returns the data.
let! (result2 : byte[])  = stream.AsyncRead(bufferSize)

let! Można użyć tylko do bezpośredniego oczekiwania na obliczenia asynchroniczne Async<T> języka F#. Możesz oczekiwać na inne rodzaje operacji asynchronicznych pośrednio:

  • Zadania Task<TResult> platformy .NET i inne niż ogólne Task, łącząc się z Async.AwaitTask
  • Zadania ValueTask<TResult> wartości platformy .NET i nieogólne ValueTask, łączące się z elementami .AsTask() i Async.AwaitTask
  • Każdy obiekt zgodnie ze wzorcem "GetAwaiter" określony w F# RFC FS-1097, łącząc z task { return! expr } |> Async.AwaitTask.

Przepływ sterowania

Wyrażenia asynchroniczne mogą obejmować konstrukcje przepływu sterowania, takie jak for .. in .. do, , while .. dotry .. with .., try .. finally .., , if .. then .. elsei if .. then ... Mogą one z kolei obejmować kolejne konstrukcje asynchroniczne, z wyjątkiem with procedur obsługi i finally , które są wykonywane synchronicznie.

Wyrażenia asynchroniczne języka F# nie obsługują asynchronicznych try .. finally ... W tym przypadku można użyć wyrażenia zadania.

usepowiązania i use!

W wyrażeniach asynchronicznych use powiązania mogą wiązać się z wartościami typu IDisposable. W przypadku tego ostatniego operacja oczyszczania usuwania jest wykonywana asynchronicznie.

Oprócz let!programu można używać use! do wykonywania powiązań asynchronicznych. Różnica między elementami let! i use! jest taka sama jak różnica między let i use. W przypadku use!elementu obiekt jest usuwany na zamknięciu bieżącego zakresu. Należy pamiętać, use! że w bieżącej wersji języka F#nie zezwala na zainicjowanie wartości null, mimo że use nie.

Asynchroniczne elementy pierwotne

Metoda, która wykonuje jedno zadanie asynchroniczne i zwraca wynik, jest nazywana asynchronicznym elementem pierwotnym, a są one przeznaczone specjalnie do użycia z elementem let!. W bibliotece podstawowej języka F# zdefiniowano kilka asynchronicznych elementów pierwotnych. Dwie takie metody dla aplikacji internetowych są zdefiniowane w module FSharp.Control.WebExtensions: WebRequest.AsyncGetResponse i WebClient.AsyncDownloadString. Oba elementy pierwotne pobierają dane ze strony sieci Web, biorąc pod uwagę adres URL. AsyncGetResponse tworzy System.Net.WebResponse obiekt i AsyncDownloadString tworzy ciąg reprezentujący kod HTML dla strony sieci Web.

W module znajduje się kilka elementów pierwotnych operacji we/wy FSharp.Control.CommonExtensions asynchronicznych. Te metody System.IO.Stream rozszerzenia klasy to Stream.AsyncRead i Stream.AsyncWrite.

Możesz również napisać własne asynchroniczne elementy pierwotne, definiując funkcję lub metodę, której treść jest wyrażeniem asynchronicznym.

Aby użyć metod asynchronicznych w programie .NET Framework, które są przeznaczone dla innych modeli asynchronicznych za pomocą modelu programowania asynchronicznego języka F#, należy utworzyć funkcję zwracającą obiekt F# Async . Biblioteka języka F# zawiera funkcje, które ułatwiają wykonywanie tych czynności.

Oto przykład użycia wyrażeń asynchronicznych; w dokumentacji dla metod klasy Async znajduje się wiele innych.

W tym przykładzie pokazano, jak używać wyrażeń asynchronicznych do równoległego wykonywania kodu.

W poniższym przykładzie kodu funkcja fetchAsync pobiera tekst HTML zwrócony z żądania internetowego. Funkcja fetchAsync zawiera asynchroniczny blok kodu. Gdy powiązanie jest wykonywane w wyniku asynchronicznego elementu pierwotnego, w tym przypadku AsyncDownloadStringlet! jest używane zamiast let.

Funkcja służy Async.RunSynchronously do wykonywania operacji asynchronicznej i oczekiwania na jej wynik. Na przykład można wykonywać wiele operacji asynchronicznych równolegle przy użyciu Async.Parallel funkcji razem z funkcją Async.RunSynchronously . Funkcja Async.Parallel pobiera listę Async obiektów, konfiguruje kod dla każdego Async obiektu zadania, który ma być uruchamiany równolegle, i zwraca Async obiekt reprezentujący obliczenia równoległe. Podobnie jak w przypadku pojedynczej operacji wywołaj metodę Async.RunSynchronously , aby rozpocząć wykonywanie.

Funkcja runAll uruchamia trzy wyrażenia asynchroniczne równolegle i czeka na ich ukończenie.

open System.Net
open Microsoft.FSharp.Control.WebExtensions

let urlList = [ "Microsoft.com", "http://www.microsoft.com/"
                "MSDN", "http://msdn.microsoft.com/"
                "Bing", "http://www.bing.com"
              ]

let fetchAsync(name, url:string) =
    async {
        try
            let uri = new System.Uri(url)
            let webClient = new WebClient()
            let! html = webClient.AsyncDownloadString(uri)
            printfn "Read %d characters for %s" html.Length name
        with
            | ex -> printfn "%s" (ex.Message);
    }

let runAll() =
    urlList
    |> Seq.map fetchAsync
    |> Async.Parallel
    |> Async.RunSynchronously
    |> ignore

runAll()

Zobacz też