วิธีที่ง่ายที่สุดในการยิงและลืมวิธีใน C #?


150

ฉันเห็นใน WCF พวกเขามี[OperationContract(IsOneWay = true)]คุณลักษณะ แต่ดูเหมือนว่า WCF จะช้าและหนักเพียงแค่สร้างฟังก์ชั่นที่ไม่บล็อก โดยหลักการแล้วจะมีบางสิ่งบางอย่างเช่นสเตจโมฆะที่ไม่มีการปิดกั้นMethodFoo(){}แต่ฉันไม่คิดว่ามีอยู่จริง

วิธีที่เร็วที่สุดในการสร้างการเรียกใช้เมธอดที่ไม่ใช่บล็อกใน C # คืออะไร?

เช่น

class Foo
{
    static void Main()
    {
        FireAway(); //No callback, just go away
        Console.WriteLine("Happens immediately");
    }

    static void FireAway()
    {
        System.Threading.Thread.Sleep(5000);
        Console.WriteLine("5 seconds later");
    }
}

หมายเหตุ : ทุกคนที่อ่านสิ่งนี้ควรพิจารณาว่าพวกเขาต้องการวิธีการให้เสร็จหรือไม่ (ดู # 2 คำตอบยอดนิยม) หากวิธีการจะต้องเสร็จสิ้นในบางสถานที่เช่นแอปพลิเคชัน ASP.NET คุณจะต้องทำอะไรบางอย่างเพื่อป้องกันและรักษาเธรดให้มีชีวิตอยู่ มิฉะนั้นสิ่งนี้อาจนำไปสู่ ​​"ไฟ - ลืม - แต่ - ไม่ - ไม่ - จริง - ดำเนินการ" ในกรณีนี้แน่นอนมันจะง่ายกว่าที่จะไม่เขียนรหัสเลย ( คำอธิบายที่ดีเกี่ยวกับวิธีการทำงานใน ASP.NET )

คำตอบ:


273
ThreadPool.QueueUserWorkItem(o => FireAway());

(ห้าปีต่อมา...)

Task.Run(() => FireAway());

เป็นแหลมออกโดยluisperezphd


คุณชนะ. ไม่จริง ๆ นี่น่าจะเป็นเวอร์ชั่นที่สั้นที่สุดเว้นแต่คุณจะใส่เข้าไปในฟังก์ชั่นอื่น
OregonGhost

13
เมื่อคิดถึงเรื่องนี้ ... ในแอปพลิเคชั่นส่วนใหญ่นี่เป็นเรื่องปกติ แต่ในแอพคอนโซลของคุณจะออกก่อนที่ FireAway จะส่งอะไรไปที่คอนโซล ThreadPool เธรดเป็นเธรดพื้นหลังและตายเมื่อแอปตาย ในทางกลับกันการใช้เธรดจะไม่ทำงานเนื่องจากหน้าต่างคอนโซลจะหายไปก่อนที่ FireAway จะพยายามเขียนลงในหน้าต่าง

3
ไม่มีวิธีใดที่จะมีการเรียกใช้เมธอดที่ไม่มีการบล็อกซึ่งรับประกันว่าจะทำงานดังนั้นนี่จึงเป็นคำตอบที่ถูกต้องที่สุดสำหรับคำถาม IMO หากคุณจำเป็นต้องดำเนินการรับประกันแล้วปิดกั้นศักยภาพความต้องการที่จะได้รับการแนะนำผ่านทางโครงสร้างการควบคุมเช่น AutoResetEvent (ตามที่กล่าวถึง Kev)
Guvante

1
เมื่อต้องการเรียกใช้งานในหัวข้อที่เป็นอิสระของสระว่ายน้ำด้ายผมเชื่อว่าคุณยังสามารถไป(new Action(FireAway)).BeginInvoke()
แซม Harwell

2
สิ่งนี้เกี่ยวกับเรื่องนี้Task.Factory.StartNew(() => myevent());จากคำตอบstackoverflow.com/questions/14858261/ …
Luis Perez

39

สำหรับ C # 4.0 และใหม่กว่านั้นทำให้ฉันรู้สึกว่าคำตอบที่ดีที่สุดตอนนี้ได้รับจาก Ade Miller: วิธีที่ง่ายที่สุดในการทำไฟและลืมวิธีใน c # 4.0

Task.Factory.StartNew(() => FireAway());

หรือแม้กระทั่ง...

Task.Factory.StartNew(FireAway);

หรือ...

new Task(FireAway).Start();

ที่ไหนFireAwayเป็น

public static void FireAway()
{
    // Blah...
}

ดังนั้นโดยอาศัยอำนาจของคลาสและชื่อเมธอดนี้จะตีรุ่น threadpool โดยระหว่างหกและสิบเก้าตัวละครขึ้นอยู่กับคนที่คุณเลือก :)

