Cách đơn giản nhất để thực hiện phương pháp chữa cháy và quên trong C #?


150

Tôi thấy trong WCF họ có [OperationContract(IsOneWay = true)]thuộc tính. Nhưng WCF có vẻ như chậm và nặng nề chỉ để tạo ra một chức năng không chặn. Lý tưởng nhất là sẽ có một cái gì đó giống như bỏ chặn khoảng trống tĩnh MethodFoo(){}, nhưng tôi không nghĩ rằng nó tồn tại.

Cách nhanh nhất để tạo một cuộc gọi phương thức không chặn trong C # là gì?

Ví dụ

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");
    }
}

NB : Mọi người đọc điều này nên suy nghĩ nếu họ thực sự muốn phương pháp kết thúc. (Xem câu trả lời hàng đầu số 2) Nếu phương thức phải kết thúc, thì ở một số nơi, như ứng dụng ASP.NET, bạn sẽ cần phải làm gì đó để chặn và giữ cho luồng tồn tại. Mặt khác, điều này có thể dẫn đến "lửa quên-nhưng-không bao giờ thực sự thực thi", trong trường hợp đó, tất nhiên, sẽ đơn giản hơn nếu không viết mã nào cả. ( Một mô tả hay về cách thức hoạt động trong ASP.NET )

Câu trả lời:


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

(năm năm sau...)

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

như được chỉ ra bởi luisperezphd .


Bạn thắng. Không, thực sự, đây dường như là phiên bản ngắn nhất, trừ khi bạn gói nó vào một chức năng khác.
OregonGhost

13
Suy nghĩ về điều này ... trong hầu hết các ứng dụng, điều này là tốt, nhưng trong ứng dụng console của bạn sẽ thoát ra trước khi FireAway gửi bất cứ thứ gì đến giao diện điều khiển. Chủ đề ThreadPool là chủ đề nền và chết khi ứng dụng chết. Mặt khác, sử dụng Thread sẽ không hoạt động vì cửa sổ giao diện điều khiển sẽ biến mất trước khi FireAway cố gắng ghi vào cửa sổ.

3
Không có cách nào để có một cuộc gọi phương thức không chặn được đảm bảo để chạy, vì vậy đây thực sự là câu trả lời chính xác nhất cho câu hỏi IMO. Nếu bạn cần đảm bảo thực thi thì việc chặn tiềm năng cần được giới thiệu thông qua cấu trúc điều khiển như AutoResetEvent (như Kev đã đề cập)
Guvante

1
Để chạy tác vụ trong một luồng độc lập với nhóm luồng, tôi tin rằng bạn cũng có thể đi(new Action(FireAway)).BeginInvoke()
Sam Harwell

2
Điều gì về điều này Task.Factory.StartNew(() => myevent());từ câu trả lời stackoverflow.com/questions/14858261/ Kẻ
Luis Perez

39

Đối với C # 4.0 và mới hơn, tôi nhận ra rằng câu trả lời hay nhất hiện được đưa ra ở đây bởi Ade Miller: Cách đơn giản nhất để thực hiện phương pháp chữa cháy và quên trong c # 4.0

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

Hoặc thậm chí ...

Task.Factory.StartNew(FireAway);

Hoặc là...

new Task(FireAway).Start();

Trong trường hợp FireAway

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

Vì vậy, nhờ vào độ chính xác của lớp và tên phương thức, điều này đánh bại phiên bản luồng bằng từ sáu đến mười chín ký tự tùy thuộc vào cái bạn chọn :)

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

11
Tôi quan tâm nhất là có câu trả lời tốt nhất dễ dàng có sẵn cho các câu hỏi hay. Tôi chắc chắn sẽ khuyến khích những người khác làm như vậy.
Patrick Szalapski

3
Theo stackoverflow.com/help/references , bạn được yêu cầu sử dụng blockquote để cho biết rằng bạn đang trích dẫn từ một nguồn khác. Vì đó là câu trả lời của bạn dường như là tất cả công việc của riêng bạn. Ý định của bạn bằng cách đăng lại một bản sao chính xác của câu trả lời có thể đã đạt được với một liên kết trong một bình luận.
tom redfern

1
Làm thế nào để truyền tham số đến FireAway?
GuidoG

Tôi tin rằng bạn phải sử dụng giải pháp đầu tiên : Task.Factory.StartNew(() => FireAway(foo));.
Patrick Szalapski

1
thận trọng trong khi sử dụng Task.Factory.StartNew(() => FireAway(foo));foo không thể được sửa đổi trong vòng lặp như được giải thích ở đâyđây
AaA

22

Đối với .NET 4.5:

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

15
Fyi, cái này chủ yếu viết tắt Task.Factory.StartNew(() => FireAway(), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);theo blog.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
Jim Geurts

2
Thật tốt khi biết, @JimGeurts!
David Murdoch

Vì lợi ích của hậu thế, bài viết được liên kết là @JimGeurts trong bình luận của họ hiện là 404'd (cảm ơn, MS!). Đưa ra dấu dữ liệu trong url đó, bài viết này của Stephen Toub dường như là chính xác - devbloss.microsoft.com/pfxteam/ Kẻ
Dan Atkinson

1
@DanAtkinson bạn đúng. Đây là bản gốc tại archive.org, chỉ trong trường hợp MS di chuyển lại: web.archive.org/web/20160226022029/http://bloss.msdn.com/b/ Lỗi
David Murdoch

18

Để thêm vào câu trả lời của Will , nếu đây là một ứng dụng bảng điều khiển, chỉ cần ném vào AutoResetEventWaitHandleđể ngăn nó thoát ra trước khi luồng công nhân hoàn thành:

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();
    }
}

