it-swarm.com.de

Verwenden von async / await oder task im Web-API-Controller (.NET Core)

Ich habe eine .NET-Core-API, die einen Controller hat, der ein aggregiertes Objekt für die Rückgabe erstellt. Das Objekt, das es erstellt, besteht aus Daten, die aus drei Methodenaufrufen einer Serviceklasse stammen. Diese sind alle unabhängig voneinander und können isoliert voneinander ausgeführt werden. Derzeit verwende ich Aufgaben, um die Leistung dieses Controllers zu verbessern. Die aktuelle Version sieht ungefähr so ​​aus ...

[HttpGet]
public IActionResult myControllerAction()
{      
    var data1 = new sometype1();
    var data2 = new sometype2();
    var data3 = new List<sometype3>();

    var t1 = new Task(() => { data1 = service.getdata1(); });
    t1.Start();

    var t2 = new Task(() => { data2 = service.getdata2(); });
    t2.Start();

    var t3 = new Task(() => { data3 = service.getdata2(); });
    t3.Start();

    Task.WaitAll(t1, t2, t3);

    var data = new returnObject
    {
         d1 = data1,
         d2 = data2,
         d2 = data3
    };

    return Ok(data);
}

Dies funktioniert gut, aber ich frage mich, ob die Verwendung von Aufgaben hier die beste Lösung ist? Wäre die Verwendung von async/await eine bessere Idee und akzeptierter?

Soll der Controller beispielsweise als asynchron markiert sein und bei jedem Aufruf der Dienstmethoden ein Warten erfolgen?

39
Ben Cameron

Dies funktioniert gut, aber ich frage mich, ob die Verwendung von Aufgaben hier die beste Lösung ist? Wäre die Verwendung von async/await eine bessere Idee und akzeptierter?

Ja absolut. Die parallele Verarbeitung in ASP.NET beansprucht mehrere Threads pro Anforderung, was sich erheblich auf Ihre Skalierbarkeit auswirken kann. Die asynchrone Verarbeitung ist für E/A weit überlegen.

Um async zu verwenden, beginnen Sie zuerst mit Ihrem Anruf auf der untersten Ebene, irgendwo in Ihrem Dienst. Wahrscheinlich führt es irgendwann einen HTTP-Aufruf durch. Ändern Sie dies, um asynchrone HTTP-Aufrufe zu verwenden (z. B. HttpClient). Dann lass async von dort aus natürlich wachsen.

Schließlich erhalten Sie asynchrone Methoden getdata1Async, getdata2Async Und getdata3Async, Die gleichzeitig als solche verwendet werden können:

[HttpGet]
public async Task<IActionResult> myControllerAction()
{
  var t1 = service.getdata1Async();
  var t2 = service.getdata2Async();
  var t3 = service.getdata3Async();
  await Task.WhenAll(t1, t2, t3);

  var data = new returnObject
  {
    d1 = await t1,
    d2 = await t2,
    d3 = await t3
  };

  return Ok(data);
}

Bei diesem Ansatz verwendet myControllerAction, während die drei Dienstaufrufe ausgeführt werden, null Threads anstelle von vier.

60
Stephen Cleary
[HttpGet]
public async Task<IActionResult> GetAsync()
{      
    var t1 = Task.Run(() => service.getdata1());
    var t2 = Task.Run(() => service.getdata2());
    var t3 = Task.Run(() => service.getdata3());

    await Task.WhenAll(t1, t2, t3);

    var data = new returnObject
    {
        d1 = t1.Status == TaskStatus.RanToCompletion ? t1.Result : null,
        d2 = t2.Status == TaskStatus.RanToCompletion ? t2.Result : null,
        d3 = t3.Status == TaskStatus.RanToCompletion ? t3.Result : null
    };

   return Ok(data);
}
  1. Ihr Aktionsthread ist derzeit blockiert, wenn Sie auf Aufgaben warten. Verwenden Sie TaskWhenAll, um das erwartete Task-Objekt zurückzugeben. Mit der asynchronen Methode können Sie also auf Aufgaben warten, anstatt den Thread zu blockieren.
  2. Anstatt lokale Variablen zu erstellen und in Tasks zuzuweisen, können Sie Task<T>, um Ergebnisse des erforderlichen Typs zurückzugeben.
  3. Anstatt Aufgaben zu erstellen und auszuführen, verwenden Sie Task<TResult>.Run Methode
  4. Ich empfehle die Verwendung einer Konvention für Aktionsnamen. Wenn die Aktion eine GET-Anforderung akzeptiert, sollte der Name mit Get beginnen.
  5. Als Nächstes sollten Sie prüfen, ob die Aufgaben erfolgreich abgeschlossen wurden. Dies erfolgt durch Überprüfen des Taskstatus. In meinem Beispiel habe ich null Werte für die Rückgabe von Objekteigenschaften verwendet, wenn einige Aufgaben nicht erfolgreich abgeschlossen wurden. Sie können einen anderen Ansatz verwenden - z. Fehler zurückgeben, wenn einige Aufgaben fehlgeschlagen sind.
12

Soweit ich weiß, soll dies parallel ausgeführt werden, daher glaube ich nicht, dass Ihr Code fehlerhaft ist. Wie Gabriel erwähnte, konnte man die Fertigstellung der Aufgaben abwarten.

[HttpGet]
public async Task<IActionResult> myControllerAction()
{      
  var data1 = new sometype1();
  var data2 = new sometype2();
  var data3 = new List<sometype3>();

  var t1 = Task.Run(() => { data1 = service.getdata1(); });
  var t2 = Task.Run(() => { data2 = service.getdata2(); });
  var t3 = Task.Run(() => { data3 = service.getdata3(); });

  await Task.WhenAll(t1, t2, t3); // otherwise a thread will be blocked here

  var data = new returnObject
  {
      d1 = data1,
      d2 = data2,
      d2 = data3
  };

 return Ok(data);
}

Sie können auch die Ergebnisse der Aufgaben verwenden, um einige Codezeilen zu speichern und den Code insgesamt "besser" zu machen (siehe Kommentare):

[HttpGet]
public async Task<IActionResult> myControllerAction()
{      
  var t1 = Task.Run(() => service.getdata1() );
  var t2 = Task.Run(() => service.getdata2() );
  var t3 = Task.Run(() => service.getdata3() );

  await Task.WhenAll(t1, t2, t3); // otherwise a thread will be blocked here

  var data = new returnObject
  {
      d1 = t1.Result,
      d2 = t2.Result,
      d2 = t3.Result
  };

 return Ok(data);
}
1
Thomas D.