Apakah dianggap dapat diterima untuk tidak memanggil Dispose () pada objek Tugas TPL?

123

Saya ingin memicu tugas untuk dijalankan di thread latar belakang. Saya tidak ingin menunggu tugas selesai.

Dalam .net 3.5 saya akan melakukan ini:

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

Dalam .net 4 TPL adalah cara yang disarankan. Pola umum yang saya lihat direkomendasikan adalah:

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

Namun, StartNew()metode tersebut mengembalikan Taskobjek yang diimplementasikan IDisposable. Hal ini tampaknya diabaikan oleh orang-orang yang merekomendasikan pola ini. Dokumentasi MSDN tentang Task.Dispose()metode tersebut mengatakan:

"Selalu hubungi Buang sebelum Anda merilis referensi terakhir Anda ke Tugas."

Anda tidak dapat memanggil buang pada tugas hingga selesai, jadi meminta utas utama menunggu dan memanggil buang akan menggagalkan tujuan melakukan pada utas latar belakang sejak awal. Tampaknya juga tidak ada acara selesai / selesai yang dapat digunakan untuk pembersihan.

Halaman MSDN di kelas Tugas tidak mengomentari hal ini, dan buku "Pro C # 2010 ..." merekomendasikan pola yang sama dan tidak memberikan komentar tentang pembuangan tugas.

Saya tahu jika saya membiarkannya, finalizer akan menangkapnya pada akhirnya, tetapi apakah ini akan kembali dan menggigit saya ketika saya melakukan banyak tugas & melupakan tugas seperti ini dan thread finalizer menjadi kewalahan?

Jadi pertanyaan saya adalah:

  • Apakah dapat diterima untuk tidak memanggil Dispose()pada Taskkelas dalam kasus ini? Dan jika ya, mengapa dan apakah ada risiko / konsekuensi?
  • Apakah ada dokumentasi yang membahas hal ini?
  • Atau adakah cara yang tepat untuk membuang Taskobjek yang saya lewatkan?
  • Atau adakah cara lain untuk melakukan tugaskan & lupakan tugas dengan TPL?
Simon P Stevens
sumber
1
Terkait: Cara yang benar untuk Api-dan-Lupakan (lihat jawaban )
Simon P Stevens

Jawaban:

108

Ada diskusi tentang ini di forum MSDN .

Stephen Toub, anggota tim Microsoft pfx mengatakan ini:

Task.Dispose ada karena Task berpotensi membungkus sebuah event handle yang digunakan saat menunggu tugas selesai, jika thread menunggu benar-benar harus memblokir (sebagai lawan berputar atau berpotensi mengeksekusi tugas yang ditunggunya). Jika semua yang Anda lakukan adalah menggunakan kelanjutan, pegangan acara itu tidak akan pernah dialokasikan
...
kemungkinan lebih baik mengandalkan finalisasi untuk menangani berbagai hal.

Pembaruan (Okt 2012)
Stephen Toub telah memposting blog berjudul Do I need to membuang Tasks? yang memberikan beberapa detail lebih lanjut, dan menjelaskan peningkatan dalam .Net 4.5.

Singkatnya: Anda tidak perlu Task99% membuang objek setiap saat.

Ada dua alasan utama untuk membuang objek: untuk membebaskan sumber daya yang tidak terkelola secara tepat waktu, cara deterministik, dan untuk menghindari biaya menjalankan finalizer objek. Tak satu pun dari ini berlaku untuk Tasksebagian besar waktu:

  1. Pada .Net 4.5, satu-satunya waktu a Taskmengalokasikan gagang tunggu internal (satu-satunya sumber daya yang tidak dikelola dalam Taskobjek) adalah saat Anda secara eksplisit menggunakan IAsyncResult.AsyncWaitHandledari Task, dan
  2. The Taskobyek itu sendiri tidak memiliki finalizer; pegangan itu sendiri dibungkus dalam sebuah objek dengan finalizer, jadi kecuali dialokasikan, tidak ada finalizer untuk dijalankan.