nếu đây không phải là ứng dụng bảng điều khiển và lớp C # của tôi có thể nhìn thấy COM, AutoEvent của bạn có hoạt động không? Là autoEvent.WaitOne () đang chặn?
dan_l

5
@dan_l - Không có ý kiến, tại sao không hỏi đó là một câu hỏi mới và làm một tài liệu tham khảo cho câu hỏi này.
Kev

@dan_l, vâng, WaitOneluôn luôn chặn và AutoResetEventsẽ luôn hoạt động
AaA

AutoResetEvent chỉ thực sự hoạt động ở đây nếu có một luồng nền duy nhất. Tôi tự hỏi nếu một Barrier sẽ tốt hơn khi sử dụng nhiều luồng. docs.microsoft.com/en-us/dotnet/stiteria/threading/barrier
Martin Brown

@MartinBrown - không chắc đó là sự thật. Bạn có thể có một mảng WaitHandles, mỗi mảng có một AutoResetEventvà một tương ứng ThreadPool.QueueUserWorkItem. Sau đó chỉ WaitHandle.WaitAll(arrayOfWaitHandles).
Kev

15

Một cách dễ dàng là tạo và bắt đầu một chuỗi với lambda không tham số:

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

Bằng cách sử dụng phương thức này trên ThreadPool.QueueUserWorkItem, bạn có thể đặt tên cho chủ đề mới của mình để giúp gỡ lỗi dễ dàng hơn. Ngoài ra, đừng quên sử dụng xử lý lỗi rộng rãi trong thói quen của bạn bởi vì bất kỳ trường hợp ngoại lệ nào chưa được xử lý bên ngoài trình gỡ lỗi sẽ đột ngột làm hỏng ứng dụng của bạn:

nhập mô tả hình ảnh ở đây


+1 cho khi bạn cần một luồng chuyên dụng do quá trình chặn hoặc chạy dài.
Paul Turner

12

Cách được đề xuất để làm điều này khi bạn đang sử dụng Asp.Net và .Net 4.5.2 là sử dụng QueueBackgroundWorkItem. Đây là một lớp người trợ giúp:

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
            }
        });
    }
}

Ví dụ sử dụng:

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

hoặc sử dụng async:

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

Điều này hoạt động rất tốt trên các trang web Azure.

Tài liệu tham khảo: Sử dụng QueueBackgroundWorkItem để lên lịch công việc nền từ ứng dụng ASP.NET trong .NET 4.5.2


7

Gọi BeginInvoke và không bắt EndInvoke không phải là một cách tiếp cận tốt. Câu trả lời rất đơn giản: Lý do mà bạn nên gọi EndInvoke là vì kết quả của lệnh gọi (ngay cả khi không có giá trị trả về) phải được lưu trữ bởi .NET cho đến khi EndInvoke được gọi. Ví dụ: nếu mã được gọi ném ra một ngoại lệ thì ngoại lệ đó được lưu trong bộ nhớ dữ liệu. Cho đến khi bạn gọi EndInvoke, nó vẫn còn trong bộ nhớ. Sau khi bạn gọi EndInvoke, bộ nhớ có thể được giải phóng. Trong trường hợp cụ thể này, có thể bộ nhớ sẽ duy trì cho đến khi quá trình tắt vì dữ liệu được duy trì bên trong bởi mã gọi. Tôi đoán rằng cuối cùng thì GC có thể thu thập nó nhưng tôi không biết làm thế nào mà GC biết rằng bạn đã từ bỏ dữ liệu so với việc chỉ mất một thời gian rất dài để lấy lại nó. Tôi nghi ngờ nó không. Do đó rò rỉ bộ nhớ có thể xảy ra.

Có thể tìm thấy nhiều hơn trên http://haacked.com/archive/2009/01/09/asynncous-fire-and-forget-with-lambdas.aspx


3
GC có thể biết khi nào mọi thứ bị bỏ rơi nếu không có tài liệu tham khảo nào tồn tại với chúng. Nếu BeginInvoketrả về một đối tượng trình bao bọc chứa tham chiếu đến, nhưng không được tham chiếu bởi, đối tượng chứa dữ liệu kết quả thực, đối tượng trình bao bọc sẽ trở thành đủ điều kiện để hoàn thiện sau khi tất cả các tham chiếu đến nó bị bỏ qua.
supercat

Tôi nghi ngờ nói rằng đây là "không phải là một cách tiếp cận tốt"; kiểm tra nó, với các điều kiện lỗi bạn quan tâm, và xem nếu bạn có vấn đề. Ngay cả khi bộ sưu tập rác không hoàn hảo, nó có thể sẽ không ảnh hưởng đến hầu hết các ứng dụng. Theo Microsoft, "Bạn có thể gọi EndInvoke để lấy giá trị trả về từ đại biểu, nếu cần thiết, nhưng điều này là không bắt buộc. EndInvoke sẽ chặn cho đến khi có thể lấy lại giá trị trả về." từ: msdn.microsoft.com/en-us/l Library / 0b1bf3y3 (v = vs.90) .aspx
Bàn tính

5

Gần 10 năm sau:

Task.Run(FireAway);

Tôi sẽ thêm xử lý ngoại lệ và đăng nhập vào FireAway


3

Cách tiếp cận .NET 2.0 đơn giản nhất và sau này là sử dụng Mô hình lập trình Asynchnonous (nghĩa là BeginInvoke trên một đại biểu):

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 );  
}

Trước khi Task.Run()tồn tại, đây có lẽ là cách ngắn gọn nhất để làm điều này.
binki
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.