Cara paling mudah untuk melakukan api dan melupakan metode dalam C #?

150

Saya melihat di WCF mereka punya [OperationContract(IsOneWay = true)] atribut. Tapi WCF tampaknya agak lambat dan berat hanya untuk membuat fungsi nonblocking. Idealnya akan ada sesuatu seperti static void nonblocking MethodFoo(){}, tapi saya rasa itu tidak ada.

Apa cara tercepat untuk membuat panggilan metode nonblocking di C #?

Misalnya

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 : Semua orang yang membaca ini harus memikirkan apakah mereka benar-benar ingin metode ini selesai. (Lihat # 2 jawaban teratas) Jika metode ini harus selesai, maka di beberapa tempat, seperti aplikasi ASP.NET, Anda perlu melakukan sesuatu untuk memblokir dan menjaga utas tetap hidup. Kalau tidak, ini bisa mengarah pada "fire-forget-but-never-sebenarnya-execute", dalam hal ini, tentu saja, akan lebih mudah untuk menulis tanpa kode sama sekali. ( Deskripsi yang baik tentang cara kerjanya di ASP.NET )

MatthewMartin
sumber

Jawaban:

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

(lima tahun kemudian...)

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

seperti yang ditunjukkan oleh luisperezphd .


sumber
Kamu menang. Tidak, sungguh, ini tampaknya merupakan versi terpendek, kecuali jika Anda merangkumnya ke dalam fungsi lain.
OregonGhost
13
Memikirkan hal ini ... di sebagian besar aplikasi ini baik-baik saja, tetapi di dalam Anda aplikasi konsol akan keluar sebelum FireAway mengirim apa pun ke konsol. Utas ThreadPool adalah utas latar belakang dan mati saat aplikasi mati. Di sisi lain, menggunakan Thread tidak akan berfungsi karena jendela konsol akan hilang sebelum FireAway mencoba menulis ke jendela.
3
Tidak ada cara untuk memiliki panggilan metode non-pemblokiran yang dijamin berjalan, jadi ini sebenarnya jawaban yang paling akurat untuk pertanyaan IMO. Jika Anda perlu menjamin eksekusi maka kemungkinan pemblokiran perlu diperkenalkan melalui struktur kontrol seperti AutoResetEvent (seperti yang disebutkan Kev)
Guvante
1
Untuk menjalankan tugas di utas independen dari kumpulan utas, saya yakin Anda juga bisa pergi(new Action(FireAway)).BeginInvoke()
27880 Sam Harwell
2
Bagaimana dengan ini Task.Factory.StartNew(() => myevent());dari jawaban stackoverflow.com/questions/14858261/…
Luis Perez
39

Untuk C # 4.0 dan yang lebih baru, menurut saya jawaban terbaik sekarang diberikan di sini oleh Ade Miller: Cara paling sederhana untuk melakukan api dan melupakan metode dalam c # 4.0

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

Atau bahkan...

Task.Factory.StartNew(FireAway);

Atau...

new Task(FireAway).Start();

dimana FireAwayadalah

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

Jadi berdasarkan kelas dan nama metode metode ini mengalahkan versi threadpool antara enam dan sembilan belas karakter tergantung pada yang Anda pilih :)

ThreadPool.QueueUserWorkItem(o => FireAway());
Patrick Szalapski
sumber
11
Saya paling tertarik untuk mendapatkan jawaban terbaik dengan mudah pada pertanyaan yang bagus. Saya pasti akan mendorong orang lain untuk melakukan hal yang sama.
Patrick Szalapski
3
Menurut stackoverflow.com/help/referencing , Anda diharuskan menggunakan blockquotes untuk menunjukkan bahwa Anda mengutip dari sumber lain. Karena jawaban Anda tampaknya adalah semua pekerjaan Anda sendiri. Niat Anda dengan memposting ulang salinan jawaban yang tepat dapat dicapai dengan tautan dalam komentar.
tom redfern
1
bagaimana cara menyampaikan parameter FireAway?
GuidoG
Saya percaya Anda harus menggunakan solusi pertama: Task.Factory.StartNew(() => FireAway(foo));.
Patrick Szalapski
1
hati-hati saat menggunakan Task.Factory.StartNew(() => FireAway(foo));foo tidak dapat dimodifikasi dalam loop seperti yang dijelaskan di sini dan di sini
AaA
22

Untuk .NET 4.5:

Task.Run(() => FireAway());
David Murdoch
sumber
15
Fyi, ini terutama singkat untuk Task.Factory.StartNew(() => FireAway(), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);menurut blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
Jim Geurts
2
Senang tahu, @JimGeurts!
David Murdoch
Demi kepentingan keturunan, artikel yang ditautkan dengan @JimGeurts dalam komentar mereka sekarang sudah 404 (terima kasih, MS!). Mengingat datestamp di url itu, artikel oleh Stephen Toub ini tampaknya yang benar - devblogs.microsoft.com/pfxteam/…
Dan Atkinson
1
@DanAtkinson Anda benar. Ini yang asli di archive.org, kalau-kalau MS memindahkannya lagi: web.archive.org/web/20160226022029/http://blogs.msdn.com/b/…
David Murdoch
18

