Einfachste Möglichkeit, eine Feuer- und Vergessensmethode in C # durchzuführen?

150

Ich habe in WCF gesehen, dass sie das [OperationContract(IsOneWay = true)]Attribut haben. Aber WCF scheint langsam und schwer zu sein, nur um eine nicht blockierende Funktion zu erstellen. Idealerweise würde es so etwas wie statische Leere geben MethodFoo(){}, die nicht blockiert , aber ich glaube nicht, dass es das gibt.

Was ist der schnellste Weg, um einen nicht blockierenden Methodenaufruf in C # zu erstellen?

Z.B

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 : Jeder, der dies liest, sollte darüber nachdenken, ob die Methode tatsächlich abgeschlossen werden soll. (Siehe Antwort Nr. 2 oben) Wenn die Methode beendet werden muss, müssen Sie an einigen Stellen, z. B. in einer ASP.NET-Anwendung, etwas tun, um den Thread zu blockieren und am Leben zu erhalten. Andernfalls könnte dies zu "Feuer-Vergessen-aber-nie-tatsächlich-Ausführen" führen. In diesem Fall wäre es natürlich einfacher, überhaupt keinen Code zu schreiben. ( Eine gute Beschreibung, wie dies in ASP.NET funktioniert )

MatthewMartin
quelle

Antworten:

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

(fünf Jahre später...)

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

wie von luisperezphd hervorgehoben .


quelle
Du gewinnst. Nein, wirklich, dies scheint die kürzeste Version zu sein, es sei denn, Sie kapseln dies in eine andere Funktion.
OregonGhost
13
Denken Sie darüber nach ... in den meisten Anwendungen ist dies in Ordnung, aber in Ihren Anwendungen wird die Konsolen-App beendet, bevor FireAway etwas an die Konsole sendet. ThreadPool-Threads sind Hintergrund-Threads und sterben, wenn die App stirbt. Andererseits würde die Verwendung eines Threads auch nicht funktionieren, da das Konsolenfenster verschwinden würde, bevor FireAway versuchte, in das Fenster zu schreiben.
3
Es gibt keine Möglichkeit, einen nicht blockierenden Methodenaufruf auszuführen, der garantiert ausgeführt wird. Dies ist also die genaueste Antwort auf die Frage IMO. Wenn Sie die Ausführung garantieren müssen, muss eine potenzielle Blockierung über eine Kontrollstruktur wie AutoResetEvent (wie von Kev erwähnt) eingeführt werden
Guvante
1
Um die Aufgabe in einem vom Thread-Pool unabhängigen Thread auszuführen, könnten Sie auch gehen(new Action(FireAway)).BeginInvoke()
Sam Harwell
2
Was ist damit Task.Factory.StartNew(() => myevent());von Antwort stackoverflow.com/questions/14858261/…
Luis Perez
39

Für C # 4.0 und neuere Versionen fällt mir auf, dass Ade Miller hier jetzt die beste Antwort gibt: Einfachste Methode, um eine Feuer- und Vergessensmethode in C # 4.0 auszuführen

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

Oder auch...

Task.Factory.StartNew(FireAway);

Oder...

new Task(FireAway).Start();

Wo FireAwayist

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

Aufgrund der Knappheit des Klassen- und Methodennamens übertrifft dies die Threadpool-Version um sechs bis neunzehn Zeichen, je nachdem, welches Sie auswählen :)

ThreadPool.QueueUserWorkItem(o => FireAway());
Patrick Szalapski
quelle
11
Ich bin am meisten daran interessiert, die besten Antworten auf gute Fragen zu haben. Ich würde definitiv andere ermutigen, dasselbe zu tun.
Patrick Szalapski
3
Laut stackoverflow.com/help/referencing müssen Sie Blockzitate verwenden, um anzuzeigen, dass Sie aus einer anderen Quelle zitieren. Wie es ist, scheint Ihre Antwort ganz Ihre eigene Arbeit zu sein. Ihre Absicht, eine genaue Kopie der Antwort erneut zu veröffentlichen, könnte mit einem Link in einem Kommentar erreicht worden sein.
Tom Redfern
1
Wie übergebe ich Parameter FireAway?
GuidoG
Ich glaube, Sie müssten die erste Lösung verwenden : Task.Factory.StartNew(() => FireAway(foo));.
Patrick Szalapski
1
Task.Factory.StartNew(() => FireAway(foo));
Seien Sie
22

Für .NET 4.5:

Task.Run(() => FireAway());
David Murdoch
quelle
15
Zu Ihrer Information, dies ist hauptsächlich kurz für Task.Factory.StartNew(() => FireAway(), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);laut blogs.msdn.com/b/pfxteam/archive/2011/10/24/10229468.aspx
Jim Geurts
2
Gut zu wissen, @JimGeurts!
David Murdoch
Im Interesse der Nachwelt ist der Artikel, der in seinem Kommentar mit @JimGeurts verlinkt ist, jetzt 404'd (danke, MS!). Angesichts des Datumsstempels in dieser URL scheint dieser Artikel von Stephen Toub der richtige zu sein - devblogs.microsoft.com/pfxteam/…
Dan Atkinson
1
@ DanAtkinson du bist richtig. Hier ist das Original auf archive.org, nur für den Fall, dass MS es erneut verschiebt: web.archive.org/web/20160226022029/http://blogs.msdn.com/b/…
David Murdoch
18

