it-swarm.com.de

Wie wird das LongRunning-Flag speziell an Task.Run () übergeben?

Ich brauche eine Möglichkeit, um eine asynchrone Aufgabe so lange wie möglich auszuführen, ohne Task.Factory.StartNew (...) und stattdessen Task.Run (...) oder ähnliches zu verwenden.

Kontext:

Ich habe eine Aufgabe, die so lange wiederholt wird, bis sie von außen abgebrochen wird, und die ich als "Langlauf" festlegen möchte (d. H. Einen dedizierten Thread zuweisen möchte). Dies kann durch den folgenden Code erreicht werden:

var cts = new CancellationTokenSource();
Task t = Task.Factory.StartNew(
async () => {
while (true)
{
    cts.Token.ThrowIfCancellationRequested();
    try
    {
        "Running...".Dump();
        await Task.Delay(500, cts.Token);
    }
    catch (TaskCanceledException ex) { }
} }, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);

Das Problem ist, dass Task.Factory.StartNew (...) nicht die übergebene aktive asynchrone Aufgabe zurückgibt, sondern eine 'Aufgabe zum Ausführen der Aktion', die funktional immer den taskStatus 'RanToCompletion' hat. Da mein Code in der Lage sein muss, den Status der Aufgabe zu verfolgen, um zu sehen, wann sie "Abgebrochen" (oder "Fehlerhaft") wird, muss ich Folgendes verwenden:

var cts = new CancellationTokenSource();
Task t = Task.Run(
async () => {
while (true)
{
    cts.Token.ThrowIfCancellationRequested();
    try
    {
        "Running...".Dump();
        await Task.Delay(500, cts.Token);
    }
    catch (TaskCanceledException ex) { }
} }, cts.Token);

Task.Run (...) gibt, wie gewünscht, den asynchronen Prozess selbst zurück, sodass ich den aktuellen Status "Abgebrochen" oder "Fehlerhaft" abrufen kann. Ich kann die Aufgabe jedoch nicht so lange ausführen. Wer weiß also, wie man am besten eine asynchrone Aufgabe ausführt, während die aktive Aufgabe selbst gespeichert wird (mit dem gewünschten taskStatus) und die Aufgabe auf Langzeitbetrieb eingestellt wird?

14
Ryan

Rufen Sie Unwrap für die von Task.Factory.StartNew zurückgegebene Aufgabe auf. Dies gibt die innere Aufgabe mit dem korrekten Status zurück.

var cts = new CancellationTokenSource();
Task t = Task.Factory.StartNew(
async () => {
while (true)
{
    cts.Token.ThrowIfCancellationRequested();
    try
    {
        "Running...".Dump();
        await Task.Delay(500, cts.Token);
    }
    catch (TaskCanceledException ex) { }
} }, cts.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default).Unwrap();
7
Ned Stoyanov

Ich habe Task, der fortlaufend durchläuft, bis es extern abgebrochen wird und als "Langlauf" (dh es wird ein dedizierter Thread) festgelegt werden soll ... Jeder weiß, wie er am besten eine asynchrone Task ausführt, während beide die aktive Task speichern sich selbst (mit dem gewünschten taskStatus) und die Task auf lang laufen?

Es gibt ein paar Probleme damit. Erstens bedeutet "Langer Lauf" nicht unbedingt einen dedizierten Thread - es bedeutet nur, dass Sie der TPL einen Hinweis darauf geben, dass die Task lange läuft. In der aktuellen (4.5) Implementierung erhalten Sie einen dedizierten Thread. Das ist jedoch nicht garantiert und könnte sich in der Zukunft ändern.

Wenn Sie also need einen dedizierten Thread erstellen, müssen Sie nur einen erstellen.

