ความแตกต่างระหว่าง Task.Run () และ Task.Factory.StartNew () คืออะไร


191

ฉันมีวิธีการ:

private static void Method()
{
    Console.WriteLine("Method() started");

    for (var i = 0; i < 20; i++)
    {
        Console.WriteLine("Method() Counter = " + i);
        Thread.Sleep(500);
    }

    Console.WriteLine("Method() finished");
}

และฉันต้องการเริ่มวิธีนี้ในงานใหม่ ฉันสามารถเริ่มงานใหม่เช่นนี้

var task = Task.Factory.StartNew(new Action(Method));

หรือสิ่งนี้

var task = Task.Run(new Action(Method));

แต่จะมีความแตกต่างใด ๆ ระหว่างและTask.Run() Task.Factory.StartNew()ทั้งคู่กำลังใช้ ThreadPool และเริ่มวิธีการ () ทันทีหลังจากสร้างอินสแตนซ์ของงาน เมื่อใดที่เราควรใช้ตัวแปรแรกและเมื่อสอง?


6
ที่จริงแล้ว StartNew ไม่จำเป็นต้องใช้ ThreadPool ดูบล็อกที่ฉันเชื่อมโยงในคำตอบ ปัญหาคือStartNewโดยค่าเริ่มต้นใช้TaskScheduler.Currentซึ่งอาจเป็นเธรดพูล แต่อาจเป็นเธรด UI
Scott Chamberlain

คำตอบ:


197

วิธีที่สองTask.Runได้รับการแนะนำใน. NET Framework รุ่นที่ใหม่กว่า (ใน. NET 4.5)

อย่างไรก็ตามวิธีแรกTask.Factory.StartNewให้โอกาสคุณในการกำหนดสิ่งที่มีประโยชน์มากมายเกี่ยวกับเธรดที่คุณต้องการสร้างในขณะที่Task.Runไม่ได้ให้สิ่งนี้

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

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

Task.Factory.StartNew(..., TaskCreationOptions.LongRunning);

ตามที่ระบุไว้ที่นี่ :

ดังนั้นใน. NET Framework 4.5 Developer Preview เราได้แนะนำวิธีการ Task.Run ใหม่ สิ่งนี้ไม่ทำให้ล้าสมัย Task.Fartory.StartNew แต่ควรคิดว่าเป็นวิธีที่รวดเร็วในการใช้งาน Task.Fartory.StartNew โดยไม่จำเป็นต้องระบุพารามิเตอร์จำนวนมาก มันเป็นทางลัด ในความเป็นจริง Task.Run ถูกนำไปใช้จริงในแง่ของตรรกะเดียวกันที่ใช้สำหรับ Task.Factory.StartNew เพียงแค่ผ่านพารามิเตอร์เริ่มต้นบางอย่าง เมื่อคุณผ่านการดำเนินการไปยังงาน Run:

Task.Run(someAction);

นั่นเทียบเท่ากับ:

Task.Factory.StartNew(someAction, 
    CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

4
ฉันมีชิ้นส่วนของรหัสที่ statemente that’s exactly equivalent toไม่ถือ
Emaborsa

7
@ Emaborsa ฉันจะขอบคุณถ้าคุณสามารถโพสต์ชิ้นส่วนของรหัสนี้และอธิบายการโต้แย้งของคุณ ขอบคุณล่วงหน้า !
Christos

4
@Emaborsa คุณสามารถสร้างส่วนสำคัญgist.github.comและแบ่งปันได้ อย่างไรก็ตามยกเว้นจากการแบ่งปันส่วนสำคัญนี้โปรดระบุว่าคุณได้รับผลลัพธ์ที่tha's exactly equivalent toไม่มีวลีนี้ได้อย่างไร ขอบคุณล่วงหน้า. มันจะเป็นการดีที่จะอธิบายด้วยความคิดเห็นในรหัสของคุณ ขอบคุณ :)
Christos

8
นอกจากนี้ยังเป็นมูลค่าการกล่าวขวัญว่า Task.Run แกะงานซ้อนกันโดยค่าเริ่มต้น ฉันแนะนำให้อ่านบทความนี้เกี่ยวกับความแตกต่างที่สำคัญ: blogs.msdn.microsoft.com/pfxteam/2011/10/24/…
Pawel Maga

1
@ The0bserver Nope TaskScheduler.Defaultมันเป็น กรุณาดูได้ที่นี่referencesource.microsoft.com/#mscorlib/system/threading/Tasks/...
Christos

46

คนพูดถึงแล้วว่า

Task.Run(A);

มีค่าเท่ากับ

Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

แต่ไม่มีใครพูดถึงเรื่องนั้น

Task.Factory.StartNew(A);

เทียบเท่ากับ:

Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Current);