Um Wills Antwort zu ergänzen , wenn dies eine Konsolenanwendung ist, geben Sie einfach ein AutoResetEventund ein ein WaitHandle, um zu verhindern, dass sie beendet wird, bevor der Arbeitsthread abgeschlossen ist:

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
quelle
Wenn dies keine Konsolen-App ist und meine C # -Klasse COM Visible ist, funktioniert Ihr AutoEvent? Blockiert autoEvent.WaitOne ()?
dan_l
5
@dan_l - Keine Ahnung, warum nicht das als neue Frage stellen und auf diese verweisen.
Kev
@ Dan_l, ja WaitOneblockiert immer und AutoResetEventwird immer funktionieren
AaA
AutoResetEvent funktioniert hier nur dann wirklich, wenn es einen einzelnen Hintergrund-Thread gibt. Ich frage mich, ob eine Barriere besser wäre, wenn mehrere Threads verwendet werden. docs.microsoft.com/en-us/dotnet/standard/threading/barrier
Martin Brown
@ MartinBrown - nicht sicher, ob das stimmt. Sie können ein Array von WaitHandles haben, jedes mit einem eigenen AutoResetEventund einem entsprechenden ThreadPool.QueueUserWorkItem. Danach einfach WaitHandle.WaitAll(arrayOfWaitHandles).
Kev
15

Eine einfache Möglichkeit besteht darin, einen Thread mit parameterlosem Lambda zu erstellen und zu starten:

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

Mit dieser Methode über ThreadPool.QueueUserWorkItem können Sie Ihren neuen Thread benennen, um das Debuggen zu vereinfachen. Vergessen Sie auch nicht, in Ihrer Routine eine umfassende Fehlerbehandlung zu verwenden, da nicht behandelte Ausnahmen außerhalb eines Debuggers Ihre Anwendung abrupt zum Absturz bringen:

Geben Sie hier die Bildbeschreibung ein

Robert Venables
quelle
+1 für den Fall, dass Sie aufgrund eines lang laufenden oder blockierenden Prozesses einen dedizierten Thread benötigen.
Paul Turner
12

Die empfohlene Vorgehensweise bei Verwendung von Asp.Net und .Net 4.5.2 ist die Verwendung von QueueBackgroundWorkItem. Hier ist eine Hilfsklasse:

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

Anwendungsbeispiel:

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

oder mit async:

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

Dies funktioniert hervorragend auf Azure-Websites.

Referenz: Verwenden von QueueBackgroundWorkItem zum Planen von Hintergrundjobs aus einer ASP.NET-Anwendung in .NET 4.5.2

Augusto Barreto
quelle
7

Es ist kein guter Ansatz, beginInvoke aufzurufen und EndInvoke nicht abzufangen. Die Antwort ist einfach: Der Grund, warum Sie EndInvoke aufrufen sollten, besteht darin, dass die Ergebnisse des Aufrufs (auch wenn kein Rückgabewert vorhanden ist) von .NET zwischengespeichert werden müssen, bis EndInvoke aufgerufen wird. Wenn der aufgerufene Code beispielsweise eine Ausnahme auslöst, wird die Ausnahme in den Aufrufdaten zwischengespeichert. Bis Sie EndInvoke aufrufen, bleibt es im Speicher. Nachdem Sie EndInvoke aufgerufen haben, kann der Speicher freigegeben werden. In diesem speziellen Fall ist es möglich, dass der Speicher bis zum Herunterfahren des Prozesses erhalten bleibt, da die Daten intern durch den Aufrufcode verwaltet werden. Ich denke, der GC könnte es irgendwann sammeln, aber ich weiß nicht, woher der GC wissen würde, dass Sie die Daten aufgegeben haben, anstatt nur sehr lange zu brauchen, um sie abzurufen. Ich bezweifle es. Daher kann ein Speicherverlust auftreten.

Weitere Informationen finden Sie unter http://haacked.com/archive/2009/01/09/asynchronous-fire-and-forget-with-lambdas.aspx

Manoj Aggarwal
quelle
3
Der GC kann feststellen, wann Dinge aufgegeben werden, wenn kein Verweis darauf vorhanden ist. Wenn BeginInvokeein Wrapper-Objekt zurückgegeben wird, das einen Verweis auf das Objekt enthält, auf das die tatsächlichen Ergebnisdaten enthalten, auf das jedoch nicht verwiesen wird, kann das Wrapper-Objekt finalisiert werden, sobald alle Verweise darauf aufgegeben wurden.
Supercat
Ich frage mich, ob dies "kein guter Ansatz" ist. Testen Sie es mit den Fehlerbedingungen, über die Sie sich Sorgen machen, und prüfen Sie, ob Sie Probleme haben. Auch wenn die Speicherbereinigung nicht perfekt ist, wird sie die meisten Anwendungen wahrscheinlich nicht merklich beeinflussen. Per Microsoft: "Sie können EndInvoke aufrufen, um den Rückgabewert vom Delegaten abzurufen, falls dies erforderlich ist. Dies ist jedoch nicht erforderlich. EndInvoke wird blockiert, bis der Rückgabewert abgerufen werden kann." von: msdn.microsoft.com/en-us/library/0b1bf3y3(v=vs.90).aspx
Abacus
5

Fast 10 Jahre später:

Task.Run(FireAway);

Ich würde die Ausnahmebehandlung und Protokollierung in FireAway hinzufügen

Oscar Fraxedas
quelle
3

Der einfachste Ansatz für .NET 2.0 und höher ist die Verwendung des asynchronen Programmiermodells (dh BeginInvoke für einen Delegaten):

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 );  
}
Asche
quelle
Bevor es Task.Run()existierte, war dies wahrscheinlich der prägnanteste Weg, dies zu tun.
Binki