ThreadPool.QueueUserWorkItem(o => FireAway());

11
ฉันสนใจที่จะมีคำตอบที่ดีที่สุดสำหรับคำถามที่ดี ฉันขอแนะนำให้ผู้อื่นทำเช่นเดียวกัน
แพทริค Szalapski

3
ตามstackoverflow.com/help/referencingคุณจะต้องใช้ blockquotes เพื่อระบุว่าคุณกำลังอ้างอิงจากแหล่งอื่น มันเป็นคำตอบของคุณดูเหมือนจะเป็นงานของคุณเองทั้งหมด ความตั้งใจของคุณโดยการโพสต์สำเนาคำตอบที่ถูกต้องสามารถทำได้ด้วยลิงก์ในความคิดเห็น
ทอม redfern

1
วิธีการส่งผ่านพารามิเตอร์ไปที่FireAway?
GuidoG

Task.Factory.StartNew(() => FireAway(foo));ผมเชื่อว่าคุณจะต้องใช้วิธีการแก้ปัญหาครั้งแรก:
แพทริค Szalapski

1
ต้องระมัดระวังในขณะที่ใช้Task.Factory.StartNew(() => FireAway(foo));foo ไม่สามารถแก้ไขได้ในวงตามที่อธิบายไว้ที่นี่และที่นี่
AAA

22

สำหรับ. NET 4.5:

Task.Run(() => FireAway());

15
ส่วนใหญ่แล้วนี่เป็นคำย่อที่Task.Factory.StartNew(() => FireAway(), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);เป็นไปตามblogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
Jim Geurts

2
เป็นการดีที่จะรู้ว่า @JimGeurts!
David Murdoch

เพื่อประโยชน์ของลูกหลานบทความที่เชื่อมโยงกับ @JimGeurts ในความคิดเห็นของพวกเขาคือ 404 ตอนนี้ (ขอบคุณ, MS!) เมื่อพิจารณาจาก dateramp ใน url บทความนี้โดย Stephen Toub จะเป็นบทความที่ถูกต้อง - devblogs.microsoft.com/pfxteam/…
Dan Atkinson

1
@DanAtkinson คุณถูกต้อง นี่คือต้นฉบับที่ archive.org ในกรณีที่ MS ย้ายไปอีกครั้ง: web.archive.org/web/20160226022029/http://blogs.msdn.com/b/…
David Murdoch

18

หากต้องการเพิ่มคำตอบของ Willหากเป็นแอปพลิเคชั่นคอนโซลเพียงแค่ใส่คำสั่งAutoResetEventa และ a WaitHandleเพื่อป้องกันการออกก่อนที่เธรดผู้ปฏิบัติงานจะเสร็จสมบูรณ์:

Using System;
Using System.Threading;

class Foo
{
    static AutoResetEvent autoEvent = new AutoResetEvent(false);

    static void Main()
    {
        ThreadPoolQueueUserWorkItem(new WaitCallback(FireAway), autoEvent);
        autoEvent.WaitOne(); // Will wait for thread to complete
    }

