F'de zaman uyumsuz programlama#

Zaman uyumsuz programlama, çeşitli nedenlerle modern uygulamalar için temel öneme sahip olan bir mekanizmadır. Çoğu geliştiricinin karşılaşacakları iki temel kullanım örnekleri vardır:

  • Önemli sayıda eşzamanlı gelen item için hizmet sunarak istek işleme sırasında kapladığı sistem kaynaklarını en aza indirerek bu işlem dışından sistemlerden veya hizmetlerden gelen girişleri bekleyen bir sunucu işlemi sunmak
  • Arka plan çalışmalarını eşzamanlı olarak ilerlerken yanıt veren bir kullanıcı arabirimini veya ana iş parçacığını koruma

Arka plan çalışmaları genellikle birden çok iş parçacığının kullanımını içeriyor olsa da, zaman uyumsuz ve çoklu iş parçacığı kavramlarını ayrı olarak göz önünde bulundurabilirsiniz. Aslında bunlar ayrı endişelerdir ve biri diğeri ifade de değildir. Bu makalede ayrı kavramlar daha ayrıntılı olarak açıklanmaktadır.

Zaman uyumsuz tanımlandı

Önceki nokta ( zaman uyumsuzluğun birden çok iş parçacığının kullanımından bağımsızdır) biraz daha açıklamaya değer. Bazen ilişkili olan ancak birbirinden tamamen bağımsız olan üç kavram vardır:

  • Eşzamanlılık; çakışan zaman aralıklarında birden çok hesaplama yürütülür.
  • Paralellik; birden çok hesaplama veya tek bir hesaplamanın birden çok parçası tam olarak aynı anda çalıştırılamalı.
  • Zaman uyumsuz; ana program akışından ayrı olarak bir veya daha fazla hesaplama yürütülebitir.

Üçü de dik kavramlardır ancak özellikle birlikte kullanıldıklarında kolayca şişirilebilir. Örneğin, birden çok zaman uyumsuz hesaplamayı paralel olarak yürütmeye ihtiyacınız olabilir. Bu ilişki, paralellik veya zaman uyumsuzluğun birbirine yönelik olduğu anlamına değildir.

"Zaman uyumsuz" sözcüğün etymolojisi göz önünde bulunduracak olursanız iki parça vardır:

  • "a", "not" anlamına gelir.
  • "zaman uyumlu", yani "aynı anda".

Bu iki terim bir araya geldiğinde "zaman uyumsuz" ifadesinin "aynı anda değil" anlamına geldiğini görüyorsunuz. İşte bu kadar! Bu tanımda eşzamanlılık veya paralellik üzerinde bir çıkarım yoktur. Bu, uygulamada da doğrudur.

Pratikte, F# içinde zaman uyumsuz hesaplamalar ana program akışından bağımsız olarak yürütülecek şekilde zamanlanmış. Bu bağımsız yürütme eşzamanlılık veya paralellik değil, ayrıca bir hesaplamanın her zaman arka planda olduğuna da bağlı değildir. Aslında zaman uyumsuz hesaplamalar, hesaplamanın doğasına ve hesaplamanın yürütülmektedir olduğu ortama bağlı olarak zaman uyumlu olarak bile yürütülür.

Burada önemli olan, zaman uyumsuz hesaplamaların ana program akışından bağımsız olmasıdır. Zaman uyumsuz hesaplamanın ne zaman veya nasıl yürütül olduğu konusunda birkaç garanti olsa da, bunları düzenlemeye ve zamanlamaya yönelik bazı yaklaşımlar vardır. Bu makalenin geri kalanında F# zaman uyumsuzluğuyla ilgili temel kavramlar ve F# içinde yerleşik türler, işlevler ve ifadelerin kullanımı inceleniyor.

Temel kavramlar

