Asenkron yöntemleri canlı tutun

Yangın ve unutma işini başlatmak ve kaybetmek kolaydır. Zaman uyumsuz bir işlem başlatır ve döndürülen Task değerine yönelik takibi bırakırsanız, tamamlanma, iptal ve hatalarla ilgili görünürlüğü kaybedersiniz.

Asenkron koddaki çoğu ömrü hataları, derleyici hatası değil, sahiplik hatalarıdır. async durum makinesi ve Task, devamlılıklar aracılığıyla işler hala erişilebilirken çalışır durumda kalır. Uygulamanız artık işi takip etmediğinde sorunlar meydana gelir.

Neden "fire-and-forget" yaklaşımı kalıcı hatalara neden oluyor?

Arka plan çalışmasını izlemeden başlattığınızda üç risk oluşturursunuz:

  • İşlem başarısız olabilir ve kimse istisnayı gözlemlemez.
  • İşlem veya ana bilgisayar, işlem tamamlanmadan kapanabilir.
  • İşlem, onu denetlemesi amaçlanan nesne veya kapsamdan daha uzun sürebilir.

Çalışma gerçekten isteğe bağlı olduğunda ve başarısızlık kabul edilebilir olduğunda yalnızca 'fire-and-forget' teknolojisini kullanın.

Arka plan çalışmasını açıkça takip etme

Bu örnek, uçuş içi görevlerin iş parçacığı açısından güvenli bir sözlüğünü tutan özel bir yardımcı sınıfı tanımlar BackgroundTaskTracker. Siz Track'yi çağırdığınızda, tamamlandığında görevi sözlükten kaldıran ve herhangi bir hatayı günlüğe kaydeden görev üzerinde bir ContinueWith devam kaydetmesini sağlar. Siz DrainAsync çağırdığınızda, Task.WhenAll hala sözlükte bulunan her görevi çağırır ve sonuçta elde edilen görevi döndürür.

public sealed class BackgroundTaskTracker
{
    private readonly ConcurrentDictionary<int, Task> _inFlight = new();

    public void Track(Task operationTask, string name)
    {
        int id = operationTask.Id;
        _inFlight[id] = operationTask;

        _ = operationTask.ContinueWith(completedTask =>
        {
            _inFlight.TryRemove(id, out _);

            if (completedTask.IsFaulted)
            {
                Console.WriteLine($"{name} failed: {completedTask.Exception?.GetBaseException().Message}");
            }
        }, TaskScheduler.Default);
    }

    public Task DrainAsync()
    {
        Task[] snapshot = _inFlight.Values.ToArray();
        return snapshot.Length == 0 ? Task.CompletedTask : Task.WhenAll(snapshot);
    }
}
Public NotInheritable Class BackgroundTaskTracker
    Private ReadOnly _inFlight As New ConcurrentDictionary(Of Integer, Task)()

    Public Sub Track(operationTask As Task, name As String)
        Dim id As Integer = operationTask.Id
        _inFlight(id) = operationTask

        Dim continuationTask As Task = operationTask.ContinueWith(Sub(completedTask)
                                                                      Dim removedTask As Task = Nothing
                                                                      _inFlight.TryRemove(id, removedTask)

                                                                      If completedTask.IsFaulted Then
                                                                          Console.WriteLine($"{name} failed: {completedTask.Exception.GetBaseException().Message}")
                                                                      End If
                                                                  End Sub,
                                                                  TaskScheduler.Default)
    End Sub

    Public Function DrainAsync() As Task
        Dim snapshot As Task() = _inFlight.Values.ToArray()

        If snapshot.Length = 0 Then
            Return Task.CompletedTask
        End If

        Return Task.WhenAll(snapshot)
    End Function
End Class

Aşağıdaki örnekte, bir arka plan işlemini başlatmak, gözlemlemek ve tahliye etmek için BackgroundTaskTracker kullanılır.

public static class FireAndForgetFix
{
    public static async Task RunAsync(BackgroundTaskTracker tracker)
    {
        Task backgroundTask = Task.Run(async () =>
        {
            await Task.Delay(100);
            throw new InvalidOperationException("Background operation failed.");
        });

        tracker.Track(backgroundTask, "Cache refresh");

        try
        {
            await tracker.DrainAsync();
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Drain observed failure: {ex.GetBaseException().Message}");
        }
    }
}
Public Module FireAndForgetFix
    Public Async Function RunAsync(tracker As BackgroundTaskTracker) As Task
        Dim backgroundTask As Task = Task.Run(Async Function()
                                                  Await Task.Delay(100)
                                                  Throw New InvalidOperationException("Background operation failed.")
                                              End Function)

        tracker.Track(backgroundTask, "Cache refresh")

        Try
            Await tracker.DrainAsync()
        Catch ex As Exception
            Console.WriteLine($"Drain observed failure: {ex.GetBaseException().Message}")
        End Try
    End Function
End Module

