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çinasync { }başlatılabilirbir birleştirilebilir zaman uyumsuz hesaplamayı temsil eden hesaplama ifadesine sahip tür. - Yürütülen
Task<'T>birtask { }.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:
- komut satırı bağımsız değişkenlerini ile bir hesaplama
Async<unit>dizisineSeq.mapdönüştürebilirsiniz. - Hesaplamaları
Async<'T[]>çalıştırarak paralel olarakprintTotalFileByteszamanlandıran ve çalıştıran bir oluşturun. - Paralel
Async<unit>hesaplamayı çalıştıracak ve sonucu yoksayacak (yani bir ) birunit[]oluşturun. - Genel olarak oluşan hesaplamayı ile açıkça çalıştırın
Async.RunSynchronouslyve 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.StartChildile başlatma, bunları paralel olarak zamanlamayla aynı değildir. Hesaplamaları paralel olarak zamanlamayı istersenizAsync.Parallelkullanı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.StartImmediateuygun 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
Taskkullanı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
ignorekod için işlevine benzer.
Dikkat gerekenler:
- kullanmak istediğiniz için
Async.Ignoreveya gerektiren başka bir işlevi kullanmak zorundaysanız,Async.StartsonucuAsync<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.RunSynchronouslyyü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.Startkonan ö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
printfnAsync.Startiş 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:
- 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.
- 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.