F# dilinde, zaman uyumsuz programlama iki temel kavram etrafında ortalanmıştır: zaman uyumsuz hesaplamalar ve görevler.

  • Bir Async<'T> görevi oluşturmak için async { } başlatılabilirbir birleştirilebilir zaman uyumsuz hesaplamayı temsil eden hesaplama ifadesine sahip tür.
  • Yürütülen Task<'T> bir task { } .NET görevini temsileden hesaplama ifadesi ile türü.

Genel olarak, sık sık async { } .NET görevleri oluşturmanız veya tüketmeniz gerekmedikçe F# dilinde programlama kullansanız iyi olur.

Zaman uyumsuz için temel kavramlar

Aşağıdaki örnekte "zaman uyumsuz" programlamanın temel kavramlarını görüyorsunuz:

open System
open System.IO

// Perform an asynchronous read of a file using 'async'
let printTotalFileBytesUsingAsync (path: string) =
    async {
        let! bytes = File.ReadAllBytesAsync(path) |> Async.AwaitTask
        let fileName = Path.GetFileName(path)
        printfn $"File {fileName} has %d{bytes.Length} bytes"
    }

[<EntryPoint>]
let main argv =
    printTotalFileBytesUsingAsync "path-to-file.txt"
    |> Async.RunSynchronously

    Console.Read() |> ignore
    0

Örnekte işlevi printTotalFileBytesUsingAsync string -> Async<unit> türündedir. işlevini çağırma işlemi aslında zaman uyumsuz hesaplamayı yürütmez. Bunun yerine, Async<unit> zaman uyumsuz olarak yürütülecek bir iş belirtimi olarak hareket bir döndürür. Gövdesinde Async.AwaitTask çağrısı yapılan bu çağrı, sonucu uygun bir ReadAllBytesAsync türe dönüştürür.

Bir diğer önemli satır da Async.RunSynchronously çağrısıdır. Bu, gerçekten bir F# zaman uyumsuz hesaplaması yürütmek için çağıracakları zaman uyumsuz modül başlatma işlevlerinden biri.

Bu, C#/Visual Basic temel bir async farktır. F# ile zaman uyumsuz hesaplamalar, Soğuk görevler olarak düşünebilirsiniz. Yürütülmaya açıkça başlanları gerekir. Bunun bazı avantajları vardır çünkü zaman uyumsuz işi C# veya diğer çalışmalara göre çok daha kolay bir şekilde birleştirmenizi Visual Basic.

Zaman uyumsuz hesaplamaları birleştirme

Hesaplamaları birleştirerek öncekini temel alan bir örnek:

open System
open System.IO

let printTotalFileBytes path =
    async {
        let! bytes = File.ReadAllBytesAsync(path) |> Async.AwaitTask
        let fileName = Path.GetFileName(path)
        printfn $"File {fileName} has %d{bytes.Length} bytes"
    }

[<EntryPoint>]
let main argv =
    argv
    |> Seq.map printTotalFileBytes
    |> Async.Parallel
    |> Async.Ignore
    |> Async.RunSynchronously

    0

Gördüğünüz gibi işlevin main birkaç öğe daha vardır. Kavramsal olarak şunları yapar:

  1. komut satırı bağımsız değişkenlerini ile bir hesaplama Async<unit> dizisine Seq.map dönüştürebilirsiniz.
  2. Hesaplamaları Async<'T[]> çalıştırarak paralel olarak printTotalFileBytes zamanlandıran ve çalıştıran bir oluşturun.
  3. Paralel Async<unit> hesaplamayı çalıştıracak ve sonucu yoksayacak (yani bir ) bir unit[] oluşturun.
  4. Genel olarak oluşan hesaplamayı ile açıkça çalıştırın Async.RunSynchronously ve tamamlayana kadar engellemeyi engelin.

Bu program çalıştır geldiğinde, printTotalFileBytes her komut satırı bağımsız değişkeni için paralel olarak çalışır. Zaman uyumsuz hesaplamalar program akışında bağımsız olarak yürütülür, bu hesaplamaların bilgilerini yazdıracakları ve yürütmeyi bitirecekleri tanımlı bir sıra yoktur. Hesaplamalar paralel olarak zamanlanmış olur, ancak bunların yürütme sırası garanti edilemez.

