İlkay İlknur

just a developer...

Task.FromResult Metodu Ne İş Yapar, Nerede Kullanılır ?

Bu makaleye Github üzerinden katkıda bulunabilirsiniz.

Bugün C# ile kod yazarken en sık kullandığımız keywordlerden ikisi şüphesiz ki async & await keywordleri. Her ne kadar bu keywordleri bilinçli olarak kullanıyoruz muyuz konusunda tam emin olamasam da bazen async & await keywordunu kullanmaktan biraz daha ötesine geçmek gerekiyor.

Task objesi bildiğimiz üzere .NET içerisinde bir asenkron operasyonla ilgili tüm bilgileri içerisinde tutan sınıf. Yani bir asenkron operasyonun o anki durumu ne ? veya işlem gerçekleşirken bir exception oldu mu ? gibi sorularımız cevaplarını alacağımız sınıf bu sınıf. Dolayısıyla C# içerisindeki async ve await keywordleri de bu task tipini kullanarak arka planda uygun bir şekilde gereken yapıları oluşturuyorlar.

Task içerisindeki FromResult metodu da en basit anlamda yeni bir Task objesi yaratarak Task'ın durumunu tamamlanmış olarak işaretliyor ve parametre olarak verdiğiniz değeri de Task'ın sonucu olarak içerisine atıyor. Bu durumda aklınıza şu soru geliyor olabilir. Normalde async & await kullandığımızda Task yaratmamız gerekmiyor. Peki bu metot neden var ? Neden ortada bir asenkron operasyon yokken Task yaratmak durumunda kalıyoruz ? Hemen kısaca kullanım alanlarına bakalım...

Interface içerisindeki asenkron metot implementasyonları

Diyelim ki 3rd party bir kütüphane kullanıyorsunuz ve o kütüphaneyi kullanabilmeniz içinde belirli bir interface'i implemente eden bir tip yaratmanız gerekiyor.

interface IThirdLib
{
    Task<int> CountAsync();
}

Bu interface içerisindeki CountAsync metodunu implemente etmek istediğinizde bulunduğunuz platformda bu işlem asenkron yapılmıyor olabilir veya static bir değer döndürmeniz gerekebilir.

class MyClass : IThirdLib
{
    public Task<int> CountAsync()
    {
        return 0;
    }
}

veya

class MyClass : IThirdLib
{
    public Task<int> CountAsync()
    {
        return Directory.EnumerateDirectories(@"C:\").Count();
    }
}

Bu durumda async await keywordu kullanmadığımız için Task tipini arka planda compiler bizim için yaratmıyor ve dolayısıyla int'i ben Task tipine çeviremem diye hata veriyor. Aklınıza hemen Task.Run yaparım onu da await ederim geliyorsa hemen o fikri unutun ve sakın yapmayın. Task.Run'ı şuursuz olarak her Task gereken yerde kullanmamak gerektiğini sakın unutmayın.

Yukarıdaki gibi bir durumla karşılırsanız Task.FromResult metodunu kullanarak tamamlanmış bir Task objesi yaratıp onu metotdan dönebilirsiniz.

class MyClass : IThirdLib
{
    public Task<int> CountAsync()
    {
        return Task.FromResult(5);
    }
}

veya

class MyClass : IThirdLib
{
    public Task<int> CountAsync()
    {
        return Task.FromResult(Directory.EnumerateDirectories(@"C:\").Count());
    }
}

Böylece bizim implementasyonumuz senkron çalışacak ama aynı zamanda bizim yazdığımız tipi kullananlar da async await kullanarak metodumuzu çağırsalar bile herhangi bir sorun yaşamayacaklar.

Bazı durumlarda senkron bazı durumlarda asenkron çalışan metotlar

Yazdığımız metotlar duruma göre senkron veya asenkron çalışabilirler. Örneğin bazı durumlarda senkron olarak validasyon yapılıp bunun sonucuna göre asenkron operasyona başlamadan exception fırlatılabilir. Örneğin MSDN'deki şu örneğe bakarsak...

static Task<long> GetFileLengthsAsync(string filePath)
{
    if (!Directory.Exists(filePath))
    {
        return Task.FromException<long>(
                    new DirectoryNotFoundException("Invalid directory name."));
    }
    else
    {
        string[] files = Directory.GetFiles(filePath);
        if (files.Length == 0)
            return Task.FromResult(0L);
        else
            return Task.Run(() => {
                long total = 0;
                Parallel.ForEach(files, (fileName) => {
                    var fs = new FileStream(fileName, FileMode.Open,
                                            FileAccess.Read, FileShare.ReadWrite,
                                            256, true);
                    long length = fs.Length;
                    Interlocked.Add(ref total, length);
                    fs.Close();
                });
                return total;
            });
    }
}

Bir klasörüdeki tüm dosyaların toplam büyüklüğünü almak istediğimizde örneğin ilk if'e bakarsanız klasörün validasyonu yapılıyor ve eğer klasör yoksa metot doğrudan geri dönüyor. Else içerisindeki 1. if'e bakarsak da klasör içerisinde hiç dosya yoksa doğrudan 0 dönüyor. Sonrasında ise Task.Run ile asenkron operasyon başlatılıyor. Dolayısıyla bu metot aslında en içteki else ifadesine kadar senkron çalışıyor. Sonrasında ise asenkron olarak devam ediyor. İşte bu gibi durumlarda da Task.FromResult metodu kullanılabilir.

Task.FromResult'a benzer diğer metotlar

Task tipi içerisinde FromResult metoduna benzeyen başka metotlar da bulabilmeniz mümkün. Örneğin, FromException veya FromCancelled gibi metotlar. Bu metotlar da isimlerindeki durumlara uyan Task tiplerini yaratıp size verirler. İçerisindeki exception olan task tipi veya iptal edilen bir Task tipi gibi...

Task.CompletedTask

.NET Framework 4.6 ile beraber Task içerisine CompletedTask propertysi eklendi. Bu Task tipi de herhangi bir değer döndürmeyen tamamlanış bir Task objesi içerisinde barındırıyor. Yani yukarıdaki gibi durumlardan birinde metodun dönüş tipi Task<int> değil de Task olsaydı Task.CompletedTask kullanabiliriz.

Taskları Cacheleme

Task.FromResult metodu her çağırdığımızda yeni bir Task objesi bize geri veriyor. Dolayısıyla her Task.FromResult(0) dediğimizde hep sonuç olarak 0 değerini barındıran bir task bize geri dönüyor.

Bu gibi durumlarda sık sık bu metodun çağırılması aslında gereksiz bir şekilde object yaratılmasına ve memoryde gereksiz bir şekilde fazladan bu objelerin yer almasına neden oluyor. Bu gibi durumlarda sıkça başvurulan yöntemlerden biri bu Taskların cachelenmesi. Bunun için örnek olarak Roslyn içerisindeki Task cacheleme mekanizmasına bakmanızı tavsiye ederim.

Roslyn içerisindeki SpecializedTasks sınıfı source code'u : https://github.com/dotnet/roslyn/blob/master/src/Workspaces/Core/Portable/Utilities/SpecializedTasks.cs



Yorum Gönder