Zoeken…


Opmerkingen

Om een van deze voorbeelden uit te voeren, noem ze gewoon zo:

static void Main()
{
    new Program().ProcessDataAsync();
    Console.ReadLine();
}

ASP.NET Configureren Wachten

Wanneer ASP.NET een aanvraag verwerkt, wordt een thread toegewezen uit de threadpool en wordt een aanvraagcontext gemaakt. De aanvraagcontext bevat informatie over de huidige aanvraag die toegankelijk is via de statische eigenschap HttpContext.Current . De aanvraagcontext voor het verzoek wordt vervolgens toegewezen aan de thread die het verzoek behandelt.

Een gegeven verzoekcontext kan slechts op één thread tegelijk actief zijn .

Wanneer de uitvoering await , wordt de thread die een verzoek await , teruggestuurd naar de threadpool terwijl de asynchrone methode wordt uitgevoerd en de aanvraagcontext gratis is voor een andere thread.

public async Task<ActionResult> Index()
{
    // Execution on the initially assigned thread
    var products = await dbContext.Products.ToListAsync();

    // Execution resumes on a "random" thread from the pool
    // Execution continues using the original request context.
    return View(products);
}

Wanneer de taak is voltooid, wijst de threadpool een andere thread toe om de uitvoering van het verzoek voort te zetten. De aanvraagcontext wordt vervolgens toegewezen aan deze thread. Dit kan al dan niet de originele draad zijn.

Het blokkeren

Wanneer het resultaat van een async methode wordt gewacht, kunnen synchroon deadlocks ontstaan. De volgende code resulteert bijvoorbeeld in een impasse wanneer IndexSync() wordt aangeroepen:

public async Task<ActionResult> Index()
{
    // Execution on the initially assigned thread
    List<Product> products = await dbContext.Products.ToListAsync();

    // Execution resumes on a "random" thread from the pool
    return View(products);
}

public ActionResult IndexSync()
{
    Task<ActionResult> task = Index();

    // Block waiting for the result synchronously
    ActionResult result = Task.Result;

    return result;       
}

Dit komt omdat standaard de verwachte taak, in dit geval db.Products.ToListAsync() de context vastlegt (in het geval van ASP.NET de aanvraagcontext) en deze probeert te gebruiken zodra deze is voltooid.

Wanneer de gehele call-stack asynchroon is, is er geen probleem omdat, zodra await is bereikt, de oorspronkelijke thread wordt vrijgegeven, waardoor de aanvraagcontext wordt vrijgemaakt.

Wanneer we synchroon blokkeren met Task.Result of Task.Wait() (of andere blokkeermethoden), is de oorspronkelijke thread nog steeds actief en blijft de aanvraagcontext behouden. De verwachte methode werkt nog steeds asynchroon en zodra de callback probeert uit te voeren, dat wil zeggen wanneer de verwachte taak is teruggekeerd, probeert deze de aanvraagcontext te verkrijgen.

Daarom ontstaat de impasse omdat, terwijl de blokkerende thread met de aanvraagcontext wacht tot de asynchrone bewerking is voltooid, de asynchrone bewerking de aanvraagcontext probeert te verkrijgen om te voltooien.

ConfigureAwait

Standaard wordt door oproepen voor een verwachte taak de huidige context vastgelegd en wordt geprobeerd de uitvoering in de context te hervatten zodra deze is voltooid.

Door ConfigureAwait(false) , kan dit worden voorkomen en kunnen deadlocks worden vermeden.

public async Task<ActionResult> Index()
{
    // Execution on the initially assigned thread
    List<Product> products = await dbContext.Products.ToListAsync().ConfigureAwait(false);

    // Execution resumes on a "random" thread from the pool without the original request context
    return View(products);
}

public ActionResult IndexSync()
{
    Task<ActionResult> task = Index();

    // Block waiting for the result synchronously
    ActionResult result = Task.Result;

    return result;       
}

Dit kan deadlocks voorkomen wanneer het nodig is om asynchrone code te blokkeren, maar dit gaat ten koste van het verlies van de context in het vervolg (code na de oproep om te wachten).

