Recherche…


Remarques

Pour exécuter l'un de ces exemples, appelez-les simplement comme ça:

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

ASP.NET Configure Await

Lorsque ASP.NET gère une demande, un thread est attribué à partir du pool de threads et un contexte de demande est créé. Le contexte de la demande contient des informations sur la requête en cours, accessibles via la propriété statique HttpContext.Current . Le contexte de demande pour la demande est ensuite affecté au thread qui gère la demande.

Un contexte de requête donné ne peut être actif que sur un thread à la fois .

Lorsque l'exécution est en await , le thread qui gère une demande est renvoyé au pool de threads pendant que la méthode asynchrone s'exécute et que le contexte de la demande est libre pour qu'un autre thread puisse l'utiliser.

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

Lorsque la tâche se termine, le pool de threads assigne un autre thread pour continuer l'exécution de la demande. Le contexte de la demande est ensuite affecté à ce thread. Cela peut être ou ne pas être le fil d'origine.

Blocage

Lorsque le résultat d'un appel de méthode async est attendu, des blocages synchrones peuvent survenir. Par exemple, le code suivant provoquera un blocage lorsque IndexSync() est appelé:

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

En effet, par défaut, la tâche attendue, dans ce cas, db.Products.ToListAsync() capturera le contexte (dans le cas d'ASP.NET, le contexte de la demande) et tentera de l'utiliser une fois terminé.

Lorsque toute la pile d'appels est asynchrone, il n'y a pas de problème car, une fois que l' await est atteinte, le thread d'origine est libéré, libérant le contexte de la requête.

Lorsque nous bloquons de manière synchrone à l'aide de Task.Result ou Task.Wait() (ou d'autres méthodes de blocage), le thread d'origine est toujours actif et conserve le contexte de la requête. La méthode attendue fonctionne toujours de manière asynchrone et une fois que le rappel tente de s'exécuter, c'est-à-dire une fois la tâche attendue renvoyée, elle tente d'obtenir le contexte de la requête.

Par conséquent, le blocage se produit car, pendant que le thread de blocage avec le contexte de requête attend la fin de l'opération asynchrone, l'opération asynchrone tente d'obtenir le contexte de la requête pour terminer.

ConfigureAwait

Par défaut, les appels à une tâche attendue captureront le contexte actuel et tenteront de reprendre l'exécution sur le contexte une fois terminé.

En utilisant ConfigureAwait(false) cela peut être évité et les blocages peuvent être évités.

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

Cela peut éviter les blocages quand il est nécessaire de bloquer le code asynchrone, mais cela entraîne la perte du contexte dans la suite (code après l'appel à attendre).

Dans ASP.NET, cela signifie que si votre code suit un appel pour await someTask.ConfigureAwait(false); tente d'accéder aux informations du contexte, par exemple HttpContext.Current.User alors les informations ont été perdues. Dans ce cas, le HttpContext.Current est nul. Par exemple:

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

Si ConfigureAwait(true) est utilisé (équivalent à ne pas avoir ConfigureAwait du tout) alors à la fois l' user et user2 sont peuplées avec les mêmes données.

Pour cette raison, il est souvent recommandé d'utiliser ConfigureAwait(false) dans le code de la bibliothèque où le contexte n'est plus utilisé.

Async / attente

Vous trouverez ci-dessous un exemple simple d'utilisation de async / waiting pour effectuer des tâches fastidieuses en arrière-plan, tout en conservant la possibilité d'effectuer d'autres tâches qui n'ont pas besoin d'attendre les tâches fastidieuses.

Cependant, si vous avez besoin de travailler avec le résultat de la méthode chronophage ultérieurement, vous pouvez le faire en attendant l'exécution.

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

Voir ci-dessous un exemple simple d'utilisation d'un objet BackgroundWorker pour effectuer des opérations chronophages dans un thread d'arrière-plan.

Tu dois:

  1. Définissez une méthode de travail qui effectue le travail DoWork et appelez-la à partir d'un gestionnaire d'événements pour l'événement DoWork d'un BackgroundWorker .
  2. Commencez l'exécution avec RunWorkerAsync . Tout argument requis par la méthode de travailleur attaché à DoWork peut être transmis via le DoWorkEventArgs paramètre à RunWorkerAsync .

Outre l'événement DoWork , la classe BackgroundWorker définit également deux événements à utiliser pour interagir avec l'interface utilisateur. Ce sont facultatifs.

  • L'événement RunWorkerCompleted est déclenché lorsque les gestionnaires DoWork sont terminés.
  • L'événement ProgressChanged est déclenché lorsque la méthode ReportProgress est appelée.
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);
}

Tâche

Voir ci-dessous un exemple simple d'utilisation d'une Task pour effectuer des Task fastidieuses en arrière-plan.

Tout ce que vous avez à faire est d’emballer votre méthode intensive en temps dans un appel 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);
}

Fil

Voir ci-dessous un exemple simple d'utilisation d'un Thread pour effectuer des tâches fastidieuses en arrière-plan.

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

Comme vous pouvez le constater, nous ne pouvons pas renvoyer une valeur de notre TimeIntensiveMethod car Thread attend une méthode void comme paramètre.

Pour obtenir une valeur de retour d'un Thread utilisez un événement ou le suivant:

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

Tâche "exécuter et oublier" extension

Dans certains cas (par exemple, la journalisation), il peut être utile d'exécuter une tâche et ne pas attendre le résultat. L'extension suivante permet d'exécuter la tâche et de continuer l'exécution du code de repos:

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

Le résultat est attendu uniquement dans la méthode d'extension. Comme async / await est utilisé, il est possible d'attraper une exception et d'appeler une méthode facultative pour la gérer.

Un exemple d'utilisation de l'extension:

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
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow