ถือว่าเป็นที่ยอมรับหรือไม่ที่จะไม่เรียก Dispose () บนวัตถุ TPL Task


123

ฉันต้องการทริกเกอร์งานให้ทำงานบนเธรดพื้นหลัง ฉันไม่อยากรอให้งานเสร็จ

ใน. net 3.5 ฉันจะทำสิ่งนี้:

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

ใน. net 4 TPL เป็นวิธีที่แนะนำ รูปแบบทั่วไปที่ฉันเห็นแนะนำคือ:

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

อย่างไรก็ตามStartNew()วิธีการส่งกลับวัตถุซึ่งการดำเนินการTask IDisposableดูเหมือนว่าคนที่แนะนำลายนี้จะมองข้ามไป เอกสาร MSDN เกี่ยวกับTask.Dispose()วิธีการกล่าวว่า:

"โทรหา Dispose ทุกครั้งก่อนที่คุณจะปล่อยการอ้างอิงล่าสุดของคุณไปที่งาน"

คุณไม่สามารถเรียกทิ้งในงานได้จนกว่าจะเสร็จสิ้นดังนั้นการรอเธรดหลักและเรียกใช้การกำจัดจะทำให้จุดที่ต้องทำบนเธรดพื้นหลังตั้งแต่แรก ดูเหมือนว่าจะไม่มีเหตุการณ์ที่เสร็จสมบูรณ์ / เสร็จสิ้นที่สามารถใช้ในการล้างข้อมูลได้

หน้า MSDN ในคลาสงานไม่แสดงความคิดเห็นเกี่ยวกับเรื่องนี้และหนังสือ "Pro C # 2010 ... " แนะนำรูปแบบเดียวกันและไม่แสดงความคิดเห็นเกี่ยวกับการกำจัดงาน

ฉันรู้ว่าถ้าฉันปล่อยทิ้งไว้โปรแกรมสุดท้ายจะจับมันได้ในที่สุด แต่นี่จะกลับมากัดฉันเมื่อฉันทำงานหนักและลืมงานแบบนี้และเธรด Finalizer ท่วมท้นหรือไม่?

ดังนั้นคำถามของฉันคือ:

  • ก็เป็นที่ยอมรับที่จะไม่เรียกDispose()ในTaskระดับในกรณีนี้หรือไม่? และถ้าเป็นเช่นนั้นทำไมและมีความเสี่ยง / ผลกระทบหรือไม่?
  • มีเอกสารที่กล่าวถึงเรื่องนี้หรือไม่?
  • หรือมีวิธีกำจัดTaskวัตถุที่ฉันพลาดไปอย่างเหมาะสมหรือไม่?
  • หรือมีวิธีอื่นในการดับเพลิงและลืมงานด้วย TPL หรือไม่?

คำตอบ:


108

มีการอภิปรายเกี่ยวกับเรื่องนี้ในฟอรัม MSDN

Stephen Toub สมาชิกของทีม Microsoft pfx กล่าวว่า:

Task.Dispose มีอยู่เนื่องจาก Task อาจตัดจุดจับเหตุการณ์ที่ใช้เมื่อรอให้งานเสร็จสมบูรณ์ในกรณีที่เธรดที่รอต้องบล็อกจริงๆ (ตรงข้ามกับการหมุนหรืออาจเรียกใช้งานที่รออยู่) หากสิ่งที่คุณทำคือการใช้ความต่อเนื่องการจัดการเหตุการณ์นั้นจะไม่ได้รับการจัดสรร
...
มันน่าจะดีกว่าที่จะพึ่งพาการสรุปเพื่อดูแลสิ่งต่างๆ

อัปเดต (ต.ค. 2555)
Stephen Toub ได้โพสต์บล็อกชื่อDo I need to dispose of Tasks? ซึ่งให้รายละเอียดเพิ่มเติมและอธิบายการปรับปรุงใน. Net 4.5

โดยสรุป: คุณไม่จำเป็นต้องทิ้งTaskวัตถุ 99% ของเวลา

