Eşzamanlı yöntemler için eşzamansız sarmalayıcılar

Bir kitaplıkta zaman uyumlu bir yönteminiz olduğunda, bunu içinde Task.Run sarmalayan zaman uyumsuz bir karşılığı kullanıma sunmanız cazip olabilir.

public T Foo() { /* synchronous work */ }

// Don't do this in a library:
public Task<T> FooAsync()
{
    return Task.Run(() => Foo());
}

Bu makalede, bu yaklaşımın kitaplıklar için neden neredeyse her zaman yanlış olduğu ve dengeleri nasıl düşünebilecekleri açıklanmaktadır.

Ölçeklenebilirlik ve boşaltma karşılaştırması

Zaman uyumsuz programlama iki farklı avantaj sağlar:

  • Ölçeklenebilirlik — G/Ç beklemeleri sırasında iş parçacıklarını serbest bırakarak kaynak tüketimini azaltın.
  • Yük boşaltma — Yanıt hızını korumak (örneğin, UI iş parçacığını boş tutmak) veya eşzamanlılık elde etmek için işi başka bir iş parçacığına aktarmak.

Bu avantajlar farklı yaklaşımlar gerektirir. Kritik önemli fark: Senkron bir yöntemi sarmalamak, yük paylaşımına yardımcı olur, ancak ölçeklenebilirlik için hiçbir şey yapmaz.

Ölçeklenebilirliği neden Task.Run geliştirmiyor?

Tam anlamıyla zaman uyumsuz bir uygulama, uzun süreli bir işlem sırasında kullanılan iş parçacığı sayısını azaltır. Sarmalayıcı Task.Run hala bir iş parçacığını engeller; yalnızca engellemeyi bir iş parçacığından diğerine taşır:

public static class TimerExampleWrong
{
    public static Task SleepAsync(int millisecondsTimeout)
    {
        return Task.Run(() => Thread.Sleep(millisecondsTimeout));
    }
}
Public Module TimerExampleWrong
    Public Function SleepAsync(millisecondsTimeout As Integer) As Task
        Return Task.Run(Sub() Thread.Sleep(millisecondsTimeout))
    End Function
End Module

Bir yaklaşımı beklerken herhangi bir iş parçacığı tüketmeyen gerçekten asenkron bir uygulamayla karşılaştırın:

public static class TimerExampleRight
{
    public static Task SleepAsync(int millisecondsTimeout)
    {
        var tcs = new TaskCompletionSource<bool>();
        var timer = new Timer(
            _ => tcs.TrySetResult(true), null, millisecondsTimeout, Timeout.Infinite);

        tcs.Task.ContinueWith(
            _ => timer.Dispose(), TaskScheduler.Default);

        return tcs.Task;
    }
}
Public Module TimerExampleRight
    Public Function SleepAsync(millisecondsTimeout As Integer) As Task
        Dim tcs As New TaskCompletionSource(Of Boolean)()
        Dim tmr As New Timer(
            Sub(state) tcs.TrySetResult(True), Nothing, millisecondsTimeout, Timeout.Infinite)

        tcs.Task.ContinueWith(
            Sub(t) tmr.Dispose(), TaskScheduler.Default)

        Return tcs.Task
    End Function
End Module

Her iki uygulama da belirtilen gecikmeden sonra tamamlar, ancak ikinci uygulama beklerken herhangi bir iş parçacığını engellemez. Birçok eşzamanlı isteği işleyen sunucu uygulamaları için bu fark, bir sunucunun aynı anda kaç isteği işleyebileceğini doğrudan etkiler.

Boşaltma tüketicinin sorumluluğundadır

Eşzamanlı çağrıları Task.Run içine sarmalamak, bir UI iş parçacığından iş yükünü boşaltmak için yararlıdır. Ancak, bu sarmalama işlemini kitaplık değil, tüketici yapmalıdır.