Zaman uyumsuz hesaplamaları sırala

Zaten Async<'T> çalışan bir görev yerine iş belirtimi olduğundan, daha karmaşık dönüştürmeleri kolayca gerçekleştirebilirsiniz. Burada, bir dizi Zaman uyumsuz hesaplamayı sırayla yürüten bir örnek ve ardından ve ardından yürütülür.

let printTotalFileBytes path =
    async {
        let! bytes = File.ReadAllBytesAsync(path) |> Async.AwaitTask
        let fileName = Path.GetFileName(path)
        printfn $"File {fileName} has %d{bytes.Length} bytes"
    }

[<EntryPoint>]
let main argv =
    argv
    |> Seq.map printTotalFileBytes
    |> Async.Sequential
    |> Async.Ignore
    |> Async.RunSynchronously
    |> ignore

Bu, printTotalFileBytes öğelerini paralel olarak zamanlamak yerine öğelerini argv sırasıyla yürütmeyi zamanlar. Sonraki her işlem, önceki hesaplamanın yürütülmesi tamamlanana kadar zamanlanmaz. Hesaplamalar, yürütmelerinde çakışma olmayacak şekilde sıralanmış olur.

Önemli Async modülü işlevleri

F# içinde zaman uyumsuz kod yazarak genellikle hesaplamaların zamanlamasını sizin için ele alan bir çerçeveyle etkileşime geçmeniz gerekir. Ancak, bu her zaman böyle değildir, bu nedenle zaman uyumsuz çalışma zamanlaması için kullanılan çeşitli işlevleri anlamak iyi bir fikirdir.

F# zaman uyumsuz hesaplamaları zaten yürüten bir iş gösterimi yerine bir iş belirtimi olduğundan, açıkça bir başlangıç işleviyle başlamaları gerekir. Farklı bağlamlarda yararlı olan birçok Async başlangıç yöntemi vardır. Aşağıdaki bölümde, daha yaygın başlangıç işlevlerinin bazıları açıkmektedir.

Async.StartChild

Zaman uyumsuz hesaplama içinde bir alt hesaplama başlatır. Bu, birden çok zaman uyumsuz hesaplamanın eşzamanlı olarak yürütülene izin verir. Alt hesaplama, üst hesaplama ile bir iptal belirteci paylaşıyor. Üst hesaplama iptal edilirse alt hesaplama da iptal edilir.

İmza:

computation: Async<'T> * ?millisecondsTimeout: int -> Async<Async<'T>>

Kullanım zamanları:

  • Bir defada bir yerine eşzamanlı olarak birden çok zaman uyumsuz hesaplama yürütmek, ancak paralel olarak zamanlanmış olmayan birden çok hesaplama yürütmek istediğiniz zaman.
  • Bir alt hesaplamanın ömrünü bir üst hesaplamanınkiyle bağlamak istediğiniz zaman.

Dikkat gerekenler:

  • birden çok hesaplamayı Async.StartChild ile başlatma, bunları paralel olarak zamanlamayla aynı değildir. Hesaplamaları paralel olarak zamanlamayı isterseniz Async.Parallel kullanın.
  • Üst hesaplamanın iptal edilmesi, başlatan tüm alt hesaplamaların iptalini tetikler.

Async.StartImmediate

Geçerli işletim sistemi iş parçacığında hemen başlayarak zaman uyumsuz bir hesaplama çalıştırır. Hesaplama sırasında çağıran iş parçacığında bir şeyi güncelleştirmeniz gerekirse bu yararlı olur. Örneğin, zaman uyumsuz hesaplamanın bir kullanıcı arabirimini güncelleştirmesi gerekirse (örneğin, ilerleme çubuğunu güncelleştirme) Async.StartImmediate kullanılmalıdır.

İmza:

computation: Async<unit> * ?cancellationToken: CancellationToken -> unit

