Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
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ın —
backgroundTaskbaş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 kaydolun —
tracker.Trackhata 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 tamamla —
tracker.DrainAsynchala ç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 durumlar —
InvalidOperationExceptionarka 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:
- Yeni çalışmayı kabul etmeyi bırakın.
- Sinyal iptali.
- Sınırlanmış zaman aşımı ile izlenen görevleri bekleme.
- 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.