มีเหตุผลหลักสองประการในการกำจัดวัตถุ: เพื่อเพิ่มทรัพยากรที่ไม่มีการจัดการในเวลาที่เหมาะสมวิธีที่กำหนดและหลีกเลี่ยงค่าใช้จ่ายในการเรียกใช้โปรแกรมสุดท้ายของออบเจ็กต์ สิ่งเหล่านี้ไม่เกี่ยวข้องกับTaskเวลาส่วนใหญ่:

  1. ตั้งแต่. Net 4.5 เวลาเดียวที่Taskจัดสรรจุดจับการรอภายใน (ทรัพยากรที่ไม่มีการจัดการเพียงอย่างเดียวในTaskออบเจ็กต์) คือเมื่อคุณใช้อย่างชัดเจนIAsyncResult.AsyncWaitHandleของTaskและ
  2. Taskวัตถุตัวเองไม่ได้มี finalizer นั้น แฮนเดิลนั้นห่อหุ้มอยู่ในอ็อบเจ็กต์ด้วย Finalizer ดังนั้นหากไม่มีการจัดสรรจะไม่มีตัวสุดท้ายให้รัน

3
ขอบคุณน่าสนใจ มันขัดกับเอกสาร MSDN มีคำที่เป็นทางการจาก MS หรือทีมงาน. net หรือไม่ว่านี่เป็นรหัสที่ยอมรับได้ นอกจากนี้ยังมีประเด็นที่ยกขึ้นในตอนท้ายของการสนทนาว่า "จะเกิดอะไรขึ้นถ้าการใช้งานเปลี่ยนแปลงในเวอร์ชันอนาคต"
Simon P Stevens

อันที่จริงฉันเพิ่งสังเกตว่าคำตอบในเธรดนั้นทำงานที่ microsoft ได้จริงดูเหมือนอยู่ในทีม pfx ดังนั้นฉันคิดว่านี่เป็นคำตอบที่เป็นทางการ แต่มีข้อเสนอแนะต่อด้านล่างของมันไม่ได้ผลในทุกกรณี หากมีการรั่วไหลที่อาจเกิดขึ้นฉันควรเปลี่ยนกลับไปใช้ ThreadPool ดีกว่าไหม QueueUserWorkItem ที่ฉันรู้ว่าปลอดภัย?
Simon P Stevens

ใช่เป็นเรื่องแปลกมากที่มี Dispose ที่คุณอาจไม่ได้เรียก หากคุณดูตัวอย่างที่นี่msdn.microsoft.com/en-us/library/dd537610.aspxและที่นี่msdn.microsoft.com/en-us/library/dd537609.aspxจะไม่ทิ้งงาน อย่างไรก็ตามตัวอย่างโค้ดใน MSDN บางครั้งแสดงเทคนิคที่ไม่ดีมาก คนที่แต่งตัวประหลาดยังตอบคำถามสำหรับ Microsoft
Kirill Muzykov

2
@Simon: (1) เอกสาร MSDN ที่คุณอ้างอิงเป็นคำแนะนำทั่วไปกรณีเฉพาะมีคำแนะนำที่เฉพาะเจาะจงมากขึ้น (เช่นไม่จำเป็นต้องใช้EndInvokeใน WinForms เมื่อใช้BeginInvokeเพื่อรันโค้ดบนเธรด UI) (2) Stephen Toub ค่อนข้างเป็นที่รู้จักในฐานะวิทยากรทั่วไปเกี่ยวกับการใช้ PFX อย่างมีประสิทธิภาพ (เช่นในchannel9.msdn.com ) ดังนั้นหากใครสามารถให้คำแนะนำที่ดีได้เขาก็เป็นเช่นนั้น สังเกตย่อหน้าที่สองของเขา: มีบางครั้งที่ทิ้งสิ่งต่างๆไว้ให้ผู้เข้ารอบสุดท้ายจะดีกว่า
Richard

12