Kullanım zamanları:

  • Zaman uyumsuz hesaplamanın ortasında çağıran iş parçacığında bir şeyi güncelleştirmeniz gereken zaman.

Dikkat gerekenler:

  • Zaman uyumsuz hesaplamada yer alan kod, zamanlanan iş parçacığında çalıştıracak. Bu iş parçacığı, kullanıcı arabirimi iş parçacığı gibi bir şekilde hassassa sorun yaratabilir. Bu gibi durumlarda kullanmak Async.StartImmediate uygun değildir.

Async.StartAsTask

İş parçacığı havuzunda bir hesaplama yürütür. Hesaplama sonlandıktan sonra karşılık gelen durumda tamamlanacak bir döndürür (sonucu üretir, özel durum oluşturur Task<TResult> veya iptal edilir). İptal belirteci sağlanamıyorsa varsayılan iptal belirteci kullanılır.

İmza:

computation: Async<'T> * ?taskCreationOptions: TaskCreationOptions * ?cancellationToken: CancellationToken -> Task<'T>

Kullanım zamanları:

  • Zaman uyumsuz hesaplamanın sonucunda temsil eden bir .NET API'sine Task<TResult> çağrı yapmak zorundayken.

Dikkat gerekenler:

  • Bu çağrı, sık Task kullanılırsa ek yük artıran ek bir nesne ayırır.

Async.Parallel

Paralel olarak yürütülecek zaman uyumsuz hesaplama dizisini zamanlar ve bu da sağlandık sırayla sonuç dizisi verir. Paralellik derecesi, parametresi belirterek isteğe bağlı olarak ayarlanmış/kısıtsız maxDegreeOfParallelism olabilir.

İmza:

computations: seq<Async<'T>> * ?maxDegreeOfParallelism: int -> Async<'T[]>

Ne zaman kullan?

  • Aynı anda bir dizi hesaplama çalıştırmaya ihtiyacınız varsa ve bunların yürütme sırasına bağlı değilsanız.
  • Hepsi tamamlanana kadar paralel olarak zamanlanan hesaplamalardan sonuç almak zorunda değilsiniz.

Dikkat gerekenler:

  • Sonuçta elde edilen değer dizisine yalnızca tüm hesaplamalar tamamlandığında erişebilirsiniz.
  • Hesaplamalar zamanlanan her zaman çalıştırılır. Bu davranış, yürütme sırasına güvenilene anlamına gelir.

Async.Sequential

Geçirildikleri sırayla yürütülecek zaman uyumsuz hesaplama dizisini zamanlar. İlk hesaplama yürütülür, sonra sonraki işlem yürütülür ve bu şekilde devam edilecektir. Paralel olarak hiçbir hesaplama yürütülmez.

İmza:

computations: seq<Async<'T>> -> Async<'T[]>

Ne zaman kullan?

  • Birden çok hesaplamayı sırayla yürütmeniz gerekirse.

Dikkat gerekenler:

  • Sonuçta elde edilen değer dizisine yalnızca tüm hesaplamalar tamamlandığında erişebilirsiniz.
  • Hesaplamalar, bu işleve geçirildikleri sırada çalıştırılır, bu da sonuçlar döndürülmeden önce daha fazla zaman geçeceğiz anlamına geliyor olabilir.

Async.AwaitTask

Verilenin tamamlandıktan sonra elde edilen sonucu döndüren zaman uyumsuz Task<TResult> bir hesaplama döndürür Async<'T>

İmza:

task: Task<'T> -> Async<'T>

Kullanım zamanları:

  • F# zaman uyumsuz hesaplama içinde döndüren bir .NET Task<TResult> API'si tüketerek.

Dikkat gerekenler:

  • Özel durumlar Görev Paralel Kitaplığı'nın kuralına göre sarmalanmış; bu davranış F# zaman uyumsuz genel olarak özel durumları AggregateException ortaya çıkararak farklıdır.

Async.Catch

