Maneira mais simples de fazer um incêndio e esquecer o método em c #?

150

Eu vi no WCF eles terem o [OperationContract(IsOneWay = true)]atributo Mas o WCF parece meio lento e pesado, apenas para criar uma função sem bloqueio. Idealmente, haveria algo como non-blocking estático MethodFoo(){}, mas acho que não existe.

Qual é a maneira mais rápida de criar uma chamada de método sem bloqueio em C #?

Por exemplo

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 : Todo mundo que lê isso deve pensar se realmente deseja que o método termine. (Consulte a segunda resposta principal) Se o método precisar terminar, em alguns lugares, como um aplicativo ASP.NET, você precisará fazer algo para bloquear e manter o thread ativo. Caso contrário, isso poderia levar a "ignorar, mas nunca executar de fato"; nesse caso, é claro, seria mais simples escrever nenhum código. ( Uma boa descrição de como isso funciona no ASP.NET )

MatthewMartin
fonte

Respostas:

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

(cinco anos depois...)

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

como apontado por luisperezphd .


fonte
Você ganha. Não, realmente, esta parece ser a versão mais curta, a menos que você a encapsule em outra função.
OregonGhost
13
Pensando nisso ... na maioria dos aplicativos, tudo bem, mas no seu aplicativo o console será encerrado antes que o FireAway envie algo ao console. Os threads do ThreadPool são threads de segundo plano e morrem quando o aplicativo morre. Por outro lado, o uso de um Thread não funcionaria, pois a janela do console desapareceria antes que o FireAway tentasse gravar na janela.
3
Não há como ter uma chamada de método sem bloqueio com garantia de execução, portanto, essa é realmente a resposta mais precisa para a pergunta IMO. Se você precisa de execução da garantia, em seguida, o potencial de bloqueio precisa ser introduzido através de uma estrutura de controle tais como AutoResetEvent (como Kev mencionado)
Guvante
1
Para executar a tarefa em um independente rosca do pool de threads, eu acredito que você também pode ir(new Action(FireAway)).BeginInvoke()
Sam Harwell
2
Que tal isso Task.Factory.StartNew(() => myevent());de resposta stackoverflow.com/questions/14858261/…
Luis Perez
39

Para C # 4.0 e mais recente, parece-me que a melhor resposta agora é dada aqui por Ade Miller: Maneira mais simples de disparar e esquecer o método no c # 4.0

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

Ou até ...

Task.Factory.StartNew(FireAway);

Ou...

new Task(FireAway).Start();

Onde FireAwayfica

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

Portanto, em virtude da dispersão do nome da classe e do método, a versão do pool de threads supera a versão entre seis e dezenove caracteres, dependendo da que você escolher :)

ThreadPool.QueueUserWorkItem(o => FireAway());
Patrick Szalapski
fonte
11
Estou mais interessado em ter as melhores respostas disponíveis com facilidade em boas perguntas. Eu definitivamente encorajaria outras pessoas a fazer o mesmo.
Patrick Szalapski
3
De acordo com stackoverflow.com/help/referencing , você deve usar citações de bloco para indicar que está citando de outra fonte. Como é sua resposta, parece ser todo seu próprio trabalho. Sua intenção ao republicar uma cópia exata da resposta poderia ter sido alcançada com um link em um comentário.
tom Redfern
1
como passar parâmetros para FireAway?
GuidoG
Eu acredito que você teria que usar a primeira solução: Task.Factory.StartNew(() => FireAway(foo));.
Patrick Szalapski
1
ser cauteloso ao usar Task.Factory.StartNew(() => FireAway(foo));foo não pode ser modificado em malha, como explicado aqui e aqui
AaA
22

Para o .NET 4.5:

Task.Run(() => FireAway());
David Murdoch
fonte
15
Fyi, isso é principalmente uma abreviação de Task.Factory.StartNew(() => FireAway(), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);acordo com blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
Jim Geurts
2
É bom saber, @JimGeurts!
David Murdoch
No interesse da posteridade, o artigo vinculado a @JimGeurts em seu comentário agora está esgotado (obrigado, MS!). Dado o carimbo de data e hora nesse URL, este artigo de Stephen Toub parece ser o correto - devblogs.microsoft.com/pfxteam/…
Dan Atkinson
1
@ DanAtkinson você está correto. Aqui está o original em archive.org, apenas para o caso de a MS mover novamente: web.archive.org/web/20160226022029/http://blogs.msdn.com/b/…
David Murdoch
18