Das andere Problem ist der Begriff "asynchrone Task". Mit async-Code, der im Thread-Pool ausgeführt wird, passiert eigentlich, dass der Thread an den Thread-Pool zurückgegeben wird , während die asynchrone Operation (d. H. Task.Delay) ausgeführt wird. Wenn die async-Operation abgeschlossen ist, wird ein Thread aus dem Thread-Pool genommen, um die async-Methode fortzusetzen. Im Allgemeinen ist dies effizienter als das Reservieren eines Threads, um diese Aufgabe abzuschließen.

Da async-Tasks im Threadpool ausgeführt werden, sind dedizierte Threads nicht wirklich sinnvoll.


In Bezug auf Lösungen:

Wenn Sie need einen dedizierten Thread zum Ausführen Ihres async-Codes benötigen, würde ich empfehlen, die AsyncContextThread aus meiner AsyncEx-Bibliothek zu verwenden :

using (var thread = new AsyncContextThread())
{
  Task t = thread.TaskFactory.Run(async () =>
  {
    while (true)
    {
      cts.Token.ThrowIfCancellationRequested();
      try
      {
        "Running...".Dump();
        await Task.Delay(500, cts.Token);
      }
      catch (TaskCanceledException ex) { }
    }
  });
}

Sie benötigen jedoch fast sicher keinen dedizierten Thread. Wenn Ihr Code im Thread-Pool ausgeführt werden kann, sollte dies wahrscheinlich der Fall sein. Ein dedizierter Thread ist für async-Methoden, die im Threadpool ausgeführt werden, nicht sinnvoll. Insbesondere macht das Flag mit langer Laufzeit keinen Sinn für async-Methoden, die im Threadpool ausgeführt werden.

Anders ausgedrückt, mit einem async-Lambda wird das, was der Thread-Pool tatsächlich ausführt ausführt (und sieht als Aufgaben), nur die Teile des Lambda zwischen den await-Anweisungen. Da diese Teile nicht lange laufen, ist das Kennzeichen für lange Laufzeit nicht erforderlich. Und Ihre Lösung wird zu:

Task t = Task.Run(async () =>
{
  while (true)
  {
    cts.Token.ThrowIfCancellationRequested(); // not long-running
    try
    {
      "Running...".Dump(); // not long-running
      await Task.Delay(500, cts.Token); // not executed by the thread pool
    }
    catch (TaskCanceledException ex) { }
  }
});
21
Stephen Cleary

In einem dedizierten Thread gibt es nichts zu geben. Verwenden Sie nicht async und await, sondern verwenden Sie synchrone Aufrufe.

Diese Frage gibt zwei Möglichkeiten, einen auskündbaren Schlaf ohne await durchzuführen:

Task.Delay(500, cts.Token).Wait(); // requires .NET 4.5

cts.WaitHandle.WaitOne(TimeSpan.FromMilliseconds(500)); // valid in .NET 4.0 and later

Wenn ein Teil Ihrer Arbeit Parallelität verwendet, können Sie parallele Aufgaben starten, diese in einem Array speichern und Task.WaitAny für Task[] verwenden. Immer noch keine Verwendung für await in der Haupt-Thread-Prozedur.

3
Ben Voigt

Dies ist nicht erforderlich und Task.Run reicht aus, da der Taskplaner jede Task auf LongRunning setzt, wenn sie länger als 0,5 Sekunden ausgeführt wird.

Sehen Sie hier, warum. https://blog.stephencleary.com/2013/08/startnew-is-dangerous.html

Sie müssen benutzerdefinierte TaskCreationOptions angeben. Betrachten wir jede der Optionen . AttachedToParent sollte nicht für asynchrone Aufgaben verwendet werden, daher ist Out. DenyChildAttach sollte immer mit asynchronen Aufgaben Verwendet werden (Hinweis: Wenn Sie dies nicht bereits gewusst haben, ist StartNew nicht das Werkzeug , Das Sie benötigen). DenyChildAttach wird von Task.Run übergeben. HideScheduler kann In einigen wirklich unübersichtlichen Planungsszenarien nützlich sein, im Allgemeinen sollte Jedoch für asynchrone Aufgaben vermieden werden. So bleiben nur LongRunning und PreferFairness übrig, beides Optimierungshinweise, die nach der Anwendungsprofilerstellung nur Angegeben werden sollten. Ich sehe oft, dass LongRunning besonders missbraucht wird. In den meisten Fällen wird der Threadpool Innerhalb von 0,5 Sekunden an alle Aufgaben mit langer Laufzeit angepasst - ohne das Flag LongRunning. Wahrscheinlich brauchen Sie es nicht wirklich.

1
rolls

Das eigentliche Problem, das Sie hier haben, ist das Ihre Operation läuft nicht lange. Die eigentliche Arbeit, die Sie ausführen, ist eine asynchrone Operation, dh sie kehrt im Grunde sofort zum Aufrufer zurück. Sie müssen also nicht nur den langen Hinweis nicht verwenden, wenn der Taskplaner ihn einplanen soll, Sie müssen nicht einmal einen Thread-Pool-Thread verwenden, um diese Arbeit zu erledigen im Grunde sofort. Sie sollten nicht StartNew oder Run überhaupt verwenden, geschweige denn mit der Langstreckenflagge.

Anstatt also Ihre asynchrone Methode zu verwenden und starting in einem anderen Thread auszuführen, können Sie sie einfach direkt im aktuellen Thread starten, indem Sie die asynchrone Methode aufrufen. Das Starten eines bereits asynchronen Vorgangs zu entladen, schafft nur mehr Arbeit, die die Dinge langsamer macht.

Ihr Code vereinfacht sich also bis hinunter zu:

var cts = new CancellationTokenSource();
Task t = DoWork();
async Task DoWork()
{
    while (true)
    {
        cts.Token.ThrowIfCancellationRequested();
        try
        {
            "Running...".Dump();
            await Task.Delay(500, cts.Token);
        }
        catch (TaskCanceledException) { }
    }
}
1
Servy

Ich denke, die Überlegung sollte nicht sein, wie lange der Thread läuft, sondern wie viel Zeit er wirklich arbeitet. In deinem Beispiel gibt es Kurzarbeit und sie await Task.Delay(...). Wenn dies in Ihrem Projekt wirklich der Fall ist, sollten Sie wahrscheinlich keinen dedizierten Thread für diese Aufgabe verwenden und ihn im regulären Thread-Pool ausführen lassen. Jedes Mal, wenn Sie await bei einer IO) - Operation oder bei Task.Delay() aufrufen, geben Sie den Thread für andere Aufgaben frei.

Sie sollten LongRunning nur verwenden, wenn Sie Ihren Thread aus dem Thread-Pool verkleinern und ihn niemals oder nur für einen kleinen Prozentsatz der Zeit zurückgeben. In einem solchen Fall (wo die Arbeit lang ist und Task.Delay(...) im Vergleich kurz ist) ist die Verwendung eines dedizierten Threads für den Job eine vernünftige Lösung. Auf der anderen Seite, wenn Ihr Thread die meiste Zeit wirklich arbeitet, verbraucht er Systemressourcen (CPU-Zeit) und es ist vielleicht nicht wirklich wichtig, ob er einen Thread des Thread-Pools enthält, da es sowieso verhindert, dass andere Arbeiten stattfinden .

Fazit? Verwenden Sie einfach Task.Run() (ohne LongRunning) und await in Ihrer langfristigen Aufgabe, wenn und soweit dies möglich ist. Kehren Sie nur dann zu LongRunning zurück, wenn Sie tatsächlich feststellen, dass der andere Ansatz Probleme verursacht, und überprüfen Sie dann Ihren Code und Ihr Design, um sicherzustellen, dass er wirklich erforderlich ist, und es gibt nichts, was Sie an Ihrem Code ändern können.

0
selalerer