Найпростіший спосіб зробити вогонь і забути метод в 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");
    }
}

NB : Кожен, хто читає це, повинен задуматися, чи дійсно вони хочуть, щоб метод закінчився. (Див. Головну відповідь №2) Якщо метод має закінчитися, то в деяких місцях, як-от додаток ASP.NET, вам потрібно буде щось зробити, щоб заблокувати і зберегти живий потік. В іншому випадку це може призвести до "пожежно-забути-але-ніколи-фактично-виконати", і в цьому випадку, звичайно, було б простіше взагалі не писати код. ( Хороший опис того, як це працює в ASP.NET )

Відповіді:


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

(через п’ять років ...)

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

як вказував luisperezphd .


Ти переміг. Ні, насправді це здається найкоротшою версією, якщо ви не інкапсулюєте це в іншу функцію.
OregonGhost

13
Якщо подумати про це ... у більшості програм це нормально, але у ваших консольна програма вийде, перш ніж FireAway надішле щось на консоль. Нитки ThreadPool є фоновими нитками і відмирають, коли програма гине. З іншого боку, використання Thread не буде працювати ні тому, що вікно консолі зникне, перш ніж FireAway спробує записати у вікно.

3
Немає можливості здійснити виклик неблокуючого методу, який гарантовано працює, тому це насправді найточніша відповідь на питання ІМО. Якщо вам потрібно гарантувати виконання, то потенційне блокування потрібно ввести через керуючу структуру, таку як AutoResetEvent (як згадував Кев)
Guvante

1
Щоб виконати завдання в потоці, незалежному від пулу потоків, я вважаю, що ви також можете піти(new Action(FireAway)).BeginInvoke()
Сем Харвелл

2
Що з цього приводу Task.Factory.StartNew(() => myevent());відповісти stackoverflow.com/questions/14858261/…
Луїс Перес

39

Що стосується C # 4.0 та новіших версій, мені здається, що найкращу відповідь зараз дає Аде Міллер: Найпростіший спосіб зробити вогонь і забути метод в # 4,0

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

Або навіть...

Task.Factory.StartNew(FireAway);

Або ...

new Task(FireAway).Start();

Де FireAwayє

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

Таким чином, в силу назви класу та методу terseness це обробляє версію потокової нитки на шість-дев'ятнадцять символів залежно від обраного вами :)

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

11
Мені найбільше цікаво, щоб найкращі відповіді були легко доступні на хороші запитання. Я б напевно закликав інших робити те саме.
Патрік Шалапський

3
Відповідно до stackoverflow.com/help/referencing , вам потрібно використовувати блок-цитати, щоб вказати, що ви цитуєте з іншого джерела. Оскільки ваша відповідь, здається, є вашою власною роботою. Ваш намір шляхом повторної публікації точної копії відповіді міг бути досягнутий за посиланням у коментарі.
tom redfern

1
як передавати параметри FireAway?
GuidoG

Я вважаю , що ви повинні використовувати перше рішення: Task.Factory.StartNew(() => FireAway(foo));.
Патрік Шалапський

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!
Девід Мердок

В інтересах нащадків стаття, пов’язана з тим, щоб бути @JimGeurts у коментарі, зараз 404'd (спасибі, MS!). З огляду на дату в цій URL-адресі, ця стаття Стівена Туба видається правильною - devblogs.microsoft.com/pfxteam/…
Ден Аткінсон,

1
@DanAtkinson ви маєте рацію. Ось оригінал на archive.org, про всяк випадок, коли MS перемістить його знову: web.archive.org/web/20160226022029/http://blogs.msdn.com/b/…
Девід Мердок

18

Щоб додати відповідь Вілла , якщо це консольний додаток, просто введіть AutoResetEventі, 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-видимий, чи працюватиме ваш AutoEvent? Блокує autoEvent.WaitOne ()?
dan_l

5
@dan_l - Не маю ідеї, чому б не поставити це як нове запитання і зробити посилання на це.
Кев

@dan_l, так WaitOne, завжди блокується і AutoResetEventзавжди працюватиме
AaA

AutoResetEvent тут справді працює лише за наявності одного фонового потоку. Цікаво, чи не буде кращим бар’єр, коли використовується декілька потоків. docs.microsoft.com/en-us/dotnet/standard/threading/barrier
Мартін Браун

@MartinBrown - не впевнений, що це правда. Ви можете мати масив WaitHandles, кожен з яких є власним AutoResetEventі відповідним ThreadPool.QueueUserWorkItem. Після цього просто WaitHandle.WaitAll(arrayOfWaitHandles).
Кев

15

Простий спосіб - створити і запустити потік з параметри лямбда:

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

Використовуючи цей метод через ThreadPool.QueueUserWorkItem, ви можете назвати новий потік, щоб полегшити його налагодження. Крім того, не забудьте використовувати широко розповсюджені помилки у вашому розпорядженні, оскільки будь-які необроблені винятки поза налагоджувачем різко збивають вашу програму:

введіть тут опис зображення


+1 для, коли вам потрібна спеціальна нитка через тривалий процес чи блокування.
Пол Тернер

12

Рекомендований спосіб зробити це під час використання Asp.Net та .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();
});

або за допомогою асинхронізації:

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

Це чудово працює на веб-сайтах Azure.

Довідка: Використання QueueBackgroundWorkItem для планування фонових завдань з програми ASP.NET у .NET 4.5.2


7

Виклик BeginInvoke і не ловити 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повертає об’єкт обгортки, на який міститься посилання, але на нього не посилається об'єкт, який містить дані про реальні результати, об'єкт обгортки стане придатним для доопрацювання, коли всі посилання на нього будуть залишені.
supercat

Я сумніваюся, кажучи, що це "не гарний підхід"; протестуйте його з умовами помилок, які вас турбують, і перевірте, чи є у вас проблеми. Навіть якщо збір сміття не є ідеальним, це, ймовірно, не вплине на більшість застосувань. У Microsoft, "Ви можете зателефонувати EndInvoke, щоб отримати повернене значення у делегата, якщо це необхідно, але це не потрібно. EndInvoke заблокує, поки значення повернення не зможе бути отримано." від: msdn.microsoft.com/en-us/library/0b1bf3y3(v=vs.90).aspx
Abacus


3

Найпростіший .NET 2.0 і пізніший підхід використовують модель асинхронної програми (тобто. 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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.