Para adicionar à resposta de Will , se esse for um aplicativo de console, basta inserir um AutoResetEvente a WaitHandlepara impedir que ele saia antes que o segmento de trabalho seja concluído:

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
fonte
se esse não for um aplicativo de console e minha classe C # for COM Visible, seu AutoEvent funcionará? O autoEvent.WaitOne () está bloqueando?
31512 dan_l
5
@dan_l - Não faço ideia, por que não fazer isso como uma nova pergunta e fazer uma referência a esta.
Kev
@dan_l, sim WaitOneé sempre o bloqueio e AutoResetEventserá sempre trabalho
AaA
O AutoResetEvent realmente funciona aqui apenas se houver um único thread em segundo plano. Gostaria de saber se uma barreira seria melhor quando vários threads estão em uso. docs.microsoft.com/en-us/dotnet/standard/threading/barrier
Martin Brown
@ MartinBrown - não sei se isso é verdade. Você pode ter uma matriz de WaitHandles, cada uma com a sua AutoResetEvente uma correspondente ThreadPool.QueueUserWorkItem. Depois disso apenas WaitHandle.WaitAll(arrayOfWaitHandles).
Kev
15

Uma maneira fácil é criar e iniciar um encadeamento com lambda sem parâmetros:

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

Usando esse método em ThreadPool.QueueUserWorkItem, você pode nomear seu novo thread para facilitar a depuração. Além disso, não se esqueça de usar o tratamento extensivo de erros em sua rotina, pois quaisquer exceções não tratadas fora de um depurador causam um travamento abrupto no aplicativo:

insira a descrição da imagem aqui

Robert Venables
fonte
+1 para quando você precisar de um segmento dedicado devido a um processo de longa execução ou bloqueio.
Paul Turner
12

A maneira recomendada de fazer isso quando você estiver usando Asp.Net e .Net 4.5.2 é usando QueueBackgroundWorkItem. Aqui está uma classe auxiliar:

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

Exemplo de uso:

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

ou usando assíncrono:

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

Isso funciona muito bem nos sites da Azure.

Referência: Usando QueueBackgroundWorkItem para agendar trabalhos em segundo plano de um aplicativo ASP.NET no .NET 4.5.2

Augusto Barreto
fonte
7

Chamar beginInvoke e não capturar EndInvoke não é uma boa abordagem. A resposta é simples: o motivo pelo qual você deve chamar EndInvoke é porque os resultados da chamada (mesmo se não houver valor de retorno) devem ser armazenados em cache pelo .NET até que EndInvoke seja chamado. Por exemplo, se o código chamado lança uma exceção, a exceção é armazenada em cache nos dados de chamada. Até você chamar EndInvoke, ele permanece na memória. Depois de ligar para o EndInvoke, a memória pode ser liberada. Nesse caso específico, é possível que a memória permaneça até que o processo seja encerrado porque os dados são mantidos internamente pelo código de chamada. Eu acho que o GC pode eventualmente coletá-lo, mas não sei como o GC saberia que você abandonou os dados vs. apenas demorou muito tempo para recuperá-lo. Duvido que sim. Portanto, um vazamento de memória pode ocorrer.

Mais informações podem ser encontradas em http://haacked.com/archive/2009/01/09/asynchronous-fire-and-forget-with-lambdas.aspx

Manoj Aggarwal
fonte
3
O GC pode dizer quando as coisas são abandonadas se não houver referência a elas. Se BeginInvokeretornar um objeto wrapper que mantém uma referência ao objeto que contém os dados reais do resultado, mas não é referenciado, o objeto wrapper se tornará elegível para finalização assim que todas as referências a ele forem abandonadas.
Supercat 23/10
Eu questiono dizendo que esta "não é uma boa abordagem"; teste-o com as condições de erro com as quais você está preocupado e veja se você tem problemas. Mesmo que a coleta de lixo não seja perfeita, provavelmente não afetará a maioria dos aplicativos visivelmente. Por Microsoft, "Você pode chamar EndInvoke para recuperar o valor de retorno do delegado, se necessário, mas isso não é necessário. EndInvoke bloqueará até que o valor de retorno possa ser recuperado." A partir de: msdn.microsoft.com/pt-br/library/0b1bf3y3(v=vs.90).aspx
Abacus
5

Quase 10 anos depois:

Task.Run(FireAway);

Eu adicionaria manipulação e registro de exceções no FireAway

Oscar Fraxedas
fonte
3

A abordagem mais simples do .NET 2.0 e posterior é usar o Modelo de programação assíncrona (ou seja, BeginInvoke em um delegado):

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 );  
}
Cinza
fonte
Antes de Task.Run()existir, essa era provavelmente a maneira mais concisa de fazer isso.
binki