ในขณะที่คุณสามารถดูสองพารามิเตอร์ที่แตกต่างกันTask.RunและTask.Factory.StartNew:

  1. TaskCreationOptions- Task.Runใช้งานTaskCreationOptions.DenyChildAttachซึ่งหมายความว่างานลูกไม่สามารถแนบกับผู้ปกครองพิจารณาสิ่งนี้:

    var parentTask = Task.Run(() =>
    {
        var childTask = new Task(() =>
        {
            Thread.Sleep(10000);
            Console.WriteLine("Child task finished.");
        }, TaskCreationOptions.AttachedToParent);
        childTask.Start();
    
        Console.WriteLine("Parent task finished.");
    });
    
    parentTask.Wait();
    Console.WriteLine("Main thread finished.");
    

    เมื่อเราเรียกparentTask.Wait(), childTaskจะไม่ถูกรอคอยมาแม้ว่าเราจะระบุTaskCreationOptions.AttachedToParentให้มันนี้เป็นเพราะTaskCreationOptions.DenyChildAttachเด็กห้ามจะแนบไปกับมัน ถ้าคุณเรียกใช้รหัสเดียวกันกับTask.Factory.StartNewแทนTask.Run, parentTask.Wait()จะรอchildTaskเพราะTask.Factory.StartNewการใช้งานTaskCreationOptions.None

  2. TaskScheduler- Task.Runใช้TaskScheduler.Defaultซึ่งหมายความว่าตัวกำหนดเวลางานเริ่มต้น (อันที่ทำงานในเธรดพูล) จะถูกใช้เพื่อเรียกใช้งานเสมอ Task.Factory.StartNewในทางกลับกันการใช้งานTaskScheduler.Currentซึ่งหมายถึงตัวกำหนดตารางเวลาของเธรดปัจจุบันอาจเป็นTaskScheduler.Defaultแต่ไม่เสมอไป ในความเป็นจริงเมื่อมีการพัฒนาWinformsหรือWPFแอปพลิเคชันจำเป็นต้องอัปเดต UI จากเธรดปัจจุบันเพื่อให้ผู้ใช้นี้ใช้ตัวTaskScheduler.FromCurrentSynchronizationContext()กำหนดเวลางานหากคุณสร้างงานที่ใช้TaskScheduler.FromCurrentSynchronizationContext()เวลานานโดยไม่ตั้งใจในงานที่ใช้ตัวกำหนดตารางเวลา UI จะถูกแช่แข็ง คำอธิบายโดยละเอียดเพิ่มเติมเกี่ยวกับสิ่งนี้สามารถพบได้ที่นี่

ดังนั้นโดยทั่วไปหากคุณไม่ได้ใช้งาน child ที่ซ้อนกันและต้องการให้งานของคุณถูกเรียกใช้บน Thread Pool จะเป็นการดีกว่าที่จะใช้Task.Runยกเว้นว่าคุณมีสถานการณ์ที่ซับซ้อนมากขึ้น


1
นี่เป็นเคล็ดลับที่ยอดเยี่ยมควรเป็นคำตอบที่ได้รับการยอมรับ
Ali Bayat


28

Task.Runได้นำมาใช้ในรุ่นกรอบ NET ใหม่และเป็นที่แนะนำ

เริ่มต้นด้วย. NET Framework 4.5 วิธี Task.Run เป็นวิธีที่แนะนำเพื่อเปิดใช้งานงานที่มีการคำนวณ ใช้เมธอด StartNew เมื่อคุณต้องการการควบคุมแบบละเอียดสำหรับงานที่ใช้เวลานานและมีการคำนวณ

Task.Factory.StartNewมีตัวเลือกมากขึ้นTask.Runเป็นชวเลข:

เมธอด Run จัดเตรียมชุดของโอเวอร์โหลดที่ทำให้ง่ายต่อการเริ่มภารกิจโดยใช้ค่าดีฟอลต์ มันเป็นทางเลือกที่มีน้ำหนักเบาเพื่อ overloads StartNew

และโดยย่อฉันหมายถึงทางลัดทางเทคนิค:

public static Task Run(Action action)
{
    return Task.InternalStartNew(null, action, null, default(CancellationToken), TaskScheduler.Default,
        TaskCreationOptions.DenyChildAttach, InternalTaskOptions.None, ref stackMark);
}

21

ตามโพสต์นี้โดยสตีเฟ่นเคลียร์งานโรงงานเริ่มใหม่ () เป็นสิ่งที่อันตราย:

ฉันเห็นโค้ดจำนวนมากบนบล็อกและในคำถาม SO ที่ใช้ Task.Factory.StartNew เพื่อหมุนงานในเธรดพื้นหลัง Stephen Toub มีบทความบล็อกที่ยอดเยี่ยมที่อธิบายว่าเหตุใด Task.Run จึงดีกว่า Task.Fartory.Start ใหม่ แต่ฉันคิดว่าผู้คนจำนวนมากยังไม่ได้อ่าน (หรือไม่เข้าใจ) ดังนั้นฉันจึงโต้แย้งกันเพิ่มภาษาที่มีพลังมากขึ้นและเราจะเห็นว่าสิ่งนี้เกิดขึ้นได้อย่างไร :) StartNew มีตัวเลือกมากกว่า Task.Run แต่มันค่อนข้างอันตรายอย่างที่เราเห็น คุณควรจะชอบ Task.Run มากกว่า Task.Factory.StartNew ในรหัส async

