Zoeken…


Invoering

In C # blokkeert een async methode niet binnen een synchroon proces, in het geval dat u op I / O gebaseerde bewerkingen gebruikt (bijv. Webtoegang, werken met bestanden, ...). Het resultaat van dergelijke met async gemarkeerde methoden kan worden afgewacht met behulp van het sleutelwoord await .

Opmerkingen

Een async methode kan void , Task of Task<T> retourneren.

Het retourtype Task wacht tot de methode is voltooid en het resultaat is void . Task<T> retourneert een waarde van type T nadat de methode is voltooid.

async methoden moeten Task of Task<T> retourneren, in tegenstelling tot void , in bijna alle omstandigheden. async void methoden kunnen niet worden await , wat tot verschillende problemen leidt. Het enige scenario waarin een async void zou moeten zijn, is in het geval van een gebeurtenishandler.

async / await werkt door uw async methode te transformeren in een async . Het doet dit door een structuur achter de schermen te maken die de huidige status en elke context (zoals lokale variabelen) opslaat, en een MoveNext() -methode blootstelt om staten vooruit te gaan (en een bijbehorende code uit te voeren) wanneer een verwachte wachttijd is voltooid.

Eenvoudige opeenvolgende oproepen

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

Het belangrijkste om hier op te merken is dat terwijl elke await afgewerkte methode asynchroon wordt genoemd - en voor de tijd van die aanroep de controle wordt teruggegeven aan het systeem - de stroom binnen de methode lineair is en geen speciale behandeling vereist vanwege asynchrony. Als een van de genoemde methoden mislukt, wordt de uitzondering verwerkt "zoals verwacht", wat in dit geval betekent dat de uitvoering van de methode wordt afgebroken en de uitzondering hogerop komt.

Try / catch / slot

6.0

Vanaf C # 6.0 kan het await sleutelwoord nu worden gebruikt binnen een catch en finally blokkeren.

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

Voorafgaand aan C # 6.0, zou je iets moeten doen in de vorm van het volgende. Merk op dat 6.0 ook de nulcontroles heeft opgeruimd met de operator Null Propagating .

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

Houd er rekening mee dat als u wacht op een taak die niet is gemaakt door async (bijvoorbeeld een taak die is gemaakt door Task.Run ), sommige foutopsporingen kunnen breken op uitzonderingen die door de taak worden veroorzaakt, zelfs wanneer deze schijnbaar wordt afgehandeld door de omringende try / catch. Dit gebeurt omdat de debugger het als niet-afgehandeld beschouwt met betrekking tot gebruikerscode. In Visual Studio is er een optie genaamd "Alleen mijn code" , die kan worden uitgeschakeld om te voorkomen dat de debugger in dergelijke situaties breekt.

Web.config instellen op doel 4.5 voor correct asynchroon gedrag.

Het web.config system.web.httpRuntime moet 4.5 targeten om ervoor te zorgen dat de thread de verzoekcontext huurt voordat uw async-methode wordt hervat.

<httpRuntime targetFramework="4.5" />

Async en await hebben ongedefinieerd gedrag op ASP.NET vóór 4.5. Async / await wordt hervat op een willekeurige thread die mogelijk niet de aanvraagcontext heeft. Toepassingen die worden geladen zullen willekeurig mislukken met nul referentie-uitzonderingen die toegang krijgen tot de HttpContext na het wachten. Het gebruik van HttpContext.Current in WebApi is gevaarlijk vanwege async

Gelijktijdige oproepen

Het is mogelijk om meerdere oproepen tegelijkertijd af te wachten door eerst de taken te wachten en deze vervolgens af te wachten.

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

    await firstTask;
    await secondTask;
}

Als alternatief kan Task.WhenAll worden gebruikt om meerdere taken in een enkele Task te groeperen, die wordt voltooid als alle doorgegeven taken zijn voltooid.

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

    await Task.WhenAll(firstTask, secondTask);
}

Je kunt dit ook binnen een lus doen, bijvoorbeeld:

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

await Task.WhenAll(tasks);

Om resultaten van een taak te krijgen na het wachten op meerdere taken met Taak. Wacht gewoon gewoon opnieuw op de taak. Omdat de taak al is voltooid, wordt het resultaat gewoon teruggestuurd

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

await Task.WhenAll(task1, task2);

var result = await task2;

Ook kan de Task.WhenAny worden gebruikt om meerdere taken parallel uit te voeren, zoals de Task.WhenAll hierboven, met het verschil dat deze methode wordt voltooid wanneer een van de geleverde taken wordt voltooid.

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

De Task geretourneerd door RunConcurrentTasksWhenAny wordt voltooid wanneer een van firstTask , secondTask of thirdTask voltooid.

Wacht op operator en async-trefwoord

await operator en async trefwoord komen samen:

De asynchrone methode waarin wachten wordt gebruikt, moet worden gewijzigd met het async- sleutelwoord.

