Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Zaman uyumsuz programlama, çeşitli nedenlerle modern uygulamalar için gerekli olan bir mekanizmadır. Çoğu geliştiricinin karşılaşacağı iki birincil kullanım örneği vardır:
- Çok sayıda eşzamanlı gelen isteğe hizmet verebilen bir sunucu işlemi sunmak, istek işleme sırasında kullanılan sistem kaynaklarını en aza indirirken bu işlem dışındaki sistemlerden veya hizmetlerden gelen girişleri bekler
- Arka plan çalışmasını aynı anda ilerletirken duyarlı bir kullanıcı arayüzünü veya ana iş parçacığını korumak.
Arka plan çalışması genellikle birden çok iş parçacığının kullanımını içerse de, zaman uyumsuzluk ve çok iş parçacıklı kavramlarını ayrı ayrı ele almak önemlidir. Aslında bunlar ayrı endişelerdir ve biri diğerini ima etmez. Bu makalede ayrı kavramlar daha ayrıntılı olarak açıklanmaktadır.
Asenkroni tanımı
Önceki nokta - zaman uyumsuzluğun birden çok iş parçacığının kullanımından bağımsız olduğu konusu - biraz daha açıklamaya değer. Bazen birbiriyle ilişkili ancak birbirinden tamamen bağımsız olan üç kavram vardır:
- Eşzamanlılık; birden çok hesaplama çakışan zaman aralıklarında yürütür.
- Paralellik; birden çok hesaplama veya tek bir hesaplamanın birkaç bölümü tam olarak aynı anda çalıştırıldığında.
- Asenkroni; bir veya daha fazla hesaplama ana program akışının dışında ayrı olarak yürütüldüğünde.
Üçü de ortogonal kavramlardır, ancak özellikle birlikte kullanıldığında kolayca birleştirilebilir. Örneğin, birden çok eşzamanlı hesaplamayı paralel olarak gerçekleştirmeniz gerekebilir. Bu ilişki, paralellik ve asenkronun birbirini ima ettiği anlamına gelmez.
"Zaman uyumsuz" sözcüğünün etymolojisini dikkate alırsanız, iki parça söz konusudur:
- "a", "değil" anlamına gelir.
- "synchronous", "aynı anda" anlamına gelir.
Bu iki terimi bir araya getirdiğinizde , "zaman uyumsuz" ifadesinin "aynı anda değil" anlamına geldiğini görürsünüz. Hepsi bu kadar! Bu tanımda eşzamanlılık veya paralelliğin bir etkisi yoktur. Bu, pratikte de geçerlidir.
Pratik olarak, F# dilindeki zaman uyumsuz hesaplamalar ana program akışından bağımsız olarak yürütülecek şekilde zamanlanır. Bu bağımsız yürütme eşzamanlılık veya paralellik anlamına gelmez ve hesaplamanın her zaman arka planda gerçekleştiği anlamına gelmez. Aslında, zaman uyumsuz hesaplamalar, hesaplamanın doğasına ve hesaplamanın yürütülmekte olduğu ortama bağlı olarak zaman uyumlu olarak bile yürütülebilir.
Sahip olmanız gereken temel nokta, zaman uyumsuz hesaplamaların ana program akışından bağımsız olmasıdır. Asenkron bir hesaplamanın ne zaman veya nasıl gerçekleştirileceğine dair az sayıda garanti olsa da, bunları düzenlemeye ve zamanlamaya yönelik bazı yaklaşımlar vardır. Bu makalenin geri kalanında F# zaman uyumsuzuna yönelik temel kavramlar ve F# içinde yerleşik olan türlerin, işlevlerin ve ifadelerin nasıl kullanılacağı incelenmektedir.
Temel kavramlar
F# dilinde zaman uyumsuz programlama, iki temel kavram üzerine kuruludur: eşzamanlı olmayan hesaplamalar ve görevler.
- Başlatıldığında bir görev oluşturmak için birleştirilebilir zaman uyumsuz hesaplamayı temsil eden
Async<'T>türü,async { }içerir. -
Task<'T>türü, yürütülen bir .NET görevini temsil edentask { }ifadeler içerir.
Genel olarak, görevleri kullanan .NET kitaplıklarıyla birlikte çalışıyorsanız ve zaman uyumsuz kodun tailcall'ları veya örtük iptal belirteci yayılmasına güvenmiyorsanız, yeni kodda task {…}'i async {…}'e tercih etmeyi düşünmelisiniz.
Asenkronun temel kavramları
Aşağıdaki örnekte "zaman uyumsuz" programlamanın temel kavramlarını görebilirsiniz:
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 türündedir string -> Async<unit>. Bir işlevi çağırmak aslında zaman uyumsuz hesaplamayı gerçekleştirmez. Bunun yerine, zaman uyumsuz olarak yürütülecek işin Async<unit> olarak davranan bir döndürür. Gövdesinde Async.AwaitTask çağrılır ve bu, ReadAllBytesAsync sonucunu uygun bir türe dönüştürür.
Diğer bir önemli satır ise Async.RunSynchronously çağrısıdır. Bu, gerçekten bir F# zaman uyumsuz hesaplamayı yürütmek istiyorsanız çağırmanız gereken Async modülünün başlatıcı işlevlerinden biridir.
Bu, C#/Visual Basic programlama stilinde temel bir farktır async . F# dilinde zaman uyumsuz hesaplamalar Soğuk görevler olarak düşünülebilir. Açık bir şekilde başlatılmalıdır ki gerçekten yürütülsün. Bu, C# veya Visual Basic'e kıyasla zaman uyumsuz çalışmayı birleştirmenize ve sıralamanıza olanak sağladığından bazı avantajları vardır.
Zaman uyumsuz hesaplamaları birleştirme
Hesaplamaları birleştirerek öncekini oluşturan bir örnek aşağıda verilmiştir:
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ç öğesi daha vardır. Kavramsal olarak aşağıdakileri yapar:
- Komut satırı bağımsız değişkenlerini bir
Async<unit>hesaplama dizisine,Seq.mapile dönüştürün. - Çalıştırıldığında hesaplamaları paralel olarak zamanlayan ve çalıştıran
Async<'T[]>birprintTotalFileBytesoluşturun. - Paralel hesaplamayı çalıştıracak ve sonucunu göz ardı edecek bir
Async<unit>oluşturun (bu birunit[]). - Genel olarak oluşturulan hesaplamayı
Async.RunSynchronouslyile açıkça çalıştırın ve tamamlanana kadar bekleyin.
Bu program çalıştırıldığında, printTotalFileBytes her komut satırı bağımsız değişkeni için paralel olarak çalışır. Zaman uyumsuz hesaplamalar program akışından bağımsız olarak yürütülürken, bilgilerini yazdırdıkları ve yürütmeyi bitirdikleri tanımlı bir sıra yoktur. Hesaplamalar paralel olarak zamanlanır, ancak bunların yürütme sırası garanti değildir.
Zaman uyumsuz hesaplamaları sırala
Zaten çalışan bir görev değil, çalışmanın belirtimi olduğu için Async<'T> kullanarak daha karmaşık dönüşümleri kolayca gerçekleştirebilirsiniz. Zaman uyumsuz hesaplamaların sırayla yürütülmesini sağlayan bir örnek aşağıda verilmiştir.
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, öğelerini paralel olarak zamanlamak yerine öğelerinin printTotalFileBytes sırasına göre yürütülecek şekilde zamanlanırargv. Önceki hesaplamanın yürütülmesi bitene kadar ardışık her işlem zamanlanmayacağından, hesaplamalar yürütülürken çakışma olmayacak şekilde sıralanır.
Önemli Asenkron modül işlevleri
F# dilinde zaman uyumsuz kod yazdığınızda genellikle sizin için hesaplamaların zamanlamasını işleyen bir çerçeveyle etkileşim kurarsınız. Ancak, her zaman böyle değildir, bu nedenle eşzamansız çalışma zamanlamak için kullanılabilecek çeşitli işlevleri anlamanın faydası vardır.
F# eşzamanlı olmayan hesaplamaları, zaten yürütülmekte olan işin bir temsili yerine çalışmanın bir tanımı olduğundan, açıkça bir başlangıç işleviyle başlatılmalıdır. Birçok farklı bağlamda yararlı olan Asenkron başlangıç metotları vardır. Aşağıdaki bölümde, daha yaygın başlangıç işlevlerinden bazıları açıklanmaktadır.
Async.StartChild
Zaman uyumsuz bir hesaplama içinde bir alt hesaplama başlatır. Bu, birden çok zaman uyumsuz hesaplamanın eşzamanlı olarak yürütülmesini sağlar. Çocuk hesaplama, üst hesaplamayla iptal belirtecini paylaşır. Ana hesaplama iptal edilirse, alt hesaplama da iptal edilir.
İmza:
computation: Async<'T> * ?millisecondsTimeout: int -> Async<Async<'T>>
Ne zaman kullanılır:
- Birden çok zaman uyumsuz hesaplamayı aynı anda değil aynı anda yürütmek istediğinizde, ancak bunların paralel olarak zamanlanmasını istemediğinizde.
- Bir alt hesaplamanın ömrünü üst hesaplamanınkine bağlamak istediğinizde.
Dikkat etmek gerekenler:
- ile birden çok hesaplama başlatmak, bunları paralel olarak zamanlamakla
Async.StartChildaynı değildir. Hesaplamaları paralel olarak zamanlamak istiyorsanız kullanınAsync.Parallel. - Ebeveyn hesaplamanın iptal edilmesi, başlattığı tüm alt hesaplamaları iptal eder.
Async.StartImmediate
Geçerli işletim sistemi iş parçacığından 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 gerekiyorsa bu yararlı olur. Örneğin, zaman uyumsuz bir hesaplamanın bir kullanıcı arabirimini güncelleştirmesi gerekiyorsa (ilerleme çubuğunu güncelleştirme gibi), Async.StartImmediate kullanılmalıdır.
İmza:
computation: Async<unit> * ?cancellationToken: CancellationToken -> unit
Ne zaman kullanılır:
- Zaman uyumsuz bir hesaplama sırasında çağıran iş parçacığında bir şeyi güncellemeniz gerektiğinde.
Dikkat etmek gerekenler:
- Zaman uyumsuz hesaplamadaki kod, denk geldiği herhangi bir iş parçacığında çalışır. Bu, kullanıcı arabirimi iş parçacığı gibi bir iş parçacığı bir şekilde hassassa sorun olabilir. Bu gibi durumlarda,
Async.StartImmediatekullanımı büyük olasılıkla uygun değildir.
Async.StartAsTask
İş parçacığı havuzunda bir hesaplama yürütür. Hesaplama sonlandırıldığında (sonucu ürettiğinde, istisna oluşturduğunda veya iptal edildiğinde) ilgili durumda tamamlanmış bir Task<TResult> döndürür. İptal belirteci sağlanmazsa varsayılan iptal belirteci kullanılır.
İmza:
computation: Async<'T> * ?taskCreationOptions: TaskCreationOptions * ?cancellationToken: CancellationToken -> Task<'T>
Ne zaman kullanılır:
- Zaman uyumsuz bir hesaplamanın sonucunu göstermek için bir Task<TResult> veren bir .NET API'sine çağrı yapmanız gerektiğinde.
Dikkat etmek gerekenler:
- Bu çağrı ek bir
Tasknesne ayırır ve bu da sık kullanılırsa ek yükü artırabilir.
Async.Parallel
Paralel olarak yürütülecek asenkron hesaplamalardan oluşan bir dizi planlanır ve temin edildikleri sıraya göre bir sonuçlar dizisi verir. Paralellik derecesi isteğe bağlı olarak parametresi belirtilerek maxDegreeOfParallelism ayarlanabilir/kısıtlanabilir.
İmza:
computations: seq<Async<'T>> * ?maxDegreeOfParallelism: int -> Async<'T[]>
Ne zaman kullanılır:
- Aynı anda bir dizi hesaplama çalıştırmanız gerekiyorsa ve bunların yürütme sırasına hiç bağlı değilseniz.
- Eğer tümü tamamlanana kadar paralel olarak zamanlanmış hesaplama sonuçlarına ihtiyacınız yoksa.
Dikkat etmek gerekenler:
- Sonuçta elde edilen değer dizisine yalnızca tüm hesaplamalar tamamlandıktan sonra erişebilirsiniz.
- Hesaplamalar, planlandıkları zaman gerçekleştirilecektir. Bu davranış, onların yürütme sıralarına güvenemeyeceğiniz anlamına gelir.
Async.Sequential
Geçildikleri sırayla yürütülecek zaman uyumsuz hesaplamalar dizisini zamanlayın. İlk hesaplama yürütülür, sonra bir sonraki, vb. Paralel olarak hiçbir hesaplama yürütülmeyecek.
İmza:
computations: seq<Async<'T>> -> Async<'T[]>
Ne zaman kullanılır:
- Birden çok hesaplamayı sırayla yürütmeniz gerekiyorsa.
Dikkat etmek gerekenler:
- Sonuçta elde edilen değer dizisine yalnızca tüm hesaplamalar tamamlandıktan sonra erişebilirsiniz.
- Hesaplamalar, bu işleve geçirilme sırasına göre çalıştırılır ve bu da sonuçlar döndürülmeden önce daha fazla süre geçmesi anlamına gelebilir.
Async.AwaitTask
Verilen Task<TResult>'ın tamamlanmasını bekleyen ve sonucunu Async<'T> olarak döndüren bir zaman uyumsuz hesaplama döndürür.
İmza:
task: Task<'T> -> Async<'T>
Ne zaman kullanılır:
- F# asenkron hesaplama içinde bir Task<TResult> döndüren bir .NET API'sini kullanırken.
Dikkat etmek gerekenler:
- Özel durumlar, Görev Paralel Kitaplığı'nın kuralına göre AggregateException biçiminde sarmalanır; bu davranış, F# eşzamanlı olmayan işlemlerinin genelde özel durumları nasıl ortaya çıkardığından farklıdır.
Async.Catch
Belirli bir Async<'T> yürütüp bir Async<Choice<'T, exn>> döndüren zaman uyumsuz bir hesaplama oluşturur. Verilen Async<'T> başarıyla tamamlanırsa, sonuç değeriyle bir Choice1Of2 döndürülür. Tamamlanmadan önce bir istisna fırlatılırsa, oluşan istisna ile birlikte bir Choice2of2 döndürülür. Zaman uyumsuz bir hesaplama birçok alt hesaplamadan oluşuyorsa ve bu alt hesaplamalardan biri bir özel durum ortaya çıkarıyorsa, tüm kapsayıcı hesaplama durdurulacaktır.
İmza:
computation: Async<'T> -> Async<Choice<'T, exn>>
Ne zaman kullanılır:
- Bir özel durumla başarısız olabilecek zaman uyumsuz bir çalışma gerçekleştirirken ve çağıranda bu özel durumu işlemek istediğinizde.
Dikkat etmek gerekenler:
- Birleştirilmiş veya sıralı asenkron hesaplamalar kullanılırken, "iç" hesaplamalardan biri bir istisna oluşturursa, kapsayan hesaplama tamamen durduracaktır.
Async.Ignore
Verilen hesaplamayı çalıştıran ancak sonucunu yok sayan asenkron bir hesaplama oluşturur.
İmza:
computation: Async<'T> -> Async<unit>
Ne zaman kullanılır:
- Sonucu gerekmeyen bir zaman uyumsuz hesaplamanız olduğunda. Bu, zaman uyumsuz kod işlevine benzer
ignore.
Dikkat etmek gerekenler:
- Eğer
Async.IgnoreveyaAsync.Startgerektiren başka bir işlevi kullanmak istediğiniz içinAsync<unit>kullanmanız gerekiyorsa, sonucu atmanın sorun olup olmadığını göz önünde bulundurun. Sonuçları yalnızca tür imzasına sığacak şekilde göz ardı etmekten kaçının.
Async.RunSynchronously
Zaman uyumsuz bir hesaplama çalıştırır ve çağrılan iş parçacığında sonucunu bekler. Hesaplama bir özel durum oluşturduğunda bir istisna yayılır. Bu çağrı engelleniyor.
İmza:
computation: Async<'T> * ?timeout: int * ?cancellationToken: CancellationToken -> 'T
Ne zaman kullanılır:
- Gerekirse, yürütülebilir dosyanın giriş noktasında bir uygulamada yalnızca bir kez kullanın.
- Performansı önemsemiyorsanız ve aynı anda bir dizi diğer zaman uyumsuz işlemi yürütmek istediğinizde.
Dikkat etmek gerekenler:
-
Async.RunSynchronouslyçağrısı, yürütme tamamlanana kadar çağrıyı yapan iş parçacığını engeller.
Async.Start
Zaman uyumsuz bir hesaplama başlatır ve unit iş parçacığı havuzunda geri döndürür. Tamamlanmasını beklemez veya bir özel durum sonucunu göz ardı eder. ile Async.Start başlatılan iç içe hesaplamalar, bunları çağıran üst hesaplamadan bağımsız olarak başlatılır; yaşam süreleri hiçbir üst hesaplamaya bağlı değildir. Üst hesaplama iptal edilirse, hiçbir alt hesaplama iptal edilmez.
İmza:
computation: Async<unit> * ?cancellationToken: CancellationToken -> unit
Yalnızca şu durumlarda kullanın:
- Bir sonuç vermeyen ve/veya bir hesaplamanın işlenmesini gerektirmeyen zaman uyumsuz bir hesaplamanız var.
- Zaman uyumsuz bir hesaplamanın ne zaman tamamlanmasını bilmeniz gerekmez.
- Zaman uyumsuz hesaplamanın hangi iş parçacığında çalıştırıldığı sizin için önemli değildir.
- Yürütmeden kaynaklanan özel durumları bilmeniz veya bildirmeniz gerekmez.
Dikkat etmek gerekenler:
-
Async.Startile başlatılan hesaplamalar tarafından tetiklenen özel durumlar çağırana aktarılmaz. Çağrı yığını tamamen kaldırılacaktır. -
printfnile başlatılan herhangi bir görev (örneğinAsync.Startçağırma), programın ana iş parçacığında bir etkiye neden olmaz.
.NET ile birlikte çalışma
Eğer async { } programlama kullanıyorsanız, async/await stili asenkron programlama kullanan bir .NET kütüphanesi veya C# kod tabanıyla birlikte çalışmanız gerekebilir. C# ve çoğu .NET kütüphanesi temel soyutlamaları olarak Task<TResult> ve Task türlerini kullandığından, bu durum F# asenkron kodunuzu yazma şeklinizi değiştirebilir.
Seçeneklerden biri, .NET görevlerini doğrudan kullanarak task { }yazmaya geçmektir. Alternatif olarak, .NET zaman uyumsuz hesaplamasını beklemek için Async.AwaitTask işlevini kullanabilirsiniz.
let getValueFromLibrary param =
async {
let! value = DotNetLibrary.GetValueAsync param |> Async.AwaitTask
return value
}
Eşzamansız bir hesaplamayı .NET arayana geçirmek için Async.StartAsTask işlevini kullanabilirsiniz.
let computationForCaller param =
async {
let! result = getAsyncResult param
return result
} |> Async.StartAsTask
API'lerle Task kullanan çalışmak için (bir değer döndürmeyen .NET zaman uyumsuz hesaplamaları), Async<'T> öğesini Task öğesine dönüştürecek ek bir işlev eklemeniz gerekebilir.
module Async =
// Async<unit> -> Task
let startTaskFromAsyncUnit (comp: Async<unit>) =
Async.StartAsTask comp :> Task
Zaten bir giriş olarak Async.AwaitTask kabul eden bir Task var. Bu ve önceden tanımlanmış startTaskFromAsyncUnit işleviyle F# zaman uyumsuz bir hesaplamadan Task türlerini başlatabilir ve bekleyebilirsiniz.
.NET görevlerini doğrudan F'de yazma#
F# dilinde 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 türündedir string -> Task<unit>. Görevi yürütmeye başlamak için işlevi çağırmak.
çağrısı task.Wait() görevin tamamlanmasını bekler.
Çoklu iş parçacığı ile ilişki
Bu makale boyunca threading'den bahsedilse de, hatırlamanız gereken iki önemli şey vardır:
- Geçerli iş parçacığında açıkça başlatılmadıkça, zaman uyumsuz hesaplama ile iş parçacığı arasında bir ilişki yoktur.
- F# dilindeki zaman uyumsuz programlama, çoklu iş parçacıkları için bir soyutlama değildir.
Örneğin, bir hesaplama, işin niteliğine bağlı olarak kendisini çağıran iş parçacığında çalışabilir. Hesaplama ayrıca iş parçacıkları arasında "atlayabilir" ve "bekleme" dönemleri arasında (örneğin, bir ağ çağrısının aktarımda olduğu durumlarda) yararlı işler yapmak için kısa bir süreliğine onları ödünç alabilir.
F# geçerli iş parçacığında zaman uyumsuz hesaplama başlatmak için bazı yetenekler sağlasa da (veya açıkça geçerli iş parçacığında değil), zaman uyumsuz genellikle belirli bir iş parçacığı stratejisiyle ilişkilendirilmemiştir.