Sök…


Introduktion

I C # kommer en metod som förklaras async inte att blockeras inom en synkron process, om du använder I / O-baserade operationer (t.ex. webbåtkomst, arbetar med filer, ...). Resultatet av sådana async-markerade metoder kan await användning av nyckelordet som await .

Anmärkningar

En async metod kan returnera void , Task eller Task<T> .

Returtypens Task väntar på att metoden ska slutföras och resultatet blir void . Task<T> kommer att returnera ett värde från typ T efter att metoden är klar.

async bör returnera Task eller Task<T> , i motsats till void , under nästan alla omständigheter. async void metoder kan inte await , vilket leder till en mängd olika problem. Det enda scenariet där en async ska återgå till void är i fallet med en händelsehanterare.

async / await fungerar genom att omvandla din async metod till en tillståndsmaskin. Det gör detta genom att skapa en struktur bakom kulisserna som lagrar det aktuella tillståndet och alla sammanhang (som lokala variabler), och exponerar en MoveNext() -metod för att förflytta tillstånd (och köra en tillhörande kod) när en väntad väntan är klar.

Enkla samtal i följd

public async Task<JobResult> GetDataFromWebAsync()
{
  var nextJob = await _database.GetNextJobAsync();
  var response = await _httpClient.GetAsync(nextJob.Uri);
  var pageContents = await response.Content.ReadAsStringAsync();
  return await _database.SaveJobResultAsync(pageContents);
}

Det viktigaste att notera här är att medan varje await -ED metod kallas asynkront - och under tiden för det samtalet kontrollen gav tillbaka till systemet - flödet inne i metoden är linjär och inte kräver någon speciell behandling på grund av asynkronism. Om någon av de metoder som kallas misslyckas behandlas undantaget "som förväntat", vilket i detta fall innebär att metodutförandet avbryts och undantaget kommer att gå upp i bunten.

Try / Catch / Finally

6,0

Från och med C # 6.0 kan nyckelordet nu await en catch och finally blockeras.

try {
   var client = new AsyncClient();
   await client.DoSomething();
} catch (MyException ex) {
   await client.LogExceptionAsync();
   throw;
} finally {
   await client.CloseAsync();
}
5,0 6,0

Innan C # 6.0 skulle du behöva göra något enligt följande. Observera att 6.0 också rensat upp nollkontrollerna med Null Propagating-operatören .

AsynClient client;
MyException caughtException;
try {
     client = new AsyncClient();
     await client.DoSomething();
} catch (MyException ex) {
     caughtException = ex;
}

if (client != null) {
    if (caughtException != null) {
       await client.LogExceptionAsync();
    }
    await client.CloseAsync();
    if (caughtException != null) throw caughtException;
}

Observera att om du väntar på en uppgift som inte har skapats av async (t.ex. en uppgift som skapats av Task.Run ), kan vissa felsökare bryta på undantag som kastas av uppgiften även om den till synes hanteras av den omgivande försök / fången. Detta händer eftersom felsökaren anser att den inte kan hanteras med avseende på användarkod. I Visual Studio finns det ett alternativ som heter "Just My Code" , som kan inaktiveras för att förhindra att felsökaren bryter i sådana situationer.

Web.config-inställning till mål 4.5 för korrekt async-beteende.

Web.config-systemet.web.httpRuntime måste inriktas på 4.5 för att säkerställa att tråden kommer att hålla begäran sammanhanget innan du fortsätter din async-metod.

<httpRuntime targetFramework="4.5" />

Async och inväntar har odefinierat beteende på ASP.NET före 4.5. Async / vänta kommer att återupptas på en godtycklig tråd som kanske inte har förfrågningskontext. Program under last misslyckas slumpmässigt med undantag från nollreferenser som går åt HttpContext efter väntan. Att använda HttpContext.Current i WebApi är farligt på grund av async

Samtidiga samtal

Det är möjligt att vänta på flera samtal samtidigt genom att först åberopa de väntade uppgifterna och sedan vänta på dem.

public async Task RunConcurrentTasks()
{
    var firstTask = DoSomethingAsync();
    var secondTask = DoSomethingElseAsync();

    await firstTask;
    await secondTask;
}