Kirill Muzykov
sumber
3
Terima kasih, menarik. Ini bertentangan dengan dokumentasi MSDN. Apakah ada kata resmi dari MS atau tim .net bahwa ini adalah kode yang dapat diterima. Ada juga poin yang diangkat di akhir diskusi itu bahwa "bagaimana jika implementasi berubah di versi yang akan datang"
Simon P Stevens
Sebenarnya, saya baru saja memperhatikan bahwa penjawab di utas itu memang berfungsi di microsoft, tampaknya di tim pfx, jadi saya kira ini adalah semacam jawaban resmi. Tetapi ada saran di bagian bawah bahwa itu tidak berfungsi di semua kasus. Jika ada potensi kebocoran, lebih baik saya kembali ke ThreadPool.QueueUserWorkItem yang saya tahu aman?
Simon P Stevens
Ya, sangat aneh bahwa ada Buang yang tidak dapat Anda panggil. Jika Anda melihat contoh di sini msdn.microsoft.com/en-us/library/dd537610.aspx dan di sini msdn.microsoft.com/en-us/library/dd537609.aspx mereka tidak membuang tugas. Namun sampel kode di MSDN terkadang menunjukkan teknik yang sangat buruk. Juga pria yang menjawab pertanyaan itu bekerja untuk Microsoft.
Kirill Muzykov
2
@Simon: (1) Dokumen MSDN yang Anda kutip adalah saran umum, kasus tertentu memiliki saran yang lebih spesifik (mis. Tidak perlu digunakan EndInvokedi WinForms saat menggunakan BeginInvokeuntuk menjalankan kode pada utas UI). (2) Stephen Toub cukup dikenal sebagai pembicara reguler tentang penggunaan PFX yang efektif (misalnya di channel9.msdn.com ), jadi jika ada yang bisa memberikan panduan yang baik maka dia itu. Perhatikan paragraf keduanya: ada kalanya menyerahkan hal-hal kepada finalis lebih baik.
Richard
12

Ini adalah jenis masalah yang sama dengan kelas Thread. Ini mengkonsumsi 5 sistem operasi menangani tetapi tidak mengimplementasikan IDisposable. Keputusan bagus dari desainer asli, tentu saja ada beberapa cara yang masuk akal untuk memanggil metode Dispose (). Anda harus menelepon Gabung () terlebih dahulu.

Kelas Tugas menambahkan satu pegangan untuk ini, acara reset manual internal. Sumber daya sistem operasi mana yang termurah. Tentu saja, metode Dispose () hanya dapat merilis satu penanganan kejadian itu, bukan 5 pegangan yang dikonsumsi Thread. Ya, jangan repot-repot .

Berhati-hatilah karena Anda harus tertarik dengan properti IsFaulted tugas. Ini adalah topik yang cukup jelek, Anda dapat membaca lebih lanjut tentang itu di artikel Perpustakaan MSDN ini . Setelah Anda menangani ini dengan benar, Anda juga harus memiliki tempat yang bagus di kode Anda untuk membuang tugas.

Hans Passant
sumber
6
Tapi tugas tidak membuat Threaddalam banyak kasus, itu menggunakan ThreadPool.
svick
-1

Saya ingin melihat seseorang mempertimbangkan teknik yang ditunjukkan dalam posting ini: Pemanggilan delegasi asynchronous yang aman dan lupakan tipe aman di C #

Sepertinya metode ekstensi sederhana akan menangani semua kasus sepele dari interaksi dengan tugas dan dapat memanggil buang di atasnya.

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());
}
Chris Marisic
sumber
3
Tentu saja itu gagal untuk membuang Taskcontoh yang dikembalikan oleh ContinueWith, tetapi lihat kutipan dari Stephen Toub adalah jawaban yang diterima: tidak ada yang dibuang jika tidak ada yang melakukan penantian pemblokiran pada suatu tugas.
Richard
1
Seperti yang disebutkan Richard, ContinueWith (...) juga mengembalikan objek Tugas kedua yang kemudian tidak dibuang.
Simon P Stevens
1
Jadi seperti ini, kode ContinueWith sebenarnya lebih buruk daripada redudan karena akan menyebabkan tugas lain dibuat hanya untuk membuang tugas lama. Dengan cara ini berdiri pada dasarnya tidak mungkin untuk memasukkan menunggu pemblokiran ke dalam blok kode ini selain jika delegasi tindakan yang Anda lewati mencoba memanipulasi Tugas itu sendiri juga benar?
Chris Marisic
1
Anda mungkin dapat menggunakan bagaimana variabel menangkap lambda dengan cara yang sedikit rumit untuk menangani tugas kedua. Task disper = null; disper = tsk.ContinueWith(cnt => { cnt.Dispose(); disper.Dispose(); });
Gideon Engelberth
@GideonEngelberth yang tampaknya harus berfungsi. Karena disper tidak boleh dibuang oleh GC, disper harus tetap valid sampai lambda memanggil dirinya sendiri untuk dibuang, dengan asumsi referensi masih valid di sana / shrug. Mungkin perlu mencoba / menangkapnya kosong?
Chris Marisic