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.Resultkullanarak 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:

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 .. dotry .. finally ..try .. with .., if .. then .. elseve if .. then ..denetim akışı yapılarını for .. in .. doiç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 IAsyncDisposablebir 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 letuseaynı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 |> ValueTaskkullanı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.Currentolmayan bir iş parçacığında başlatılırsa, kullanarak Task.Runiş parçacığı havuzundaki bir arka plan iş parçacığına geçer. null SynchronizationContext.Currentile 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 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.

Ayrıca bkz.