Alternativt kan Task.WhenAll användas för att gruppera flera uppgifter i en enda Task , som slutförs när alla dess godkända uppgifter är slutförda.

public async Task RunConcurrentTasks()
{
    var firstTask = DoSomethingAsync();
    var secondTask = DoSomethingElseAsync();

    await Task.WhenAll(firstTask, secondTask);
}

Du kan också göra detta i en slinga, till exempel:

List<Task> tasks = new List<Task>();
while (something) {
    // do stuff
    Task someAsyncTask = someAsyncMethod();
    tasks.Add(someAsyncTask);
}

await Task.WhenAll(tasks);

För att få resultat från en uppgift efter att ha väntat på flera uppgifter med Task.WhenAll, vänta helt enkelt på uppgiften igen. Eftersom uppgiften redan är klar kommer den bara att returnera resultatet

var task1 = SomeOpAsync();
var task2 = SomeOtherOpAsync();

await Task.WhenAll(task1, task2);

var result = await task2;

Även Task.WhenAny kan användas för att utföra flera uppgifter parallellt, som Task.WhenAll ovan, med skillnaden att den här metoden kommer att slutföras när någon av de medföljande uppgifterna kommer att slutföras.

public async Task RunConcurrentTasksWhenAny()
{
    var firstTask = TaskOperation("#firstTask executed");
    var secondTask = TaskOperation("#secondTask executed");
    var thirdTask = TaskOperation("#thirdTask executed");
    await Task.WhenAny(firstTask, secondTask, thirdTask);
}

Task returneras av RunConcurrentTasksWhenAny kommer att slutföras när någon av första firstTask , andra secondTask eller thirdTask klar.

Vänta på operatörs- och async-nyckelord

await operatör och async nyckelord samman:

Den asynkrona metoden där väntan används måste modifieras av sökordet async .

Det motsatta är inte alltid sant: du kan markera en metod som async utan att använda await i kroppen.

Det som await faktiskt är att avbryta körningen av koden tills den väntade uppgiften är klar. alla uppgifter kan vänta.

Obs! Du kan inte vänta på asynkmetod som returnerar ingenting (ogiltigt).

Egentligen är ordet "avstängd" lite vilseledande eftersom inte bara exekveringen stoppar, utan tråden kan bli fri för att utföra andra operationer. Under huven implementeras await med lite kompilermagi: den delar upp en metod i två delar - före och efter await . Den senare delen körs när den väntade uppgiften är klar.

Om vi ignorerar några viktiga detaljer, gör kompilatorn ungefär detta åt dig:

public async Task<TResult> DoIt()
{
    // do something and acquire someTask of type Task<TSomeResult>  
    var awaitedResult = await someTask;
    // ... do something more and produce result of type TResult
    return result;
}

blir:

public Task<TResult> DoIt()
{
    // ...
    return someTask.ContinueWith(task => {
        var result = ((Task<TSomeResult>)task).Result;
        return DoIt_Continuation(result);
    });
}

private TResult DoIt_Continuation(TSomeResult awaitedResult)
{
    // ...
}

Alla vanliga metoder kan förvandlas till async på följande sätt:

await Task.Run(() => YourSyncMethod());

Detta kan vara fördelaktigt när du behöver köra en lång körningsmetod på UI-tråden utan att frysa gränssnittet.

Men det finns en mycket viktig kommentar här: Asynkron betyder inte alltid samtidigt (parallell eller till och med flergängad). Även på en enda tråd async - await fortfarande asynkron kod. Se till exempel denna anpassade uppgiftsschemaläggare . En sådan "galen" uppgiftsschemaläggare kan helt enkelt förvandla uppgifter till funktioner som kallas inom behandling av meddelandeslinga.

Vi måste fråga oss själva: Vilken tråd kommer att genomföra fortsättningen av vår metod DoIt_Continuation ?

Som standard schemaläggs den await operatören exekveringen av fortsättning med det aktuella synkroniseringssammanhanget . Det betyder att WinForms och WPF fortsätter som standard i UI-tråden som standard. Om du av någon anledning behöver ändra detta beteende använder du metoden Task.ConfigureAwait() :

await Task.Run(() => YourSyncMethod()).ConfigureAwait(continueOnCapturedContext: false);

Återvända en uppgift utan att vänta