    static void FireAway(object stateInfo)
    {
        System.Threading.Thread.Sleep(5000);
        Console.WriteLine("5 seconds later");
        ((AutoResetEvent)stateInfo).Set();
    }
}

หากนี่ไม่ใช่แอปคอนโซลและคลาส C # ของฉันคือ COM Visible AutoEvent ของคุณจะใช้งานได้หรือไม่ autoEvent.WaitOne () ปิดกั้นอยู่หรือไม่
dan_l

5
@dan_l - ไม่มีความคิดทำไมไม่ถามคำถามนั้นเป็นคำถามใหม่และอ้างอิงถึงสิ่งนี้
Kev

@dan_l ใช่WaitOneบล็อกอยู่เสมอและAutoResetEventจะใช้ได้เสมอ
AaA

AutoResetEvent ใช้งานได้จริงที่นี่หากมีเธรดพื้นหลังเดียว ฉันสงสัยว่า Barrier จะดีขึ้นเมื่อใช้หลายเธรด docs.microsoft.com/en-us/dotnet/standard/threading/barrier
Martin Brown เมื่อ

@MartinBrown - ไม่แน่ใจว่าเป็นเรื่องจริง คุณสามารถมีอาร์เรย์ของWaitHandles หนึ่งที่มีของตัวเองในแต่ละและสอดคล้องกันAutoResetEvent หลังจากนั้นเพียงว่าThreadPool.QueueUserWorkItem WaitHandle.WaitAll(arrayOfWaitHandles)
Kev

15

วิธีง่ายๆในการสร้างและเริ่มเธรดด้วยแลมบ์ดาแบบไม่มีพารามิเตอร์:

(new Thread(() => { 
    FireAway(); 
    MessageBox.Show("FireAway Finished!"); 
}) { 
    Name = "Long Running Work Thread (FireAway Call)",
    Priority = ThreadPriority.BelowNormal 
}).Start();

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

ป้อนคำอธิบายรูปภาพที่นี่


+1 เมื่อคุณต้องการเธรดเฉพาะเนื่องจากกระบวนการทำงานนานหรือบล็อก
Paul Turner

12

วิธีที่แนะนำการทำเช่นนี้เมื่อคุณกำลังใช้ Asp.Net และสุทธิ 4.5.2 QueueBackgroundWorkItemคือการใช้ นี่คือคลาสตัวช่วย:

public static class BackgroundTaskRunner
{     
    public static void FireAndForgetTask(Action action)
    {
        HostingEnvironment.QueueBackgroundWorkItem(cancellationToken => // .Net 4.5.2 required
        {
            try
            {
                action();
            }
            catch (Exception e)
            {
                // TODO: handle exception
            }
        });
    }

    /// <summary>
    /// Using async
    /// </summary>
    public static void FireAndForgetTask(Func<Task> action)
    {
        HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken => // .Net 4.5.2 required
        {
            try
            {
                await action();
            }
            catch (Exception e)
            {
                // TODO: handle exception
            }
        });
    }
}

ตัวอย่างการใช้งาน:

BackgroundTaskRunner.FireAndForgetTask(() =>
{
    FireAway();
});

หรือใช้ async:

BackgroundTaskRunner.FireAndForgetTask(async () =>
{
    await FireAway();
});

สิ่งนี้ใช้งานได้ดีบนเว็บไซต์ Azure

การอ้างอิง: การใช้ QueueBackgroundWorkItem เพื่อกำหนดเวลางานเบื้องหลังจากแอปพลิเคชัน ASP.NET ใน. NET 4.5.2


7

