Самый простой способ сделать огонь и забыть метод в C #? - PullRequest
128 голосов
/ 19 июня 2009

Я видел в WCF, что они имеют атрибут [OperationContract(IsOneWay = true)]. Но WCF кажется медленным и тяжелым только для того, чтобы создать неблокирующую функцию. В идеале было бы что-то вроде статического неблокирования пустот MethodFoo(){}, но я не думаю, что оно существует.

Какой самый быстрый способ создать вызов неблокирующего метода в C #?

1007 * Е.Г. *

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 )

Ответы [ 9 ]

243 голосов
/ 19 июня 2009
ThreadPool.QueueUserWorkItem(o => FireAway());

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

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

как указано luisperezphd .

34 голосов
/ 24 октября 2012

Для C # 4.0 и новее, мне кажется, что лучший ответ теперь дает здесь Эде Миллер: Самый простой способ сделать огонь и забыть метод в 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());
17 голосов
/ 19 июня 2009

Чтобы добавить к ответ Уилла , если это консольное приложение, просто добавьте 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();
    }
}
15 голосов
/ 11 октября 2013

Для .NET 4.5:

Task.Run(() => FireAway());
13 голосов
/ 19 июня 2009

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

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

Используя этот метод над ThreadPool.QueueUserWorkItem, вы можете назвать новый поток, чтобы облегчить его отладку. Кроме того, не забудьте использовать расширенную обработку ошибок в своей программе, потому что любые необработанные исключения вне отладчика могут внезапно привести к сбою приложения:

enter image description here

10 голосов
/ 03 августа 2015

Рекомендованный способ сделать это при использовании 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 голосов
/ 17 января 2010

Вызов beginInvoke и отсутствие перехвата EndInvoke не очень хороший подход. Ответ прост: Причина, по которой вы должны вызывать EndInvoke, заключается в том, что результаты вызова (даже если нет возвращаемого значения) должны кэшироваться .NET до вызова EndInvoke. Например, если вызванный код генерирует исключение, то исключение кэшируется в данных вызова. Пока вы не вызовете EndInvoke, он останется в памяти. После вызова EndInvoke память может быть освобождена. В этом конкретном случае возможно, что память останется до тех пор, пока процесс не остановится, потому что данные поддерживаются внутренне кодом вызова. Я думаю, что GC может в конечном итоге собрать его, но я не знаю, как GC узнает, что вы отказались от данных, а просто потратили очень много времени на их получение. Я сомневаюсь, что это так. Следовательно, может произойти утечка памяти.

Больше можно найти на http://haacked.com/archive/2009/01/09/asynchronous-fire-and-forget-with-lambdas.aspx

2 голосов
/ 09 декабря 2009

В простейшем подходе .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 );  
}
1 голос
/ 16 июля 2018

Почти 10 лет спустя:

Task.Run(FireAway);

Я бы добавил обработку и ведение исключений внутри FireAway

...