Verilen bir 'i yürüten ve döndüren zaman uyumsuz Async<'T> bir hesaplama Async<Choice<'T, exn>> oluşturur. Verilen Async<'T> başarıyla tamamlanırsa, Choice1Of2 sonuç değeriyle bir döndürülür. Bir özel durum tamamlandıktan önce sızıyorsa, bir Choice2of2 yükseltilmiş özel durum ile döndürülür. Kendisi birçok hesaplamadan oluşan bir zaman uyumsuz hesaplamada kullanılıyorsa ve bu hesaplamalardan biri özel durum oluşturursa, kapsayan hesaplama tamamen durdurulur.

İmza:

computation: Async<'T> -> Async<Choice<'T, exn>>

Kullanım zamanları:

  • Bir özel durumla başarısız olan zaman uyumsuz işler gerçekleştirerek bu özel durumu çağıranın içinde işlemek istediğiniz durumlar.

Dikkat gerekenler:

  • Birleştirilmiş veya sıralı zaman uyumsuz hesaplamalar kullanırken, "iç" hesaplamalarından biri özel durum oluşturursa kapsayan hesaplama tamamen durur.

Async.Ignore

Verilen hesaplamayı çalıştıran ancak sonucu düşüren zaman uyumsuz bir hesaplama oluşturur.

İmza:

computation: Async<'T> -> Async<unit>

Kullanım zamanları:

  • Sonucu gerekli olmayan bir zaman uyumsuz hesaplamaya sahip olduğunda. Bu, zaman uyumsuz ignore kod için işlevine benzer.

Dikkat gerekenler:

  • kullanmak istediğiniz için Async.Ignore veya gerektiren başka bir işlevi kullanmak zorundaysanız, Async.Start sonucu Async<unit> atma sorun değil. Yalnızca bir tür imzasını sığdırmak için sonuçları atmaktan kaçının.

Async.RunSynchronously

Zaman uyumsuz hesaplama çalıştırır ve çağıran iş parçacığında sonucu bekler. Hesaplama bir özel durum oluşturur. Bu çağrı engelliyor.

İmza:

computation: Async<'T> * ?timeout: int * ?cancellationToken: CancellationToken -> 'T

Ne zaman kullan?

  • Buna ihtiyacınız varsa, bir yürütülebilir dosyanın giriş noktasında, bir uygulamada yalnızca bir kez kullanın.
  • Performansı önemser ve bir dizi diğer zaman uyumsuz işlemleri aynı anda yürütmek istediğiniz zaman.

Dikkat gerekenler:

  • Çağırma, Async.RunSynchronously yürütme tamamlayana kadar çağıran iş parçacığını engeller.

Async.Start

İş parçacığı havuzunda döndüren zaman uyumsuz unit bir hesaplama başlatır. Tamamlanmasını beklemez ve/veya bir özel durum sonucunu gözlemler. ile başlayan iç içe geçmiş hesaplamalar, onları çağıran üst hesaplamadan bağımsız olarak başlatılabilir; yaşamları Async.Start hiçbir üst hesaplamaya bağlı değildir. Üst hesaplama iptal edilirse, hiçbir alt hesaplama iptal edilir.

İmza:

computation: Async<unit> * ?cancellationToken: CancellationToken -> unit

Yalnızca aşağıdakiler olduğunda kullanın:

  • Sonuç vermeyen ve/veya işleme gerektiren zaman uyumsuz bir hesaplamaya sahipsiniz.
  • Zaman uyumsuz hesaplamanın ne zaman tamamlandıktan sonra bunu bilmek zorunda değildir.
  • Zaman uyumsuz hesaplamanın hangi iş parçacığı üzerinde çalıştır olduğu önemli değildir.
  • Yürütmenin sonucunda ortaya çıkan özel durumları fark etme veya bildirmeye gerek yok.

Dikkat gerekenler:

  • ile başlayan hesaplamalar tarafından ortaya Async.Start konan özel durumlar çağırana yayılmaz. Çağrı yığını tamamen kaldırılmış olur.
  • ile başlayan herhangi bir iş (örneğin, çağrısı) bir programın yürütülmesinin ana printfn Async.Start iş parçacığı üzerinde etkisine neden olmaz.