การโทรเริ่มต้นโทรเข้าและไม่ติดต่อ EndInvoke ไม่ใช่วิธีที่ดี คำตอบนั้นง่าย: เหตุผลที่คุณควรจะเรียก EndInvoke นั้นเป็นเพราะผลของการร้องขอ (แม้ว่าจะไม่มีค่าที่ส่งคืน) จะต้องถูกแคชโดย. NET จนกว่าจะเรียก EndInvoke ตัวอย่างเช่นหากโค้ดที่เรียกใช้แสดงข้อยกเว้นดังนั้นข้อยกเว้นจะถูกแคชไว้ในข้อมูลการเรียกใช้ จนกว่าคุณจะเรียก EndInvoke มันจะยังคงอยู่ในหน่วยความจำ หลังจากที่คุณเรียก EndInvoke หน่วยความจำสามารถปล่อย สำหรับกรณีนี้เป็นไปได้ที่หน่วยความจำจะยังคงอยู่จนกว่ากระบวนการจะปิดตัวลงเนื่องจากข้อมูลได้รับการดูแลภายในโดยรหัสการเรียกใช้ ฉันเดาว่า GC อาจจะเก็บรวบรวมมันในที่สุด แต่ฉันไม่รู้ว่า GC จะรู้ได้อย่างไรว่าคุณได้ละทิ้งข้อมูลเทียบกับการใช้เวลานานมากในการเรียกคืน ฉันสงสัยว่ามันจะทำ ดังนั้นหน่วยความจำรั่วสามารถเกิดขึ้นได้

พบข้อมูลเพิ่มเติมได้ที่http://haacked.com/archive/2009/01/09/asynchronous-fire-and-forget-with-lambdas.aspx


3
GC สามารถบอกได้ว่าเมื่อใดที่สิ่งที่ถูกทอดทิ้งหากไม่มีการอ้างอิงอยู่ หากBeginInvokeส่งคืนออบเจกต์ wrapper ที่เก็บการอ้างอิงถึง แต่ไม่ได้อ้างอิงโดยออบเจกต์ที่เก็บข้อมูลผลจริงวัตถุ wrapper จะมีสิทธิ์ได้รับการสรุปเมื่อการอ้างอิงทั้งหมดถูกทอดทิ้ง
supercat

ฉันถามว่านี่คือ "ไม่ใช่วิธีที่ดี"; ทดสอบโดยใช้เงื่อนไขข้อผิดพลาดที่คุณกังวลและดูว่าคุณมีปัญหาหรือไม่ แม้ว่าการรวบรวมขยะจะไม่สมบูรณ์แบบ แต่ก็อาจไม่ส่งผลกระทบต่อแอปพลิเคชันส่วนใหญ่อย่างเห็นได้ชัด สำหรับ Microsoft "คุณสามารถโทรหา EndInvoke เพื่อดึงค่าส่งคืนจากผู้รับมอบสิทธิ์หากจำเป็น แต่ไม่จำเป็น EndInvoke จะปิดกั้นจนกว่าจะสามารถเรียกคืนค่าได้" จาก: msdn.microsoft.com/en-us/library/0b1bf3y3(v=vs.90).aspx
Abacus

5

เกือบ 10 ปีต่อมา:

Task.Run(FireAway);

ฉันจะเพิ่มการจัดการข้อยกเว้นและการเข้าสู่ระบบใน FireAway


3

. NET 2.0 ที่ง่ายที่สุดและวิธีที่ใหม่กว่านั้นใช้ Asynchnonous Programming Model (เช่น BeginInvoke กับผู้รับมอบสิทธิ์):

static void Main(string[] args)
{
      new MethodInvoker(FireAway).BeginInvoke(null, null);

      Console.WriteLine("Main: " + Thread.CurrentThread.ManagedThreadId);

      Thread.Sleep(5000);
}

private static void FireAway()
{
    Thread.Sleep(2000);

    Console.WriteLine("FireAway: " + Thread.CurrentThread.ManagedThreadId );  
}

ก่อนหน้านี้Task.Run()มีอยู่นี่อาจเป็นวิธีที่รัดกุมที่สุดในการทำเช่นนี้
binki
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.