Metoder som utför asynkron operation behöver inte await om:

  • Det finns bara ett asynkront samtal i metoden
  • Det asynkrona samtalet är i slutet av metoden
  • Fångst / hantering undantag som kan hända inom uppgiften är inte nödvändigt

Tänk på den här metoden som returnerar en Task :

public async Task<User> GetUserAsync(int id)
{
    var lookupKey = "Users" + id;

    return await dataStore.GetByKeyAsync(lookupKey);
}

Om GetByKeyAsync har samma signatur som GetUserAsync (returnerar en Task<User> GetUserAsync Task<User> ) kan metoden förenklas:

public Task<User> GetUserAsync(int id)
{
    var lookupKey = "Users" + id;

    return dataStore.GetByKeyAsync(lookupKey);
}

I det här fallet behöver metoden inte markeras async , även om den förformar en asynkron operation. Uppgiften som returneras av GetByKeyAsync överförs direkt till samtalsmetoden, där den kommer att await .

Viktigt : Återvända Task istället för att vänta på den, ändrar metodens undantagsbeteende, eftersom det inte kommer att kasta undantaget in i metoden som startar uppgiften utan i metoden som väntar på den.

public Task SaveAsync()
{
    try {
        return dataStore.SaveChangesAsync();
    }
    catch(Exception ex)
    {
        // this will never be called
        logger.LogException(ex);
    }
}

// Some other code calling SaveAsync()

// If exception happens, it will be thrown here, not inside SaveAsync()
await SaveAsync();

Detta kommer att förbättra prestanda eftersom det sparar kompilatorn genereringen av en extra async- tillståndsmaskin.

Blockering av async-kod kan orsaka dödlås

Det är en dålig praxis att blockera på async-samtal eftersom det kan orsaka dödlås i miljöer som har en synkroniseringskontext. Den bästa praxis är att använda async / vänta "hela vägen ner." Till exempel orsakar följande Windows Forms-kod en dödlås:

private async Task<bool> TryThis()
{
    Trace.TraceInformation("Starting TryThis");
    await Task.Run(() =>
    {
        Trace.TraceInformation("In TryThis task");
        for (int i = 0; i < 100; i++)
        {
            // This runs successfully - the loop runs to completion
            Trace.TraceInformation("For loop " + i);
            System.Threading.Thread.Sleep(10);
        }
    });

    // This never happens due to the deadlock
    Trace.TraceInformation("About to return");
    return true;
}

// Button click event handler
private void button1_Click(object sender, EventArgs e)
{
    // .Result causes this to block on the asynchronous call
    bool result = TryThis().Result;
    // Never actually gets here
    Trace.TraceInformation("Done with result");
}

När async-samtalet slutförs väntar det på att synkroniseringskonteksten blir tillgängligt. Men händelseshanteraren "håller fast" i synkroniseringssituationen medan den väntar på att TryThis() -metoden ska slutföras och därmed orsakar en cirkulär vänta.

För att fixa detta bör kod ändras till

private async void button1_Click(object sender, EventArgs e)
{
  bool result = await TryThis();
  Trace.TraceInformation("Done with result");
}

Obs: händelsehanterare är den enda platsen där async void ska användas (eftersom du inte kan vänta på en async void metod).

Async / wait kommer bara att förbättra prestanda om det gör det möjligt för maskinen att utföra ytterligare arbete

Tänk på följande kod:

public async Task MethodA()
{
     await MethodB();
     // Do other work
}

public async Task MethodB()
{
     await MethodC();
     // Do other work
}

public async Task MethodC()
{
     // Or await some other async work
     await Task.Delay(100);
}

Detta kommer inte att prestera bättre än

public void MethodA()
{
     MethodB();
     // Do other work
}

public void MethodB()
{
     MethodC();
     // Do other work
}

public void MethodC()
{
     Thread.Sleep(100);
}

Det primära syftet med async / invänta är att låta maskinen utföra ytterligare arbete - till exempel att låta den ringande tråden göra annat arbete medan den väntar på ett resultat från någon I / O-operation. I det här fallet får den ringande tråden aldrig göra mer arbete än vad det annars skulle ha kunnat göra, så det finns ingen prestationsförstärkning än att bara ringa MethodA() , MethodB() och MethodC() synkront.



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow