Il modo più semplice per fare un incendio e dimenticare il metodo in C #?


150

Ho visto in WCF che hanno l' [OperationContract(IsOneWay = true)]attributo. Ma WCF sembra un po 'lento e pesante solo per creare una funzione non bloccante. Idealmente ci sarebbe qualcosa di simile al vuoto statico senza blocco MethodFoo(){}, ma non credo che esista.

Qual è il modo più rapido per creare una chiamata di metodo non bloccante in C #?

Per esempio

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 : Chiunque legga questo dovrebbe pensare se vogliono davvero che il metodo finisca. (Vedi risposta principale n. 2) Se il metodo deve terminare, in alcuni casi, come un'applicazione ASP.NET, dovrai fare qualcosa per bloccare e mantenere attivo il thread. Altrimenti, questo potrebbe portare a "fuoco-dimentica-ma-mai-effettivamente-esegui", nel qual caso, ovviamente, sarebbe più semplice non scrivere alcun codice. ( Una buona descrizione di come funziona in ASP.NET )

Risposte:


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

(Cinque anni dopo...)

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

come sottolineato da luisperezphd .


Hai vinto. No, davvero, questa sembra essere la versione più breve, a meno che non la incapsuli in un'altra funzione.
OregonGhost,

13
Pensando a questo ... nella maggior parte delle applicazioni va bene, ma nella tua l'app della console uscirà prima che FireAway invii qualcosa alla console. I thread di ThreadPool sono thread in background e muoiono quando l'app muore. D'altra parte, l'uso di un thread non funzionerebbe, poiché la finestra della console scomparirebbe prima che FireAway provasse a scrivere sulla finestra.

3
Non c'è modo di avere una chiamata di metodo non bloccante che sia garantita per essere eseguita, quindi questa è in realtà la risposta più accurata alla domanda IMO. Se è necessario garantire l'esecuzione, è necessario introdurre un potenziale blocco tramite una struttura di controllo come AutoResetEvent (come menzionato Kev)
Guvante,

1
Per eseguire l'attività in un thread indipendente dal pool di thread, credo che potresti anche andare(new Action(FireAway)).BeginInvoke()
Sam Harwell,

2
Che dire di questo Task.Factory.StartNew(() => myevent());dalla risposta stackoverflow.com/questions/14858261/…
Luis Perez,

39

Per C # 4.0 e successivi, mi sembra che la risposta migliore sia stata data qui da Ade Miller: modo più semplice per fare un incendio e dimenticare il metodo in c # 4.0

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

O anche...

Task.Factory.StartNew(FireAway);

O...

new Task(FireAway).Start();

Dove FireAway si trova

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

Quindi, in virtù della terseness del nome della classe e del metodo, questo batte la versione di threadpool tra sei e diciannove caratteri a seconda di quello che scegli :)

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

11
Sono molto interessato ad avere le migliori risposte facilmente disponibili su buone domande. Incoraggerei sicuramente gli altri a fare lo stesso.
Patrick Szalapski il

3
Secondo stackoverflow.com/help/referencing , è necessario utilizzare le virgolette per indicare che si sta citando da un'altra fonte. Come è la tua risposta sembra essere tutto il tuo lavoro. La tua intenzione di ripubblicare una copia esatta della risposta avrebbe potuto essere raggiunta con un link in un commento.
Tom Redfern,

1
come passare i parametri a FireAway?
GuidoG,

Credo che avrei dovuto usare la prima soluzione: Task.Factory.StartNew(() => FireAway(foo));.
Patrick Szalapski il

1
Task.Factory.StartNew(() => FireAway(foo));fai attenzione quando usi foo non può essere modificato in loop come spiegato qui e qui
AaA

22

Per .NET 4.5:

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

15
Cordiali saluti, questo è principalmente l'abbreviazione di Task.Factory.StartNew(() => FireAway(), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);secondo blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
Jim Geurts

2
Buono a sapersi, @JimGeurts!
David Murdoch,

Nell'interesse dei posteri, l'articolo collegato a @JimGeurts nel loro commento ora è 404 (grazie, MS!). Dato il datestamp in quell'URL, questo articolo di Stephen Toub sembra essere quello corretto - devblogs.microsoft.com/pfxteam/…
Dan Atkinson

1
@DanAtkinson hai ragione. Ecco l'originale su archive.org, nel caso in cui MS lo sposta di nuovo: web.archive.org/web/20160226022029/http://blogs.msdn.com/b/…
David Murdoch

18

Per aggiungere alla risposta di Will , se si tratta di un'applicazione console, basta inserire an AutoResetEvente a WaitHandleper impedirne l'uscita prima che il thread di lavoro venga completato:

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

