Самый простой способ сделать огонь и забыть метод в 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 )

MatthewMartin
источник

Ответы:

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

(пять лет спустя...)

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

как указывает Луисперефд .


источник
Ты победил. Нет, на самом деле, это, кажется, самая короткая версия, если вы не включили это в другую функцию.
OregonGhost
13
Думая об этом ... в большинстве приложений это нормально, но у вас консольное приложение завершится до того, как FireAway отправит что-нибудь на консоль. Потоки ThreadPool являются фоновыми потоками и умирают, когда приложение умирает. С другой стороны, использование Thread не сработает, так как окно консоли исчезнет до того, как FireAway попытается выполнить запись в окно.
3
Невозможно выполнить неблокирующий вызов метода, который гарантированно выполняется, так что это на самом деле самый точный ответ на вопрос IMO. Если вам нужно гарантировать выполнение, тогда потенциальная блокировка должна быть введена через управляющую структуру, такую ​​как AutoResetEvent (как упомянул Кев)
Guvante
1
Чтобы запустить задачу в потоке, не зависящем от пула потоков, я думаю, вы также можете пойти(new Action(FireAway)).BeginInvoke()
Сэм Харвелл,
2
Как насчет этого Task.Factory.StartNew(() => myevent());из ответа stackoverflow.com/questions/14858261/…
Луис Перес
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.QueueUserWorkItem(o => FireAway());
Патрик Салапски
источник
11
Меня больше всего интересуют лучшие ответы на хорошие вопросы. Я определенно призываю других сделать то же самое.
Патрик Салапски
3
Согласно stackoverflow.com/help/referencing , вы должны использовать кавычки, чтобы указать, что вы цитируете из другого источника. Поскольку это ваш ответ, кажется, все ваша собственная работа. Ваше намерение путем повторной публикации точной копии ответа могло быть достигнуто с помощью ссылки в комментарии.
Том Редферн
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);К вашему сведению
Джим Гёртс
2
Полезно знать, @JimGeurts!
Дэвид Мердок
В интересах потомков, статья, связанная с @JimGeurts в их комментарии, теперь имеет 404 (спасибо, MS!). Учитывая дату в этом URL, эта статья Стивена Тауба кажется правильной - devblogs.microsoft.com/pfxteam/…
Дэн Аткинсон
1
@DanAtkinson вы правы. Вот оригинал на archive.org, на случай, если MS снова его переместит: web.archive.org/web/20160226022029/http://blogs.msdn.com/b/…
Дэвид Мердок
18

Чтобы добавить к ответу Уилла , если это консольное приложение, просто добавьте 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 - Понятия не имею, почему бы не задать этот вопрос как новый вопрос и не дать ссылку на этот вопрос.
Кев
@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();
});

или используя async:

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возвращает объект-обертку, который содержит ссылку, но не на которую ссылается объект, который содержит реальные данные результата, объект-обертка получит право на финализацию, как только все ссылки на него будут отменены.
суперкат
Я сомневаюсь, что это «не очень хороший подход»; Протестируйте его с ошибочными условиями, которые вас беспокоят, и посмотрите, есть ли у вас проблемы. Даже если сборка мусора не идеальна, она, вероятно, не окажет заметного влияния на большинство приложений. Согласно 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 и более поздних версий является использование модели асинхронного программирования (т. Е. 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()этого это был, пожалуй, самый лаконичный способ сделать это.
Бинки,