In ASP.NET betekent dit dat als uw code na een oproep await someTask.ConfigureAwait(false); probeert toegang te krijgen tot informatie uit de context, bijvoorbeeld HttpContext.Current.User dan is de informatie verloren gegaan. In dit geval is de HttpContext.Current nul. Bijvoorbeeld:

public async Task<ActionResult> Index()
{
    // Contains information about the user sending the request
    var user = System.Web.HttpContext.Current.User;

    using (var client = new HttpClient())
    {
        await client.GetAsync("http://google.com").ConfigureAwait(false);
    }

    // Null Reference Exception, Current is null
    var user2 = System.Web.HttpContext.Current.User;

    return View();
}

Als ConfigureAwait(true) wordt gebruikt (equivalent aan zonder ConfigureAwait helemaal niet) dan zowel user en de user2 zijn gevuld met dezelfde gegevens.

Om deze reden wordt het vaak aanbevolen om ConfigureAwait(false) in bibliotheekcode waar de context niet langer wordt gebruikt.

Async / af te wachten

Zie hieronder voor een eenvoudig voorbeeld van het gebruik van async / await om wat tijdintensieve dingen in een achtergrondproces te doen, met behoud van de optie om andere dingen te doen die niet hoeven te wachten op de tijdintensieve dingen om te voltooien.

Als u echter later met het resultaat van de tijdintensieve methode moet werken, kunt u dit doen door de uitvoering af te wachten.

public async Task ProcessDataAsync()
{
    // Start the time intensive method
    Task<int> task = TimeintensiveMethod(@"PATH_TO_SOME_FILE");

    // Control returns here before TimeintensiveMethod returns
    Console.WriteLine("You can read this while TimeintensiveMethod is still running.");

    // Wait for TimeintensiveMethod to complete and get its result
    int x = await task;
    Console.WriteLine("Count: " + x);
}

private async Task<int> TimeintensiveMethod(object file)
{
    Console.WriteLine("Start TimeintensiveMethod.");

    // Do some time intensive calculations...
    using (StreamReader reader = new StreamReader(file.ToString()))
    {
        string s = await reader.ReadToEndAsync();

        for (int i = 0; i < 10000; i++)
            s.GetHashCode();
    }
    Console.WriteLine("End TimeintensiveMethod.");

    // return something as a "result"
    return new Random().Next(100);
}

BackgroundWorker

Zie hieronder voor een eenvoudig voorbeeld van het gebruik van een BackgroundWorker object om tijdrovende bewerkingen in een achtergrondthread uit te voeren.

Je moet:

  1. Definieer een werkmethode die het tijdrovende werk doet en noem deze vanuit een gebeurtenishandler voor de DoWork gebeurtenis van een BackgroundWorker .
  2. Start de uitvoering met RunWorkerAsync . Elk argument vereist door de werknemer methode bevestigd aan DoWork kan worden doorgegeven via de DoWorkEventArgs parameter RunWorkerAsync .

Naast de DoWork gebeurtenis definieert de klasse BackgroundWorker ook twee gebeurtenissen die moeten worden gebruikt voor interactie met de gebruikersinterface. Deze zijn optioneel.

  • De gebeurtenis RunWorkerCompleted wordt geactiveerd wanneer de DoWork handlers zijn voltooid.
  • De gebeurtenis ProgressChanged wordt geactiveerd wanneer de methode ReportProgress wordt aangeroepen.
public void ProcessDataAsync()
{
    // Start the time intensive method
    BackgroundWorker bw = new BackgroundWorker();
    bw.DoWork += BwDoWork;
    bw.RunWorkerCompleted += BwRunWorkerCompleted;
    bw.RunWorkerAsync(@"PATH_TO_SOME_FILE");

    // Control returns here before TimeintensiveMethod returns
    Console.WriteLine("You can read this while TimeintensiveMethod is still running.");
}

// Method that will be called after BwDoWork exits
private void BwRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // we can access possible return values of our Method via the Parameter e
    Console.WriteLine("Count: " + e.Result);
}

// execution of our time intensive Method
private void BwDoWork(object sender, DoWorkEventArgs e)
{
    e.Result = TimeintensiveMethod(e.Argument);
}