นี่เป็นปัญหาประเภทเดียวกับคลาสเธรด มันใช้ระบบปฏิบัติการ 5 ตัวจัดการ แต่ไม่ได้ใช้ IDisposable การตัดสินใจที่ดีของนักออกแบบดั้งเดิมมีวิธีการที่สมเหตุสมผลไม่กี่วิธีในการเรียกเมธอด Dispose () คุณต้องโทรเข้าร่วม () ก่อน

คลาสงานจะเพิ่มหมายเลขอ้างอิงหนึ่งรายการซึ่งเป็นเหตุการณ์รีเซ็ตด้วยตนเองภายใน ซึ่งเป็นทรัพยากรระบบปฏิบัติการที่ถูกที่สุดที่มี แน่นอนว่าเมธอด Dispose () ของมันสามารถปล่อยจุดจัดการเหตุการณ์นั้นได้เพียงหนึ่งจุดจับเท่านั้นไม่ใช่ 5 ตัวจัดการที่เธรดใช้ ใช่ไม่รำคาญ

ระวังว่าคุณควรสนใจคุณสมบัติ IsFaulted ของงาน มันเป็นหัวข้อที่น่าเกลียดธรรมคุณสามารถอ่านเพิ่มเติมเกี่ยวกับเรื่องนี้ในบทความ MSDN ห้องสมุด เมื่อคุณจัดการกับสิ่งนี้อย่างถูกต้องคุณควรมีจุดที่ดีในรหัสของคุณเพื่อกำจัดงาน


6
แต่งานไม่ได้สร้างขึ้นThreadในกรณีส่วนใหญ่จะใช้ ThreadPool
svick

-1

ฉันชอบที่จะเห็นใครบางคนให้ความสำคัญกับเทคนิคที่แสดงในโพสต์นี้: typesafe fire-and-forget asynchronous delegate invocation ใน C #

ดูเหมือนว่าวิธีการขยายแบบง่ายๆจะจัดการกับกรณีที่ไม่สำคัญทั้งหมดในการโต้ตอบกับงานและสามารถเรียกใช้ทิ้งได้

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
แน่นอนว่าล้มเหลวในการกำจัดTaskอินสแตนซ์ที่ส่งคืนมาContinueWithแต่ดูคำพูดจาก Stephen Toub เป็นคำตอบที่ยอมรับได้: ไม่มีอะไรต้องกำจัดหากไม่มีสิ่งใดทำการปิดกั้นรอในงาน
Richard

1
ตามที่ Richard กล่าวถึง ContinueWith (... ) ยังส่งคืนอ็อบเจ็กต์งานที่สองซึ่งไม่ได้รับการกำจัด
Simon P Stevens

1
ด้วยเหตุนี้รหัส ContinueWith จึงแย่กว่า redudant เนื่องจากจะทำให้งานอื่นถูกสร้างขึ้นเพื่อกำจัดงานเก่า ด้วยวิธีนี้โดยพื้นฐานแล้วจะเป็นไปไม่ได้ที่จะแนะนำการรอการบล็อกในบล็อกโค้ดนี้นอกเหนือจากว่าการดำเนินการที่มอบหมายให้คุณส่งผ่านไปนั้นพยายามที่จะจัดการกับงานเองหรือไม่?
Chris Marisic

1
คุณอาจสามารถใช้วิธีที่แลมบดัสจับตัวแปรด้วยวิธีที่ยุ่งยากเล็กน้อยในการดูแลงานที่สอง Task disper = null; disper = tsk.ContinueWith(cnt => { cnt.Dispose(); disper.Dispose(); });
Gideon Engelberth

@GideonEngelberth ที่ดูเหมือนจะต้องทำงาน เนื่องจากความไม่เหมาะสมไม่ควรถูกกำจัดโดย GC จึงควรยังคงใช้ได้จนกว่าแลมบ์ดาจะเรียกตัวเองให้กำจัดโดยถือว่าการอ้างอิงยังคงใช้ได้อยู่ที่นั่น / ยักไหล่ อาจจะต้องลอง / จับรอบ ๆ หรือเปล่า?
Chris Marisic
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.