.NET ile birlikte çalışma

Programlama async { } kullanıyorsanız, async/await-style zaman uyumsuz programlama kullanan bir .NET kitaplığı veya C# kod tabanı ile birlikte çalışmanız gerekir. C# ve .NET kitaplıklarının çoğu temel soyutlamaları olarak ve türlerini kullanır. Bu, F# zaman uyumsuz kodunuzu Task<TResult> Task yazmanızı değiştirebilir.

Seçeneklerden biri kullanarak doğrudan .NET görevleri yazmaya task { } geçmektir. Alternatif olarak, işlevini kullanarak Async.AwaitTask .NET zaman uyumsuz hesaplamayı beklersiniz:

let getValueFromLibrary param =
    async {
        let! value = DotNetLibrary.GetValueAsync param |> Async.AwaitTask
        return value
    }

Bir Async.StartAsTask .NET çağıranı için zaman uyumsuz hesaplamayı geçmek için işlevini kullanabilirsiniz:

let computationForCaller param =
    async {
        let! result = getAsyncResult param
        return result
    } |> Async.StartAsTask

kullanan API'lerle çalışmak için (başka bir ifadeyi kullanmayan .NET zaman uyumsuz hesaplamalar) için, bir 'i değerine dönüştürecek ek bir Task işlev eklemeniz Async<'T> Task gerekir:

module Async =
    // Async<unit> -> Task
    let startTaskFromAsyncUnit (comp: Async<unit>) =
        Async.StartAsTask comp :> Task

Giriş olarak kabul Async.AwaitTask eden bir Task zaten vardır. Bu ve daha önce tanımlanan işlevle, türleri F# zaman uyumsuz startTaskFromAsyncUnit Task hesaplamadan başlatarak beklersiniz.

.NET görevlerini doğrudan F'de yazma#

F# ile görevleri doğrudan kullanarak task { } yazabilirsiniz, örneğin:

open System
open System.IO

/// Perform an asynchronous read of a file using 'task'
let printTotalFileBytesUsingTasks (path: string) =
    task {
        let! bytes = File.ReadAllBytesAsync(path)
        let fileName = Path.GetFileName(path)
        printfn $"File {fileName} has %d{bytes.Length} bytes"
    }

[<EntryPoint>]
let main argv =
    let task = printTotalFileBytesUsingTasks "path-to-file.txt"
    task.Wait()

    Console.Read() |> ignore
    0

Örnekte işlevi printTotalFileBytesUsingTasks string -> Task<unit> türündedir. İşlevi çağırma görevi yürütmeye başlar. çağrısı, task.Wait() görevin tamamlandıktan sonra tamamlanır.

Çoklu iş parçacığı ile ilişki

Bu makale boyunca iş parçacığına değiniliyor olsa da, anımsanacak iki önemli şey vardır:

  1. Geçerli iş parçacığında açıkça başlatılamadıkça, zaman uyumsuz hesaplama ile iş parçacığı arasında benzeşm yoktur.
  2. F# dilinde zaman uyumsuz programlama, çoklu iş parçacığı için bir soyutlama değildir.

Örneğin, bir hesaplama aslında iş doğasına bağlı olarak çağıranın iş parçacığı üzerinde çalışır. Hesaplama, "bekleme" dönemleri (örneğin, bir ağ çağrısının geçişte olduğu dönemler) arasında yararlı iş yapmak için bunları kısa bir süre ödünç alıp iş parçacıkları arasında "atlar" da olabilir.

F# geçerli iş parçacığında (veya açıkça geçerli iş parçacığında değil) zaman uyumsuz hesaplama başlatmaya yönelik bazı özellikler sağlar ancak zaman uyumsuzluğun genellikle belirli bir iş parçacığı stratejisiyle ilişkilendirilmaması gerekir.

Ayrıca bkz.