Het tegenovergestelde is niet altijd waar: je kunt een methode als async markeren zonder await in zijn lichaam te gebruiken.

Wat eigenlijk await , is de uitvoering van de code opschorten totdat de verwachte taak is voltooid; elke taak kan worden afgewacht.

Opmerking: u kunt niet wachten op de async-methode die niets retourneert (ongeldig).

Het woord 'opschorten' is eigenlijk een beetje misleidend, omdat niet alleen de uitvoering stopt, maar de thread mogelijk vrij komt voor het uitvoeren van andere bewerkingen. Onder de motorkap wordt await geïmplementeerd door een beetje compiler-magie: het splitst een methode in twee delen - voor en na await . Het laatste deel wordt uitgevoerd wanneer de verwachte taak is voltooid.

Als we enkele belangrijke details negeren, doet de compiler dit ruwweg voor u:

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

wordt:

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

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

Elke gebruikelijke methode kan op de volgende manier worden omgezet in asynchroon:

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

Dit kan voordelig zijn wanneer u een langlopende methode op de UI-thread moet uitvoeren zonder de UI te bevriezen.

Maar er is hier een heel belangrijke opmerking: asynchroon betekent niet altijd gelijktijdig (parallel of zelfs multi-threaded). Zelfs op een enkele thread, async - await nog steeds asynchrone code toe. Zie bijvoorbeeld deze aangepaste taakplanner . Zo'n 'gekke' taakplanner kan taken eenvoudig omzetten in functies die worden opgeroepen binnen de berichtenlusverwerking.

We moeten ons afvragen: welke draad zal de voortzetting van onze methode DoIt_Continuation ?

Standaard await de await operator de uitvoering van de voortzetting met de huidige synchronisatiecontext . Het betekent dat standaard voor WinForms en WPF voortzetting in de UI-thread wordt uitgevoerd. Als u om een of andere reden dit gedrag moet wijzigen, gebruikt u de methode Task.ConfigureAwait() :

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

Een taak retourneren zonder te wachten

Methoden die asynchrone bewerkingen uitvoeren, behoeven niet te gebruiken await indien:

  • Er is slechts één asynchrone aanroep binnen de methode
  • De asynchrone aanroep bevindt zich aan het einde van de methode
  • Een uitzondering voor het vangen / verwerken die binnen de taak kan optreden, is niet nodig

Overweeg deze methode die een Task retourneert:

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

    return await dataStore.GetByKeyAsync(lookupKey);
}

Als GetByKeyAsync dezelfde handtekening heeft als GetUserAsync (een Task<User> retourneren), kan de methode worden vereenvoudigd:

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

    return dataStore.GetByKeyAsync(lookupKey);
}

In dit geval hoeft de methode niet als async te worden gemarkeerd, hoewel deze een asynchrone bewerking uitvoert. De taak die wordt geretourneerd door GetByKeyAsync wordt direct doorgegeven aan de GetByKeyAsync , waar deze wordt await .

Belangrijk : als u de Task plaats van erop te wachten, wordt het uitzonderingsgedrag van de methode gewijzigd, omdat de uitzondering niet in de methode wordt gegooid die de taak start, maar in de methode die erop wacht.

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

Dit zal de prestaties verbeteren, omdat het de compiler het genereren van een extra async-statusmachine zal besparen.

Het blokkeren van async-code kan deadlocks veroorzaken

Het is een slechte gewoonte om asynchrone oproepen te blokkeren, omdat dit deadlocks kan veroorzaken in omgevingen met een synchronisatiecontext. De beste praktijk is om asynchroon te gebruiken / af te wachten "helemaal naar beneden". De volgende Windows Forms-code veroorzaakt bijvoorbeeld een impasse:

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

Nadat de async-oproep is voltooid, wacht deze in wezen totdat de synchronisatiecontext beschikbaar is. De gebeurtenishandler "houdt echter vast" aan de synchronisatiecontext terwijl deze wacht tot de methode TryThis() is voltooid, waardoor een cirkelvormige wachttijd wordt veroorzaakt.

Om dit te verhelpen, moet code worden gewijzigd in

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

Opmerking: gebeurtenishandlers zijn de enige plaats waar async void moet worden gebruikt (omdat u niet kunt wachten op een async void methode).

Async / await verbetert de prestaties alleen als de machine extra werk kan doen

Overweeg de volgende code:

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

Dit zal niet beter presteren dan

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

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

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

Het primaire doel van async / await is om de machine extra werk te laten doen, bijvoorbeeld om de aanroepende thread ander werk te laten doen terwijl het wacht op een resultaat van een I / O-bewerking. In dit geval mag de aanroepende thread nooit meer werk doen dan anders het geval zou zijn geweest, dus er is geen prestatiewinst ten opzichte van het eenvoudig MethodA() aanroepen van MethodA() , MethodB() en MethodC() .



Modified text is an extract of the original Stack Overflow Documentation
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow