Görev ifadeleri
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 zaman uyumsuz 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, tarafından expression
temsil edilen hesaplama bir .NET görevi olarak çalışacak şekilde ayarlanmıştır. Görev, bu kod yürütüldükten hemen sonra başlatılır ve ilk zaman uyumsuz işlemi (örneğin, zaman uyumsuz bir uyku, zaman uyumsuz G/Ç veya diğer ilkel zaman uyumsuz işlem) gerçekleştirilene kadar geçerli iş parçacığında çalışır. İfadenin türü, Task<'T>
burada 'T
anahtar sözcük kullanıldığında ifade return
tarafından döndürülen türdür.
let! 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!
. bunun etkisi let!
, hesaplama gerçekleştirilirken yürütmenin diğer hesaplamalarda veya iş parçacıklarında devam edebilmesini sağlamaktır. Bağlamanın sağ tarafı let!
döndükten sonra, görevin geri kalanı yürütmeye devam eder.
Aşağıdaki kod ile let!
arasındaki let
farkı gösterir. kullanan let
kod satırı, örneğin veya task.Result
kullanarak task.Wait()
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 bekleyebilirsiniz:
- .NET görevleri Task<TResult> ve genel Taskolmayan .
- .NET değer görevleri ValueTask<TResult> ve genel ValueTaskolmayan .
- F# zaman uyumsuz 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. Kullanarak let!
ve ardından sonucu hemen döndürmeye eşdeğerdir.
Denetim akışı
Görev ifadeleri, while .. do
try .. finally ..
try .. with ..
, if .. then .. else
ve if .. then ..
denetim akışı yapılarını for .. in .. do
içerebilir. Bunlar da zaman uyumlu olarak yürütülen ve finally
işleyicileri dışında with
başka görev yapıları da içerebilir. Zaman uyumsuz try .. finally ..
bir öğesine ihtiyacınız varsa, türündeki IAsyncDisposable
bir use
nesneyle birlikte bağlama kullanın.
use
ve use!
bağlamaları
Görev ifadeleri içinde bağlamalar use
veya IAsyncDisposabletüründeki IDisposable değerlere bağlanabilir. İkincisi için, atma temizleme işlemi zaman uyumsuz olarak yürütülür.
öğesine ek olarak let!
, zaman uyumsuz bağlamalar gerçekleştirmek için kullanabilirsiniz use!
. ile use!
arasındaki let!
fark ile arasındaki farkla let
use
aynıdır. için use!
nesnesi geçerli kapsamın kapanışında atılır. F# 6'da, use!
bir değerin null olarak başlatılmasına izin vermediğini unutmayın.use
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, kullanılarak .AsTask()
gerçek 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 veya |> ValueTask
kullanın|> ValueTask<ReturnType>
. Örneğin:
let makeTask() =
task { return 1 }
makeTask() |> ValueTask<int>
İ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
biri iptal belirteci kabul eden birden çok aşırı yükleme vardır. Bu aşırı yüklemeyi kullanmazsanız, bu belirli zaman uyumsuz okuma işlemi iptal edilemez.
Arka plan görevleri
Varsayılan olarak, .NET görevleri varsa kullanılarak SynchronizationContext.Current zamanlanır. Bu, görevlerin kullanıcı arabirimi iş parçacığında kullanıcı arabirimi iş parçacığında kullanıcı arabirimini engellemeden yürütülen işbirliğine bağlı, araya katılmış aracılar olarak görev yapmasına olanak tanır. Yoksa, görev devamlılıkları .NET iş parçacığı havuzuna zamanlanır.
Pratikte, görevleri oluşturan kitaplık kodunun eşitleme bağlamını yoksayması ve gerekirse her zaman .NET iş parçacığı havuzuna geçmeleri genellikle tercih edilir. Bunu şu şekilde backgroundTask { }
gerçekleştirebilirsiniz:
backgroundTask { expression }
Arka plan görevi şu anlamda herhangi birini SynchronizationContext.Current
yoksayar: null SynchronizationContext.Current
olmayan bir iş parçacığında başlatılırsa, kullanarak Task.Run
iş parçacığı havuzundaki bir arka plan iş parçacığına geçer. null SynchronizationContext.Current
ile bir iş parçacığında başlatılırsa, aynı iş parçacığında yürütülür.
Not
Pratikte bu, çağrısının ConfigureAwait(false)
genellikle F# görev kodunda 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örevinin tamamlanmasıyla SynchronizationContext.Current
yeniden eşitlenir.
Tailcalls ile ilgili görevlerin sınırlamaları
F# zaman uyumsuz ifadelerinden farklı olarak, görev ifadeleri tailcall'ları desteklemez. Yani, yürütülürken return!
, geçerli görev sonucu döndürülmekte olan görevi bekliyor olarak kaydedilir. Bu, görev ifadeleri kullanılarak uygulanan özyinelemeli işlevlerin ve yöntemlerin ilişkisiz görev zincirleri oluşturabileceği ve bunların ilişkisiz 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 StackOverflowException
olur. 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.