public static class UIOffloadExample
{
    public static int ComputeIntensive(int input)
    {
        int result = 0;
        for (int i = 0; i < input; i++)
        {
            result += i;
        }
        return result;
    }

    public static async Task ConsumeFromUIThreadAsync()
    {
        int result = await Task.Run(() => ComputeIntensive(10_000));
        Console.WriteLine($"Result: {result}");
    }
}
Public Module UIOffloadExample
    Public Function ComputeIntensive(input As Integer) As Integer
        Dim result As Integer = 0
        For i As Integer = 0 To input - 1
            result += i
        Next
        Return result
    End Function

    Public Async Function ConsumeFromUIThreadAsync() As Task
        Dim result As Integer = Await Task.Run(Function() ComputeIntensive(10_000))
        Console.WriteLine($"Result: {result}")
    End Function
End Module

Kullanıcı bağlamını bilir: UI iş parçacığında olup olmadıklarını, ne kadar ayrıntı düzeyine ihtiyaç duyduklarını ve yük aktarımının değer katıp katmadığını. Kitaplıkta yok.

Kitaplıklar neden zaman uyumsuz eşitleme sarmalayıcılarını kullanıma sunmamalıdır?

Bir kitaplık yalnızca zaman uyumlu yöntemi (zaman uyumsuz sarmalayıcı değil) kullanıma sunarsa, tüketiciler çeşitli yollarla avantaj sağlar:

  • Azaltılmış API yüzey alanı: Öğrenme, test etme ve bakım için daha az yöntem.
  • Yanıltıcı ölçeklenebilirlik beklentileri yok: Kullanıcılar yalnızca zaman uyumsuz olarak kullanıma sunulan yöntemlerin ölçeklenebilirlik avantajları sağladığını bilir.
  • Tüketici denetimi: Arayanlar, doğru ayrıntı düzeyinde yükü aktarıp aktarmayacağını ve nasıl olacağını seçer. Yüksek aktarım hızına sahip bir sunucu uygulaması, zaman uyumlu yöntemi doğrudan çağırarak gereksiz ek yüklerden Task.Runkaçınabilir.
  • Daha iyi performans: Zaman uyumsuz sarmalayıcılar ayırmalar, bağlam geçişleri ve iş parçacığı havuzu zamanlaması aracılığıyla ek yük ekler. Ayrıntılı işlemler için bu ek yük önemli olabilir.

Kurala özel durumlar

Bazı temel sınıflar, türetilmiş sınıfların bunları gerçekten asenkron uygulamalarla geçersiz kılabilmesi için asenkron yöntemler sunar. Temel sınıf, zaman uyumsuz-üzerinden-uyumlu varsayılan bir yapı sunar.

Örneğin, Stream, ReadAsync ve WriteAsync'yi açığa çıkarır. Temel uygulamalar, eşzamanlı Read ve Write yöntemlerini sarar. FileStream ve NetworkStream gibi türetilmiş sınıflar, gerçek ölçeklenebilirlik avantajları sağlayan zaman uyumsuz G/Ç uygulamalarıyla bu yöntemleri geçersiz kılar.

Benzer şekilde, TextReader temel sınıf üzerinde bir sarmalayıcı olarak ReadToEndAsync sağlar ve StreamReader, dahili olarak ReadAsync çağırarak gerçekten zaman uyumsuz bir uygulamayla bunu geçersiz kılar.

Bu özel durumlar şunlardan dolayı geçerlidir:

  • Desen, çok biçimlilik için tasarlanmıştır. Arayanlar temel türle etkileşim kurar.
  • Türetilmiş türler gerçekten eş zamanlı olmayan geçersiz kılmalar sağlar.

Yönerge

Bir kütüphaneden asenkron yöntemleri yalnızca uygulama, senkron karşılığına göre gerçekten bir ölçeklenebilirlik avantajı sağladığında kullanıma sunun. Asenkron yöntemleri yalnızca iş yükünü devretmek için kullanıma sunmayın. Bu seçimi tüketiciye bırakın.

Ayrıca bakınız