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.
Bu makalede, zaman uyumsuz ifadelere benzer ancak .NET görevlerini doğrudan yazmanıza olanak tanıyan görev ifadeleri için F# desteği açıklanmaktadır. Zaman uyumsuz ifadeler gibi, görev ifadeleri de kodu zaman uyumsuz olarak, diğer bir deyişle diğer bir çalışmanın yürütülmesini engellemeden yürütür.
Zaman uyumsuz kod normalde async ifadeler kullanılarak yazılır. .NET görevleri oluşturan veya kullanan .NET kitaplıklarıyla birlikte çalışırken görev ifadelerinin kullanılması tercih edilir. Görev ifadeleri performansı ve hata ayıklama deneyimini de geliştirebilir. Ancak, görev ifadeleri makalenin ilerleyen bölümlerinde açıklanan bazı sınırlamalarla birlikte gelir.
Sözdizimi
task { expression }
Önceki söz diziminde, expression ile temsil edilen hesaplama bir .NET görevi olarak çalışacak şekilde ayarlanmıştır. Bu kod yürütüldükten sonra görev derhal başlatılır ve mevcut iş parçacığında ilk zaman uyumsuz işlem gerçekleştirildiğinde kadar çalışır (örneğin, zaman uyumsuz bir uyku, zaman uyumsuz G/Ç veya diğer ilkel zaman uyumsuz işlemler). İfadenin türü Task<'T>'dir, burada 'T ifadenin return anahtar sözcüğü kullanılarak döndürülen türdür.
let! ifadesini kullanarak bağlama
Görev ifadesinde, bazı ifadeler ve işlemler zaman uyumlu, bazıları ise zaman uyumsuz. Zaman uyumsuz bir işlemin sonucunu beklediğinizde, sıradan let bir bağlama yerine kullanırsınız let!.
let! etkisi, hesaplama gerçekleştirilirken yürütmenin diğer hesaplamalar veya iş parçacıkları üzerinde devam edebilmesini sağlamaktır. Bağlamanın sağ tarafı let! döndükten sonra, görevin geri kalanı yürütülmeye devam eder.
Aşağıdaki kod ile letarasındaki let! farkı gösterir.
let kullanan kod satırı, örneğin task.Wait() veya task.Result kullanarak daha sonra bekleyebileceğiniz bir nesne olarak bir görev oluşturur. Kullanan let! kod satırı görevi başlatır ve sonucunu bekler.
// let just stores the result as a task.
let (result1 : Task<int>) = stream.ReadAsync(buffer, offset, count, cancellationToken)
// let! completes the asynchronous operation and returns the data.
let! (result2 : int) = stream.ReadAsync(buffer, offset, count, cancellationToken)
F# task { } ifadeleri aşağıdaki zaman uyumsuz işlem türlerini bekleyebilir:
- .NET görevleri Task<TResult> ve genel olmayan Task.
- .NET değer görevi, ValueTask<TResult> ve genel olmayan ValueTask.
- F# asenkron hesaplamalar
Async<T>. - F# RFC FS-1097'de belirtilen "GetAwaiter" desenini izleyen herhangi bir nesne.
return Ifa -de
Görev ifadeleri içinde, return expr bir görevin sonucunu döndürmek için kullanılır.
return! Ifa -de
Görev ifadeleri içinde, return! expr başka bir görevin sonucunu döndürmek için kullanılır.
let! kullanıp ardından hemen sonucu döndürmeye eşdeğerdir.
Denetim akışı
Görev ifadeleri, denetim akışı yapıları for .. in .. do, while .. do, try .. with .., try .. finally .., if .. then .. else ve if .. then .. içerebilir. Bunlar, with ve finally işleyicileri dışında, zaman uyumlu olarak yürütülen başka görev yapıları da içerebilir. Eğer eşzamansız bir try .. finally .. ihtiyacınız varsa, türü use olan bir nesne ile birlikte bir IAsyncDisposable bağlama kullanın.
use ve use! bağlamaları
Görev ifadeleri içinde bağlamalar use veya IDisposabletüründeki IAsyncDisposable değerlere bağlanabilir. İkincisi için, atık temizleme operasyonu eş zamansız olarak gerçekleştirilir.
let! öğesine ek olarak, zaman uyumsuz bağlamalar gerçekleştirmek için use! kullanabilirsiniz.
let! ile use! arasındaki fark, let ile use arasındaki farkla aynıdır.
use! objesi için, geçerli kapsamın kapanışında nesne atılır. F# 6'da, use! bir değerin null olarak başlatılmasına izin vermediğini, ancak use'nin izin verdiğini unutmayın.
open System
open System.IO
open System.Security.Cryptography
task {
// use IDisposable
use httpClient = new Net.Http.HttpClient()
// use! Task<IDisposable>
use! exampleDomain = httpClient.GetAsync "https://example.com/data.enc"
// use IDisposable
use aes = Aes.Create()
aes.KeySize <- 256
aes.GenerateIV()
aes.GenerateKey()
// do! Task
do! File.WriteAllTextAsync("key.iv.txt", $"Key: {Convert.ToBase64String aes.Key}\nIV: {Convert.ToBase64String aes.IV}")
// use IAsyncDisposable
use outputStream = File.Create "secret.enc"
// use IDisposable
use encryptor = aes.CreateEncryptor()
// use IAsyncDisposable
use cryptoStream = new CryptoStream(outputStream, encryptor, CryptoStreamMode.Write)
// do! Task
do! exampleDomain.Content.CopyToAsync cryptoStream
}
Değer Görevleri
Değer görevleri, görev tabanlı programlamada ayırmaları önlemek için kullanılan yapılardır. Değer görevi, .AsTask() kullanılarak gerçek bir göreve dönüştürülen kısa ömürlü bir değerdir.
Bir görev ifadesinden değer görevi oluşturmak için |> ValueTask<ReturnType> veya |> ValueTask kullanın. Örneğin:
let makeTask() =
task { return 1 }
makeTask() |> ValueTask<int>
and! bağlantılar (F# 10'dan itibaren)
Görev ifadeleri içinde, eşzamanlı olarak birden çok zaman uyumsuz işlem (Task<'T>, ValueTask<'T>, Async<'T> vb.) beklemek mümkündür. Karşılaştırmak:
// We'll wait for x to resolve and then for y to resolve. Overall execution time is sum of two execution times.
let getResultsSequentially() =
task {
let! x = getX()
let! y = getY()
return x, y
}
// x and y will be awaited concurrently. Overall execution time is the time of the slowest operation.
let getResultsConcurrently() =
task {
let! x = getX()
and! y = getY()
return x, y
}
İptal belirteçleri ve iptal denetimleri ekleme
F# zaman uyumsuz ifadelerinden farklı olarak, görev ifadeleri örtük olarak bir iptal belirteci geçirmez ve örtük olarak iptal denetimleri gerçekleştirmez. Kodunuz bir iptal belirteci gerektiriyorsa, iptal belirtecini parametre olarak belirtmeniz gerekir. Örneğin:
open System.Threading
let someTaskCode (cancellationToken: CancellationToken) =
task {
cancellationToken.ThrowIfCancellationRequested()
printfn $"continuing..."
}
Kodunuzu doğru bir şekilde iptal edilebilir hale getirmek istiyorsanız, iptal belirtecini iptal etmeyi destekleyen tüm .NET kitaplık işlemlerine geçirip geçirmediğinizden emin olun. Örneğin, Stream.ReadAsync'ün biri iptal belirteci kabul eden birden fazla aşırı yüklemesi vardır. Bu aşırı yükü kullanmazsanız, belirli bir zaman uyumsuz okuma işlemi iptal edilemez.
Arka plan görevleri
Varsayılan olarak, mevcutsa SynchronizationContext.Current kullanılarak .NET görevleri zamanlanır. Bu, görevlerin kullanıcı arabirimi iş parçacığında, kullanıcı arabirimini engellemeden işbirliği içinde ve ara belleğe alınmış bir şekilde çalışmasına olanak tanır. Mevcut değilse, görev devamlılıkları .NET iş parçacığı havuzuna planlanır.
Pratikte, görevleri oluşturan kitaplık kodunun senkronizasyon bağlamını dikkate almaması ve bunun yerine gerekli olduğunda her zaman .NET iş parçacığı havuzuna geçiş yapması genellikle tercih edilir. Bunu şu şekilde backgroundTask { }gerçekleştirebilirsiniz:
backgroundTask { expression }
Null olmayan bir iş parçacığında başlatılırsa, arka plan görevi şu anlamda herhangi bir SynchronizationContext.Current'ü yoksayar: iş parçacığı havuzundaki bir arka plan iş parçacığına SynchronizationContext.Current kullanarak geçer. null SynchronizationContext.Currentile bir iş parçacığında başlatılırsa, aynı iş parçacığında yürütülür.
Uyarı
Pratikte bu, F# iş kodunda ConfigureAwait(false) çağrısının genellikle gerekli olmadığı anlamına gelir. Bunun yerine, arka planda çalıştırılması amaçlanan görevler kullanılarak backgroundTask { ... }yazılmalıdır. Arka plan görevine yapılan dış görev bağlamaları, arka plan görevi tamamlandığında SynchronizationContext.Current ile tekrar senkronize edilir.
Tailcall'larla ilgili görevlerin sınırlamaları
F# zaman uyumsuz ifadelerinden farklı olarak, görev ifadeleri tailcall'ları desteklemez. Geçerli görev, return! yürütüldüğünde, sonucu dönen görevi bekliyor olarak kaydedilir. Bu, görev ifadeleri kullanılarak uygulanan özyinelemeli fonksiyonlar ve yöntemlerin sınırsız görev zincirleri oluşturabileceği ve bunların sınırsız çağrı yığını veya yığın kullanabileceği anlamına gelir. Örneğin, aşağıdaki kodu göz önünde bulundurun:
let rec taskLoopBad (count: int) : Task<string> =
task {
if count = 0 then
return "done!"
else
printfn $"looping..., count = {count}"
return! taskLoopBad (count-1)
}
let t = taskLoopBad 10000000
t.Wait()
Bu kodlama stili görev ifadeleriyle kullanılmamalıdır; 10000000 görevden oluşan bir zincir oluşturur ve neden StackOverflowExceptionolur. Her döngü çağrısına zaman uyumsuz bir işlem eklenirse kod temelde ilişkisiz bir yığın kullanır. Bu kodu açık bir döngü kullanacak şekilde değiştirmeyi göz önünde bulundurun, örneğin:
let taskLoopGood (count: int) : Task<string> =
task {
for i in count .. 1 do
printfn $"looping... count = {count}"
return "done!"
}
let t = taskLoopGood 10000000
t.Wait()
Zaman uyumsuz tailcall'lar gerekiyorsa, tailcall'ları destekleyen bir F# zaman uyumsuz ifadesi kullanın. Örneğin:
let rec asyncLoopGood (count: int) =
async {
if count = 0 then
return "done!"
else
printfn $"looping..., count = {count}"
return! asyncLoopGood (count-1)
}
let t = asyncLoopGood 1000000 |> Async.StartAsTask
t.Wait()
Görev uygulaması
Görevler, F# 6'daki yeni bir özellik olan Devam Ettirilebilir Kod kullanılarak uygulanır. Görevler, F# derleyicisi tarafından "Devam Ettirilebilir Durum Makineleri" olarak derlenir. Bunlar, Sürdürme kodu RFC'sinde ve bir F# derleyicisi topluluk oturumunda ayrıntılı olarak açıklanmıştır.