Bir TPL Task nesnesinde Dispose () 'u çağırmamak kabul edilebilir mi?


123

Arka planda çalışacak bir görevi tetiklemek istiyorum. Görevlerin tamamlanmasını beklemek istemiyorum.

.Net 3.5'te bunu yapardım:

ThreadPool.QueueUserWorkItem(d => { DoSomething(); });

.Net 4'te TPL önerilen yoldur. Tavsiye ettiğim yaygın model şudur:

Task.Factory.StartNew(() => { DoSomething(); });

Bununla birlikte, StartNew()yöntem , Taskuygulayan bir nesne döndürür IDisposable. Bu, bu modeli öneren kişiler tarafından gözden kaçırılmış gibi görünüyor. Task.Dispose()Yöntemle ilgili MSDN belgeleri şunları söylüyor:

"Göreve son başvurunuzu bırakmadan önce daima Dispose arayın."

Tamamlanana kadar bir görev için dispose çağıramazsınız, bu nedenle ana iş parçacığının wait and call dispose'a sahip olmak, ilk etapta bir arka plan iş parçacığı üzerinde yapma noktasını ortadan kaldırır. Ayrıca, temizleme için kullanılabilecek tamamlanmış / bitmiş herhangi bir olay görünmüyor.

Task sınıfındaki MSDN sayfası bu konuda yorum yapmaz ve "Pro C # 2010 ..." kitabı aynı kalıbı önerir ve görevlerin atılmasıyla ilgili yorum yapmaz.

Sadece bırakırsam, sonlandırıcı sonunda onu yakalayacağını biliyorum, ancak bu gibi çok fazla ateş ve unut görevi yaptığımda ve sonlandırıcı iş parçacığı boğulduğunda bu geri gelip beni ısıracak mı?

Yani sorularım:

  • O demiyorlar için kabul edilebilir mi Dispose()üzerindeki Taskbu durumda sınıfın? Ve eğer öyleyse, neden ve riskler / sonuçlar var mı?
  • Bunu tartışan herhangi bir belge var mı?
  • Yoksa Taskkaçırdığım nesneyi elden çıkarmanın uygun bir yolu var mı?
  • Yoksa TPL ile ateş et ve unut görevlerini yapmanın başka bir yolu var mı?

Yanıtlar:


108

MSDN forumlarında bununla ilgili bir tartışma var .

Microsoft pfx ekibinin bir üyesi olan Stephen Toub şunu söylüyor:

Task.Dispose, Görev'in, görevin tamamlanmasını beklerken kullanılan bir olay tutamacını, bekleyen iş parçacığının gerçekten engellemesi gerektiğinde (dönme veya beklediği görevi yürütme potansiyeli yerine) potansiyel olarak sarmalaması nedeniyle mevcuttur. Yaptığınız tek şey sürekliliği kullanmaksa, bu olay tutacağı asla tahsis edilmeyecektir
...
şeylerle ilgilenmek için sonlandırmaya güvenmek muhtemelen daha iyidir.

Güncelleme (Ekim 2012)
Stephen Toub, Görevleri atmam gerekiyor mu? Başlıklı bir blog yayınladı . bu biraz daha detay verir ve .Net 4.5'teki iyileştirmeleri açıklar.

Özetle: TaskNesneleri% 99 oranında elden çıkarmanıza gerek yoktur .

Bir nesneyi elden çıkarmanın iki ana nedeni vardır: yönetilmeyen kaynakları zamanında, belirleyici bir şekilde serbest bırakmak ve nesnenin sonlandırıcısını çalıştırma maliyetinden kaçınmak. Bunların hiçbiri Taskçoğu zaman geçerli değildir:

  1. .Net 4.5, sadece bir zaman itibariyle bir Taskayırır iç bekleme kolu (sadece yönetilmeyen kaynak Tasknesne) açıkça kullandığınızda olduğu IAsyncResult.AsyncWaitHandlebir Taskve
  2. TaskNesnesi, bir finalizer sahip değildir; tutamacın kendisi bir sonlandırıcıya sahip bir nesneye sarılır, bu nedenle tahsis edilmediği sürece çalıştırılacak bir sonlandırıcı yoktur.

3
Teşekkürler, ilginç. Yine de MSDN belgelerine aykırı. MS'ten veya .net ekibinden bunun kabul edilebilir kod olduğuna dair resmi bir açıklama var mı? Bu tartışmanın sonunda ortaya atılan bir nokta da var: "Ya uygulama gelecekteki bir sürümde değişirse"
Simon P Stevens

Aslında, bu konudaki yanıtlayanın gerçekten de Microsoft'ta, görünüşe göre pfx ekibinde çalıştığını fark ettim, bu yüzden bunun resmi bir cevap olduğunu düşünüyorum. Ancak, her durumda çalışmayan alt kısmına doğru bir öneri var. Olası bir sızıntı varsa, güvenli olduğunu bildiğim ThreadPool.QueueUserWorkItem'e geri dönmem daha iyi olur mu?
Simon P Stevens

Evet, aramayabileceğiniz bir Dispose olması çok garip. Örneklere buradan msdn.microsoft.com/en-us/library/dd537610.aspx ve burada msdn.microsoft.com/en-us/library/dd537609.aspx bakarsanız, bunlar görevleri elden çıkarmazlar. Ancak, MSDN'deki kod örnekleri bazen çok kötü teknikler gösterir. Ayrıca adam soruyu cevapladı Microsoft için çalışıyor.
Kirill Muzykov

2
@Simon: (1) Alıntı yaptığınız MSDN dokümanı genel bir tavsiyedir, belirli durumlarda daha spesifik tavsiyeler vardır (örneğin , UI iş parçacığında kod çalıştırmak için kullanırken EndInvokeWinForms'da kullanılması gerekmemektedir BeginInvoke). (2) Stephen Toub, PFX'in etkili kullanımı konusunda (örneğin channel9.msdn.com'da ) düzenli bir konuşmacı olarak oldukça iyi bilinir , bu nedenle iyi bir rehberlik edebilecek biri varsa o zaman o kadar. İkinci paragrafına dikkat edin: İşleri sonlandırıcıya bırakmanın daha iyi olduğu zamanlar vardır.
Richard

12

Bu, Thread sınıfıyla aynı türden bir sorundur. 5 işletim sistemi tanıtıcısı kullanır, ancak IDisposable'ı uygulamaz. Orijinal tasarımcıların iyi kararı, Dispose () yöntemini çağırmanın elbette birkaç makul yolu vardır. Önce Join () 'i aramanız gerekir.

Task sınıfı buna dahili bir manuel sıfırlama olayı olan bir tutamaç ekler. Var olan en ucuz işletim sistemi kaynağı hangisidir. Elbette, Dispose () yöntemi, Thread'ın tükettiği 5 tanıtıcıyı değil, yalnızca bir olay tutamacını serbest bırakabilir. Evet, zahmet etme .

Görevin IsFaulted özelliğiyle ilgilenmeniz gerektiğine dikkat edin. Oldukça çirkin bir konu, bu MSDN Kitaplığı makalesinde bununla ilgili daha fazla bilgi edinebilirsiniz . Bununla doğru bir şekilde başa çıktıktan sonra, görevleri elden çıkarmak için kodunuzda iyi bir yeriniz olmalıdır.


6
Ancak bir görev Threadçoğu durumda bir oluşturmaz , ThreadPool'u kullanır.
svick

-1

Birinin bu yazıda gösterilen tekniği tarttığını görmek isterim: C # 'da Typesafe ateşle ve unut eşzamansız temsilci çağrısı

Görünüşe göre basit bir genişletme yöntemi, görevlerle etkileşimde bulunmanın tüm önemsiz durumlarını ele alacak ve bunun üzerine dispose çağırabilecektir.

public static void FireAndForget<T>(this Action<T> act,T arg1)
{
    var tsk = Task.Factory.StartNew( ()=> act(arg1),
                                     TaskCreationOptions.LongRunning);
    tsk.ContinueWith(cnt => cnt.Dispose());
}

3
Elbette bu, Taskgeri gönderilen örneği elden ContinueWithçıkarmada başarısız olur , ancak kabul edilen yanıt Stephen Toub'dan alınan alıntıdır: Bir görevde engelleyici bir bekleme gerçekleştirmezse, elden çıkarılacak hiçbir şey yoktur.
Richard

1
Richard'ın bahsettiği gibi, ContinueWith (...) ayrıca daha sonra elden çıkarılmayan ikinci bir Task nesnesi döndürür.
Simon P Stevens

1
Bu durumda, ContinueWith kodu aslında gereksiz olmaktan daha kötüdür çünkü sadece eski görevi elden çıkarmak için başka bir görevin oluşturulmasına neden olacaktır. Bu durumda, bu kod bloğuna bir engelleme beklemesi getirmenin yanı sıra, kendisine ilettiğiniz eylem temsilcisinin Görevleri de doğru şekilde yönetmeye çalışması dışında, temelde mümkün olmaz mıydı?
Chris Marisic

1
İkinci görevi yerine getirmek için lambdaların değişkenleri biraz zor bir şekilde nasıl yakaladığını kullanabilirsiniz. Task disper = null; disper = tsk.ContinueWith(cnt => { cnt.Dispose(); disper.Dispose(); });
Gideon Engelberth

Görünüşe göre çalışması gereken @GideonEngelberth. Disper, GC tarafından asla imha edilmemesi gerektiğinden, referansın hala orada / omuz silkme için geçerli olduğunu varsayarak, lambda kendisini atmaya çağırana kadar geçerli kalmalıdır. Belki boş bir deneme / yakalamaya ihtiyacı var?
Chris Marisic
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.