Ricerca…


Osservazioni

Per eseguire uno di questi esempi, chiamali in questo modo:

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

ASP.NET Configure Await

Quando ASP.NET gestisce una richiesta, viene assegnato un thread dal pool di thread e viene creato un contesto di richiesta . Il contesto della richiesta contiene informazioni sulla richiesta corrente a cui è possibile accedere tramite la proprietà statica HttpContext.Current . Il contesto della richiesta per la richiesta viene quindi assegnato al thread che gestisce la richiesta.

Un determinato contesto di richiesta può essere attivo solo su un thread alla volta .

Quando l'esecuzione giunge in await , il thread che gestisce una richiesta viene restituito al pool di thread mentre viene eseguito il metodo asincrono e il contesto della richiesta è gratuito per un altro thread da utilizzare.

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

Al termine dell'attività, il pool di thread assegna un altro thread per continuare l'esecuzione della richiesta. Il contesto della richiesta viene quindi assegnato a questo thread. Questo potrebbe essere o non essere il thread originale.

Blocco

Quando si attende il risultato di una chiamata al metodo async , possono verificarsi deadlock sincroni . Ad esempio, il seguente codice genererà un deadlock quando viene chiamato IndexSync() :

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

Questo perché, per impostazione predefinita, l'attività attesa, in questo caso db.Products.ToListAsync() acquisisce il contesto (nel caso di ASP.NET il contesto della richiesta) e tenta di utilizzarlo una volta completato.

Quando l'intero stack di chiamate è asincrono, non ci sono problemi perché, una volta in await viene raggiunto, il thread originale viene rilasciato, liberando il contesto della richiesta.

Quando si blocca in modo sincrono utilizzando Task.Result o Task.Wait() (o altri metodi di blocco) il thread originale è ancora attivo e conserva il contesto della richiesta. Il metodo atteso funziona ancora in modo asincrono e una volta che il callback tenta di essere eseguito, ovvero quando viene restituita l'attività attesa, tenta di ottenere il contesto della richiesta.

Pertanto il deadlock si verifica perché mentre il thread di blocco con il contesto della richiesta è in attesa del completamento dell'operazione asincrona, l'operazione asincrona sta tentando di ottenere il contesto della richiesta per il completamento.

ConfigureAwait

Per impostazione predefinita, le chiamate a un'attività attesa acquisiscono il contesto corrente e tentano di riprendere l'esecuzione nel contesto una volta completato.

Usando ConfigureAwait(false) questo può essere prevenuto e le deadlock possono essere evitate.

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

Questo può evitare deadlock quando è necessario bloccare il codice asincrono, tuttavia ciò comporta il costo di perdere il contesto nella continuazione (codice dopo la chiamata in attesa).

In ASP.NET questo significa che se il tuo codice segue una chiamata per await someTask.ConfigureAwait(false); tenta di accedere alle informazioni dal contesto, ad esempio HttpContext.Current.User quindi le informazioni sono state perse. In questo caso, HttpContext.Current è null. Per esempio:

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

Se viene utilizzato ConfigureAwait(true) (equivalente a non avere affatto ConfigureAwait), sia l' user che l' user2 vengono popolati con gli stessi dati.

Per questo motivo, si consiglia spesso di utilizzare ConfigureAwait(false) nel codice della libreria in cui il contesto non è più utilizzato.

Async / await

Vedi sotto per un semplice esempio di come usare async / attendi di fare un po 'di tempo in un processo in background, mantenendo l'opzione di fare altre cose che non hanno bisogno di aspettare le cose che richiedono molto tempo per essere completate.

Tuttavia, se è necessario lavorare con il risultato del metodo intensivo in un secondo momento, è possibile farlo attendendo l'esecuzione.

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

Vedi sotto per un semplice esempio di come utilizzare un oggetto BackgroundWorker per eseguire operazioni che richiedono molto tempo in un thread in background.

Devi:

  1. Definire un metodo di lavoro che DoWork il lavoro intensivo e chiamarlo da un gestore di eventi per l'evento DoWork di un BackgroundWorker .
  2. Inizia l'esecuzione con RunWorkerAsync . Qualsiasi argomento richiesto dal metodo lavoratore attaccato DoWork possono essere trasmesse tramite la DoWorkEventArgs parametro RunWorkerAsync .

Oltre all'evento DoWork , la classe BackgroundWorker definisce anche due eventi che dovrebbero essere utilizzati per interagire con l'interfaccia utente. Questi sono opzionali.

  • L'evento RunWorkerCompleted viene attivato quando i gestori DoWork sono stati completati.
  • L'evento ProgressChanged viene attivato quando viene chiamato il metodo ReportProgress .
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);
}

Compito

Vedi sotto per un semplice esempio di come usare un Task per fare cose che richiedono molto tempo in un processo in background.

Tutto quello che devi fare è avvolgere il tuo metodo intensivo in una chiamata 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);
}

Filo

Vedi sotto per un semplice esempio di come usare un Thread per fare cose che richiedono tempo in un processo in background.

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

Come puoi vedere, non possiamo restituire un valore dal nostro TimeIntensiveMethod perché Thread aspetta un metodo void come parametro.

Per ottenere un valore di ritorno da una Thread utilizzare un evento o il seguente:

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

Attività "esegui e dimentica" l'estensione

In alcuni casi (ad esempio la registrazione) potrebbe essere utile eseguire l'attività e non attendere il risultato. La seguente estensione consente di eseguire attività e continuare l'esecuzione del codice di resto:

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

Il risultato è atteso solo all'interno del metodo di estensione. Poiché async / await viene utilizzato, è possibile rilevare un'eccezione e chiamare un metodo opzionale per gestirlo.

Un esempio di come utilizzare l'estensione:

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
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow