wake-up-neo.net

async Task <HttpResponseMessage> Get VS HttpResponseMessage Get

Ich würde deine Hilfe im Folgenden brauchen. Seit fast einem Monat lese ich über Aufgaben und Async.

Ich wollte versuchen, mein neu erworbenes Wissen in einem einfachen wep-api-Projekt umzusetzen. Ich habe die folgenden Methoden und beide arbeiten wie erwartet:

 public HttpResponseMessage Get()
 {
        var data = _userServices.GetUsers();
        return Request.CreateResponse(HttpStatusCode.OK, data);
 }

public async Task<HttpResponseMessage> Get()
{
        var data = _userServices.GetUsers();


        return await Task<HttpResponseMessage>.Factory.StartNew(() =>
        {
           return Request.CreateResponse(HttpStatusCode.OK, data);
        });
 }

Also die frage. Ich habe versucht, Fiddler zu verwenden und zu sehen, was der Unterschied zwischen diesen beiden ist. Der Async ist etwas schneller, aber was ist der eigentliche Vorteil, wenn man so etwas in einer Web-API implementiert?

7
Veronica_Zotali

Wie bereits erwähnt, besteht der Punkt von async in ASP.NET darin, dass ein Thread des ASP.NET-Thread-Pools freigegeben wird. Dies funktioniert hervorragend für natürlich asynchrone Operationen wie E/A-gebundene Operationen, da dies ein Thread weniger auf dem Server ist (es gibt keinen Thread, der die asynchrone Operation "verarbeitet", wie ich es in meinem Blog erklärt habe ). Der hauptsächliche Vorteil von async auf der Serverseite ist daher Skalierbarkeit .

Sie möchten jedoch Task.Run (und, noch schlimmer, Task.Factory.StartNew) in ASP.NET vermeiden. Ich bezeichne das als "gefälschte Asynchronie", weil sie gerade einen Thread-Pool-Thread synchron/blockieren. Sie sind in UI-Apps nützlich, bei denen Sie die Arbeit aus dem UI-Thread heraus verschieben möchten, damit die UI reaktionsfähig bleibt. Sie sollten (fast) jedoch niemals auf ASP.NET oder anderen Server-Apps verwendet werden.

Die Verwendung von Task.Run oder Task.Factory.StartNew in ASP.NET verringert die Skalierbarkeit. Sie verursachen einige unnötige Threadwechsel. Bei länger laufenden Vorgängen können Sie die Heuristiken des ASP.NET-Threadpools verwerfen, wodurch zusätzliche Threads erstellt und später unnötig gelöscht werden. Ich untersuche diese Leistungsprobleme Schritt für Schritt in einem anderen Blogbeitrag .

Sie müssen also darüber nachdenken, was die einzelnen Aktionen tun und ob sollte asynchron sein. Wenn dies der Fall ist, sollte diese Aktion asynchron sein. In Ihrem Fall:

public HttpResponseMessage Get()
{
  var data = _userServices.GetUsers();
  return Request.CreateResponse(HttpStatusCode.OK, data);
}

Was genau macht Request.CreateResponse? Es wird nur ein Antwortobjekt erstellt. Das war's - nur eine schicke new. Dort gibt es keine I/O-Vorgänge, und es ist sicherlich nicht etwas, das zu einem Hintergrundthread verschoben werden muss.

GetUsers ist jedoch viel interessanter. Das klingt eher nach einem Datenlesen, der ist I/O-basiert. Wenn Ihr Backend skalierbar ist (z. B. Azure SQL/Tables/etc), sollten Sie zuerst die async-Einstellung vornehmen. Sobald Ihr Dienst eine GetUsersAsync verfügbar macht, könnte diese Aktion ebenfalls async werden:

public async Task<HttpResponseMessage> Get()
{
  var data = await _userServices.GetUsersAsync();
  return Request.CreateResponse(HttpStatusCode.OK, data);
}
22
Stephen Cleary

Es ist sinnvoller, wenn der Aufruf mit größeren IO -Operationen stattfindet. Ja, Async ist schneller, da der Anforderungs-Thread für die Zeit freigegeben wird, zu der die Vorgänge ausgeführt werden. Aus Sicht des Webservers geben Sie dem Pool also einen Thread zurück, der vom Server für alle zukünftigen Anrufe verwendet werden kann.