se questa non è un'app console e la mia classe C # è COM Visible, AutoEvent funzionerà? AutoEvent.WaitOne () sta bloccando?
dan_l

5
@dan_l - Nessuna idea, perché non farlo come una nuova domanda e fare un riferimento a questo.
Kev

@dan_l, sì, WaitOneblocca sempre e AutoResetEventfunzionerà sempre
AaA

AutoResetEvent funziona davvero qui solo se esiste un singolo thread in background. Mi chiedo se una barriera sarebbe meglio quando sono in uso più thread. docs.microsoft.com/en-us/dotnet/standard/threading/barrier
Martin Brown

@MartinBrown - non sono sicuro che sia vero. Puoi avere una matrice di WaitHandles, ognuna con la propria AutoResetEvente una corrispondente ThreadPool.QueueUserWorkItem. Dopo quello giusto WaitHandle.WaitAll(arrayOfWaitHandles).
Kev

15

Un modo semplice è creare e avviare un thread con lambda senza parametri:

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

Utilizzando questo metodo su ThreadPool.QueueUserWorkItem è possibile assegnare un nome al nuovo thread per semplificare il debug. Inoltre, non dimenticare di utilizzare la gestione estesa degli errori nella routine perché eventuali eccezioni non gestite al di fuori di un debugger causano il crash improvviso dell'applicazione:

inserisci qui la descrizione dell'immagine


+1 per quando è necessario un thread dedicato a causa di un processo di lunga durata o di blocco.
Paul Turner,

12

Il modo raccomandato per farlo quando si utilizzano Asp.Net e .Net 4.5.2 è usando QueueBackgroundWorkItem. Ecco una classe di aiuto:

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

Esempio di utilizzo:

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

o usando asincrono:

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

Funziona perfettamente sui siti Web di Azure.

Riferimento: utilizzo di QueueBackgroundWorkItem per pianificare processi in background da un'applicazione ASP.NET in .NET 4.5.2


7

Chiamare beginInvoke e non intercettare EndInvoke non è un buon approccio. La risposta è semplice: il motivo per cui è necessario chiamare EndInvoke è perché i risultati dell'invocazione (anche se non è presente alcun valore restituito) devono essere memorizzati nella cache di .NET fino a quando non viene chiamato EndInvoke. Ad esempio, se il codice invocato genera un'eccezione, l'eccezione viene memorizzata nella cache nei dati di chiamata. Fino a quando si chiama EndInvoke rimane in memoria. Dopo aver chiamato EndInvoke, la memoria può essere rilasciata. Per questo caso particolare è possibile che la memoria rimanga finché il processo non si arresta perché i dati sono gestiti internamente dal codice di chiamata. Immagino che alla fine il GC potrebbe raccoglierlo, ma non so come il GC saprebbe che hai abbandonato i dati piuttosto che impiegare davvero molto tempo per recuperarli. Ne dubito. Quindi può verificarsi una perdita di memoria.

Maggiori informazioni su http://haacked.com/archive/2009/01/09/asynchronous-fire-and-forget-with-lambdas.aspx


3
Il GC può dire quando le cose vengono abbandonate se non esiste alcun riferimento ad esse. Se BeginInvokerestituisce un oggetto wrapper che contiene un riferimento, ma a cui non fa riferimento, l'oggetto che contiene i dati del risultato reale, l'oggetto wrapper diventerebbe idoneo alla finalizzazione una volta che tutti i riferimenti ad esso fossero stati abbandonati.
supercat,

Dubito che questo non sia "un buon approccio"; testalo, con le condizioni di errore di cui ti preoccupi, e vedi se hai problemi. Anche se la garbage collection non è perfetta, probabilmente non influirà in modo evidente sulla maggior parte delle applicazioni. Per Microsoft, "È possibile chiamare EndInvoke per recuperare il valore restituito dal delegato, se necessario, ma ciò non è necessario. EndInvoke si bloccherà fino a quando non sarà possibile recuperare il valore restituito." da: msdn.microsoft.com/en-us/library/0b1bf3y3(v=vs.90).aspx
Abacus

5

Quasi 10 anni dopo:

Task.Run(FireAway);

Aggiungerei la gestione e la registrazione delle eccezioni all'interno di FireAway


3

L'approccio .NET 2.0 e successivi più semplice sta usando il modello di programmazione asincrono (ovvero BeginInvoke su un delegato):

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

Prima Task.Run()esisteva, questo era probabilmente il modo più conciso per farlo.
binki,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.