นี่คือเหตุผลที่แท้จริง:

  1. ไม่เข้าใจผู้ได้รับมอบหมาย async นี่เป็นจุดเดียวกับที่ 1 ในสาเหตุที่คุณต้องการใช้ StartNew ปัญหาคือเมื่อคุณส่งผู้แทน async ให้กับ StartNew เป็นเรื่องธรรมดาที่จะถือว่างานที่ส่งคืนนั้นแสดงถึงผู้รับมอบสิทธิ์นั้น อย่างไรก็ตามเนื่องจาก StartNew ไม่เข้าใจผู้รับมอบสิทธิ์ async สิ่งที่หมายถึงงานนั้นจริงๆเป็นเพียงการเริ่มต้นของผู้รับมอบสิทธิ์นั้น นี่เป็นหนึ่งในข้อผิดพลาดครั้งแรกที่ตัวแปลงสัญญาณพบเมื่อใช้ StartNew ในรหัส async
  2. สับสนกำหนดการเริ่มต้น ตกลงเคล็ดลับคำถามเวลา: ในรหัสด้านล่างวิธีการใดที่เธรด“ A” ทำงานอยู่
Task.Factory.StartNew(A);

private static void A() { }

คุณรู้ไหมว่ามันเป็นคำถามที่หลอกลวงใช่มั้ย หากคุณตอบ“ เธรดกลุ่มเธรด” ฉันขอโทษ แต่นั่นไม่ถูกต้อง “ A” จะทำงานบน TaskScheduler อะไรก็ตามที่กำลังทำงาน!

นั่นหมายความว่ามันอาจจะทำงานบนเธรด UI ได้หากการดำเนินการเสร็จสิ้นและกลับไปที่เธรด UI เนื่องจากการดำเนินการต่อเนื่องจาก Stephen Cleary อธิบายอย่างเต็มที่ในโพสต์ของเขา

ในกรณีของฉันฉันพยายามเรียกใช้งานในพื้นหลังเมื่อโหลดดาต้ากริดสำหรับมุมมองในขณะที่ยังแสดงภาพเคลื่อนไหวที่ไม่ว่าง ภาพเคลื่อนไหวยุ่งไม่ได้แสดงเมื่อใช้แต่ภาพเคลื่อนไหวที่แสดงอย่างถูกต้องเมื่อฉันเปลี่ยนTask.Factory.StartNew()Task.Run()

สำหรับรายละเอียดโปรดดูที่https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html


1

นอกเหนือจากความคล้ายคลึงกันเช่น Task.Run () เป็นชวเลขสำหรับ Task.Fartory.StartNew () มีความแตกต่างเล็กน้อยระหว่างพฤติกรรมของพวกเขาในกรณีของการซิงค์และตัวแทน async

สมมติว่ามีสองวิธีต่อไปนี้:

public async Task<int> GetIntAsync()
{
    return Task.FromResult(1);
}

public int GetInt()
{
    return 1;
}

พิจารณารหัสต่อไปนี้

var sync1 = Task.Run(() => GetInt());
var sync2 = Task.Factory.StartNew(() => GetInt());

ที่นี่ทั้ง sync1 และ sync2 เป็นประเภท Task <int>

อย่างไรก็ตามความแตกต่างมาในกรณีของวิธีการ async

var async1 = Task.Run(() => GetIntAsync());
var async2 = Task.Factory.StartNew(() => GetIntAsync());

ในสถานการณ์สมมตินี้ async1 เป็นประเภท Task <int> แต่ async2 เป็นประเภท Task <Task <int>>


Yeap เพราะTask.RunมีตัวในการทำงานแกะของUnwrapวิธีการ นี่คือการโพสต์บล็อกที่อธิบายถึงเหตุผลที่อยู่เบื้องหลังการตัดสินใจนี้
Theodor Zoulias

-8

ในใบสมัครของฉันซึ่งเรียกสองบริการผมเทียบทั้ง Task.Run และTask.Factory.StartNew ฉันพบว่าในกรณีของฉันทั้งคู่ทำงานได้ดี อย่างไรก็ตามอันที่สองนั้นเร็วกว่า


ฉันไม่รู้ว่าทำไมคำตอบนี้สมควรได้รับการโหวตลงคะแนน "10" ถึงแม้ว่ามันอาจจะไม่ถูกต้องหรือเป็นประโยชน์ ...
Mayer Spitzer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.