Şunu sorabilirsiniz: DrainAsync yalnızca başlattığınız tek görevi bekliyorsa, neden doğrudan (c1 /> kullanmak yerine takipçi tamamen atlanmıyor? Tek bir yöntemdeki tek bir görev için bunu yapabilirsiniz. Bir bileşenin ömrü boyunca birçok farklı yerden görevler başlatıldığında izleyici sistem değerli hale gelir. Her bir çağrıcı görevini paylaşılan izleyiciye iletir ve kapanış sırasında yapılan tek bir DrainAsync çağrı, kaç tanesinin başlatıldığını veya kimin başlattığını bilmeden hepsini bekler. İzleyici tutarlı bir özel durum gözlem ilkesi de uygular: kayıtlı her görev aynı hata günlüğü devamlılığını alır, bu nedenle çalışmayı başlatan kod yolundan bağımsız olarak hiçbir özel durum fark edilmeden geçemez.

İzlenen desenin üç temel bileşeni şunlardır:

  • Görevi bir değişkene atayınbackgroundTask başvurusu tutmak, izlemeyi mümkün kılan şeydir. Başvuramayacağınız bir görev, boşaltamayacağınız veya gözlemleyemeyeceğiniz bir görevdir.
  • İzleyici ile kaydoluntracker.Track hata kaydı devamlılığını ekler ve görevi işlemdeki görevler kümesine ekler. Arka plan çalışmasının attığı herhangi bir istisna sessizce kaybolmak yerine bu devam aracılığıyla ortaya çıkar.
  • Kapatma sırasında tamamlatracker.DrainAsync hala çalışmakta olan süreçlerin tamamlanmasını bekler. Uçuş sırasında hiçbir in-flight işinin terkedilmeden emin olmak için bileşeninizden veya işlemden çıkmadan önce bunu arayın.

Takip edilmeyen ateşle ve unutun sonuçları

Döndürüleni Task izlemek yerine atarsanız sessiz hata oluşturursunuz:

public static class FireAndForgetPitfall
{
    public static async Task RunAsync()
    {
        _ = Task.Run(async () =>
        {
            await Task.Delay(100);
            throw new InvalidOperationException("Background operation failed.");
        });

        await Task.Delay(150);
        Console.WriteLine("Caller finished without observing background completion.");
    }
}
Public Module FireAndForgetPitfall
    Public Async Function RunAsync() As Task
        Dim discardedTask As Task = Task.Run(Async Function()
                                                 Await Task.Delay(100)
                                                 Throw New InvalidOperationException("Background operation failed.")
                                             End Function)

        Await Task.Delay(150)
        Console.WriteLine("Caller finished without observing background completion.")
    End Function
End Module

Görevi bırakmanın ardından üç sorun çıkar:

  • Sessiz özel durumlarInvalidOperationException arka plan işleminin sonucu hiçbir zaman gözlemlenmez. Çalışma zamanı, sonlandırma aşamasında bunu UnobservedTaskException'e yönlendirir; bu, belirlenimci değildir ve düzgün bir şekilde işlemek için çok geçtir.
  • Kapatma koordinasyonu yok — çağıran, işlemin bitmesini beklemeden devam eder ve çıkar. Kısa ömürlü bir işlemde veya kapatma zaman aşımı süresi olan bir sunucuda, arka plan çalışması iptal edilir veya tamamen kaybolur.
  • Görünürlük yok ; göreve başvuru olmadan işlemin başarılı mı, başarısız mı yoksa hala mı çalıştığını belirleyemezsiniz.

İzlenmeyen yangın ve unut işlem yalnızca aşağıdaki koşulların üçü de geçerli olduğunda kabul edilebilir: çalışma gerçekten isteğe bağlıdır, hata güvenli bir şekilde görmezden gelinebilir, ve işlem, beklenen süreç ömrü içinde sorunsuz tamamlanır. Kritik olmayan bir telemetri pingini kaydetmek, bu koşulların tümünün geçerli olabileceği bir örnektir.

Sahipliği açık tutun

Şu sahiplik modellerinden birini kullanın:

  • Task'i döndürün ve arayanların bunu beklemesini gerektirin.
  • Belirli bir sahip hizmetinde arka plan görevlerini takip edin.
  • Konağın kullanım süresine sahip olması için konak yönetimli bir arka plan soyutlaması kullanın.

Çağıran geri döndükten sonra işin devam etmesi gerekiyorsa, sahipliği açıkça aktarın. Örneğin, görevi hataları günlüğe kaydeden ve kapatma işlemine katılan bir izleyiciye teslim edin.

Arka plan görevlerinden özel durumlar Surface

Bırakılan görevler, sonlandırma ve ele alınmamış istisnaların işlenmesi gerçekleşene kadar sessizce başarısız olabilir. Bu zamanlama belirleyici değildir ve normal istek veya iş akışı işleme için çok geç.

Arka plan çalışmasını kuyruğa aldığınızda gözlem mantığı ekleyin. En azından bir işlem devamlılığında hata kaydedin. Kuyruğa alınan her işlemin aynı ilkeyi alması için merkezi bir izleyici tercih edin.

Özel durum aktarım ayrıntıları için bkz. Görev özel durumu işleme.

İptal ve kapatmayı koordine etme

Arka plan çalışmasını, uygulama veya işlem ömrünü temsil eden bir iptal belirtecine bağlayın. Kapatma sırasında:

  1. Yeni çalışmayı kabul etmeyi bırakın.
  2. Sinyal iptali.
  3. Sınırlanmış zaman aşımı ile izlenen görevleri bekleme.
  4. Tamamlanmamış işlemleri günlüğe kaydetme.

Bu akış kapatmayı öngörülebilir hale getirir ve kısmi yazmaları veya yarıda kesilmiş işlemleri önler.

GC, tamamlanmadan önce asenkron bir yöntemi toplayabilir mi?

Çalışma zamanı, devamlar hâlâ ona referans verdiğinde zaman uyumsuz durum makinesini aktif tutar. Genellikle durum makinesinin çöp toplama işlemi için zaman uyumsuz bir işlem kaybetmezsiniz.

Döndürülen görevin sahipliğini kaybederseniz, gerekli kaynakları erken serbest bırakırsanız veya işlemin tamamlanmadan sona ermesine izin verirseniz, yine de doğruluğu kaybedebilirsiniz. Görev sahipliğine ve eşgüdümlü kapatmaya odaklanın.

Ayrıca bakınız