Untuk menambah jawaban Will , jika ini adalah aplikasi konsol, cukup masukkan a AutoResetEventdan a WaitHandleuntuk mencegahnya keluar sebelum utas pekerja selesai:

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();
    }
}
Kev
sumber
jika ini bukan aplikasi konsol dan kelas C # saya COM Visible, akankah AutoEvent Anda berfungsi? Apakah autoEvent.WaitOne () memblokir?
dan_l
5
@dan_l - Tidak tahu, mengapa tidak menanyakan itu sebagai pertanyaan baru dan buat referensi untuk yang ini.
Kev
@dan_l, ya WaitOneselalu memblokir dan AutoResetEventakan selalu bekerja
AaA
AutoResetEvent hanya benar-benar berfungsi di sini jika ada utas latar belakang tunggal. Saya ingin tahu apakah Penghalang akan lebih baik ketika beberapa utas sedang digunakan. docs.microsoft.com/en-us/dotnet/standard/threading/barrier
Martin Brown
@ MartinBrown - tidak yakin itu benar. Anda dapat memiliki larik WaitHandles, masing-masing dengan lariknya sendiri AutoResetEventdan yang sesuai ThreadPool.QueueUserWorkItem. Setelah itu baru saja WaitHandle.WaitAll(arrayOfWaitHandles).
Kev
15

Cara mudah adalah membuat dan memulai utas dengan parameter lambda:

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

Dengan menggunakan metode ini di atas ThreadPool.QueueUserWorkItem Anda dapat memberi nama utas baru Anda agar lebih mudah untuk debugging. Selain itu, jangan lupa untuk menggunakan penanganan kesalahan yang luas dalam rutinitas Anda karena pengecualian yang tidak ditangani di luar debugger akan secara tiba-tiba merusak aplikasi Anda:

masukkan deskripsi gambar di sini

Robert Venables
sumber
Memberi +1 untuk saat Anda memerlukan utas khusus karena proses yang berjalan lama atau memblokir.
Paul Turner
12

Cara yang disarankan untuk melakukan ini ketika Anda menggunakan Asp.Net dan .Net 4.5.2 adalah dengan menggunakan QueueBackgroundWorkItem. Ini adalah kelas pembantu:

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

Contoh penggunaan:

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

atau menggunakan async:

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

Ini berfungsi dengan baik di Situs Web Azure.

Referensi: Menggunakan QueueBackgroundWorkItem untuk Menjadwalkan Pekerjaan Latar Belakang dari Aplikasi ASP.NET di .NET 4.5.2

Augusto Barreto
sumber
7

Memanggil beginInvoke dan tidak menangkap EndInvoke bukanlah pendekatan yang baik. Jawabannya sederhana: Alasan mengapa Anda harus memanggil EndInvoke adalah karena hasil doa (bahkan jika tidak ada nilai balik) harus di-cache oleh .NET hingga EndInvoke dipanggil. Sebagai contoh jika kode yang dipanggil melempar pengecualian maka pengecualian di-cache dalam data doa. Sampai Anda memanggil EndInvoke, ia tetap ada dalam memori. Setelah Anda menelepon EndInvoke, memori dapat dirilis. Untuk kasus khusus ini dimungkinkan memori akan tetap sampai proses dimatikan karena data dikelola secara internal oleh kode doa. Saya kira GC akhirnya akan mengumpulkannya tetapi saya tidak tahu bagaimana GC akan tahu bahwa Anda telah meninggalkan data vs hanya mengambil waktu yang sangat lama untuk mengambilnya. Saya ragu itu. Karenanya kebocoran memori dapat terjadi.

Lebih banyak dapat ditemukan di http://haacked.com/archive/2009/01/09/asynchronous-fire-and-forget-with-lambdas.aspx

Manoj Aggarwal
sumber
3
GC dapat mengetahui kapan hal-hal ditinggalkan jika tidak ada referensi. Jika BeginInvokemengembalikan objek pembungkus yang memegang referensi, tetapi tidak direferensikan oleh, objek yang menyimpan data hasil nyata, objek pembungkus akan memenuhi syarat untuk finalisasi setelah semua referensi untuk itu ditinggalkan.
supercat
Saya mempertanyakan mengatakan ini "bukan pendekatan yang baik"; mengujinya, dengan kondisi kesalahan yang Anda khawatirkan, dan lihat apakah Anda memiliki masalah. Bahkan jika pengumpulan sampah tidak sempurna, itu mungkin tidak akan mempengaruhi sebagian besar aplikasi secara nyata. Per Microsoft, "Anda dapat memanggil EndInvoke untuk mengambil nilai kembali dari delegasi, jika perlu, tetapi ini tidak diperlukan. EndInvoke akan memblokir hingga nilai balik dapat diambil." from: msdn.microsoft.com/en-us/library/0b1bf3y3(v=vs.90).aspx
Abacus
5

Hampir 10 tahun kemudian:

Task.Run(FireAway);

Saya akan menambahkan penanganan pengecualian dan masuk ke dalam FireAway

Oscar Fraxedas
sumber
3

Pendekatan .NET 2.0 dan kemudian yang paling sederhana adalah menggunakan Asynchnonous Programming Model (mis. BeginInvoke pada delegasi):

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 );  
}
Abu
sumber
Sebelum Task.Run()ada, ini mungkin cara paling ringkas untuk melakukan ini.
binki