private int TimeintensiveMethod(object file)
{
    Console.WriteLine("Start TimeintensiveMethod.");

    // Do some time intensive calculations...
    using (StreamReader reader = new StreamReader(file.ToString()))
    {
        string s = reader.ReadToEnd();

       for (int i = 0; i < 10000; i++)
            s.GetHashCode();
    }
    Console.WriteLine("End TimeintensiveMethod.");

    // return something as a "result"
    return new Random().Next(100);
}

Taak

Zie hieronder voor een eenvoudig voorbeeld van het gebruik van een Task om tijdrovende dingen in een achtergrondproces te doen.

Het enige wat u hoeft te doen is uw Task.Run() methode in een Task.Run() .

public void ProcessDataAsync()
{
    // Start the time intensive method
    Task<int> t = Task.Run(() => TimeintensiveMethod(@"PATH_TO_SOME_FILE"));

    // Control returns here before TimeintensiveMethod returns
    Console.WriteLine("You can read this while TimeintensiveMethod is still running.");

    Console.WriteLine("Count: " + t.Result);
}

private int TimeintensiveMethod(object file)
{
    Console.WriteLine("Start TimeintensiveMethod.");

    // Do some time intensive calculations...
    using (StreamReader reader = new StreamReader(file.ToString()))
    {
        string s = reader.ReadToEnd();

        for (int i = 0; i < 10000; i++)
            s.GetHashCode();
    }
    Console.WriteLine("End TimeintensiveMethod.");

    // return something as a "result"
    return new Random().Next(100);
}

Draad

Zie hieronder voor een eenvoudig voorbeeld van het gebruik van een Thread om tijdrovende dingen in een achtergrondproces te doen.

public async void ProcessDataAsync()
{
    // Start the time intensive method
    Thread t = new Thread(TimeintensiveMethod);

    // Control returns here before TimeintensiveMethod returns
    Console.WriteLine("You can read this while TimeintensiveMethod is still running.");
}

private void TimeintensiveMethod()
{
    Console.WriteLine("Start TimeintensiveMethod.");

    // Do some time intensive calculations...
    using (StreamReader reader = new StreamReader(@"PATH_TO_SOME_FILE"))
    {
        string v = reader.ReadToEnd();

        for (int i = 0; i < 10000; i++)
            v.GetHashCode();
    }
    Console.WriteLine("End TimeintensiveMethod.");
}

Zoals u kunt zien, kunnen we geen waarde retourneren uit onze TimeIntensiveMethod omdat Thread een ongeldige methode als parameter verwacht.

Om een retourwaarde van een Thread gebruikt u een gebeurtenis of de volgende:

int ret;
Thread t= new Thread(() => 
{
    Console.WriteLine("Start TimeintensiveMethod.");

    // Do some time intensive calculations...
    using (StreamReader reader = new StreamReader(file))
    {
        string s = reader.ReadToEnd();

        for (int i = 0; i < 10000; i++)
            s.GetHashCode();
    }
    Console.WriteLine("End TimeintensiveMethod.");

    // return something to demonstrate the coolness of await-async
    ret = new Random().Next(100);
});

t.Start();
t.Join(1000);
Console.Writeline("Count: " + ret);

Taak "Uitvoeren en vergeten" extensie

In bepaalde gevallen (bijvoorbeeld logboekregistratie) kan het handig zijn om een taak uit te voeren en niet op het resultaat te wachten. De volgende extensie maakt het mogelijk om de taak uit te voeren en door te gaan met de uitvoering van de restcode:

public static class TaskExtensions
{
    public static async void RunAndForget(
        this Task task, Action<Exception> onException = null)
    {
        try
        {
            await task;
        }
        catch (Exception ex)
        {
            onException?.Invoke(ex);
        }
    }
}

Het resultaat wordt alleen verwacht binnen de uitbreidingsmethode. Omdat async / await wordt gebruikt, is het mogelijk om een uitzondering op te vangen en een optionele methode aan te roepen voor de afhandeling.

Een voorbeeld van het gebruik van de extensie:

var task = Task.FromResult(0); // Or any other task from e.g. external lib.
task.RunAndForget(
    e =>
    {
        // Something went wrong, handle it.
    });


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