Also für z. Wenn Sie einen Suchvorgang auf dem SQL-Server durchführen, möchten Sie möglicherweise asynchron arbeiten und den Leistungsvorteil sehen.

Dies ist gut für die Skalierbarkeit, die mehrere Server umfasst.

So zum Beispiel Wenn SearchRecordAsync sein SQL an die Datenbank sendet, gibt es eine unvollständige Aufgabe zurück. Wenn die Anforderung das Erwarten trifft, gibt sie den Anforderungsthread an den Threadpool zurück. Später, wenn die DB-Operation abgeschlossen ist, wird ein Anforderungsthread aus dem Threadpool genommen und zum Fortsetzen der Anforderung verwendet.

Auch wenn Sie keine SQL-Operationen verwenden, sagen Sie, Sie möchten eine E-Mail an 10 Personen senden. In diesem Fall macht auch asynchron mehr Sinn.

Async ist auch sehr praktisch, um den Fortschritt eines langen Ereignisses anzuzeigen. Der Benutzer erhält also weiterhin die aktive GUI, während die Task im Hintergrund ausgeführt wird.

Um dies zu verstehen, schauen Sie sich bitte dieses Beispiel an.

Hier versuche ich, eine Aufgabe namens send mail zu initiieren. Interim Ich möchte die Datenbank aktualisieren, während der Hintergrund die Sendemailaufgabe ausführt.

Sobald die Datenbankaktualisierung durchgeführt wurde, wartet sie darauf, dass die Sendemail-Aufgabe abgeschlossen ist. Bei diesem Ansatz ist es jedoch ziemlich klar, dass ich Task im Hintergrund ausführen und trotzdem mit dem ursprünglichen (Haupt-) Thread fortfahren kann.

using System;
using System.Threading;
using System.Threading.Tasks;

public class Program
{
    public static void Main()
    {
        Console.WriteLine("Starting Send Mail Async Task");
        Task task = new Task(SendMessage);
        task.Start();
        Console.WriteLine("Update Database");
        UpdateDatabase();

        while (true)
        {
            // dummy wait for background send mail.
            if (task.Status == TaskStatus.RanToCompletion)
            {
                break;
            }
        }

    }

    public static async void SendMessage()
    {
        // Calls to TaskOfTResult_MethodAsync
        Task<bool> returnedTaskTResult = MailSenderAsync();
        bool result = await returnedTaskTResult;

        if (result)
        {
            UpdateDatabase();
        }

        Console.WriteLine("Mail Sent!");
    }

    private static void UpdateDatabase()
    {
        for (var i = 1; i < 1000; i++) ;
        Console.WriteLine("Database Updated!");
    }

    private static async Task<bool> MailSenderAsync()
    {
        Console.WriteLine("Send Mail Start.");
        for (var i = 1; i < 1000000000; i++) ;
        return true;
    }
}
2
codebased

Die Verwendung von async auf Ihrem Server kann die Skalierbarkeit erheblich verbessern, da der Thread, der die Anforderung bedient, zur Bearbeitung anderer Anforderungen freigegeben wird, während der Vorgang async ausgeführt wird. In einer synchronen IO -Operation würde der Thread beispielsweise angehalten und nichts tun, bis die Operation abgeschlossen ist und nicht für eine andere Anforderung verfügbar wäre.

Wenn Sie jedoch Task.Factory.StartNew verwenden, wird ein weiterer Thread gestartet , sodass Sie die Skalierbarkeitsvorteile nicht erhalten. Ihr ursprünglicher Thread kann wiederverwendet werden, aber Sie haben die Arbeit in einen anderen Thread verschoben, so dass es keinen Nettogewinn gibt. In der Tat ist der Wechsel zu einem anderen Thread mit Kosten verbunden, aber das ist minimal.

Bei wirklich asynchronen Operationen wird kein Thread gestartet, und ich würde nachsehen, ob eine solche Operation existiert oder ob eine für Request.CreateResponse geschrieben werden kann. Dann wäre Ihr Code viel besser skalierbar. Wenn nicht, halten Sie sich lieber an den synchronen Ansatz.

1
Ned Stoyanov