Suche…


Bemerkungen

Um eines dieser Beispiele auszuführen, nennen Sie es einfach so:

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

ASP.NET Konfigurieren Sie Await

Wenn ASP.NET eine Anforderung verarbeitet, wird ein Thread aus dem Threadpool zugewiesen und ein Anforderungskontext erstellt. Der Anforderungskontext enthält Informationen zur aktuellen Anforderung, auf die über die statische Eigenschaft HttpContext.Current zugegriffen werden kann. Der Anforderungskontext für die Anforderung wird dann dem Thread zugewiesen, der die Anforderung bearbeitet.

Ein gegebener Anforderungskontext kann jeweils nur für einen Thread aktiv sein .

Wenn die Ausführung await erreicht, wird der Thread, der eine Anforderung verarbeitet, an den Threadpool zurückgegeben, während die asynchrone Methode ausgeführt wird und der Anforderungskontext für einen anderen Thread frei ist.

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

Wenn die Aufgabe abgeschlossen ist, weist der Thread-Pool einen anderen Thread zu, um die Ausführung der Anforderung fortzusetzen. Der Anforderungskontext wird dann diesem Thread zugewiesen. Dies kann der ursprüngliche Thread sein oder nicht.

Blockierung

Wenn das Ergebnis eines async Methodenaufrufs auf synchron gewartet wird, können Deadlocks entstehen. Der folgende Code führt beispielsweise zu einem Deadlock, wenn IndexSync() aufgerufen wird:

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

Dies liegt daran, dass standardmäßig die erwartete Task in diesem Fall db.Products.ToListAsync() den Kontext erfasst (im Fall von ASP.NET den Anforderungskontext) und versucht, ihn zu verwenden, sobald er abgeschlossen ist.

Wenn der gesamte Call - Stack ist asynchron ist es kein Problem , denn sobald await den ursprünglichen Thread erreicht Mitteilung ist, den Anforderungskontext zu befreien.

Wenn wir synchron mit Task.Result oder Task.Wait() (oder anderen Blockierungsmethoden) blockieren, ist der ursprüngliche Thread noch aktiv und behält den Anforderungskontext bei. Die erwartete Methode arbeitet weiterhin asynchron und sobald der Callback versucht, auszuführen, dh nachdem die erwartete Task zurückgegeben wurde, versucht sie, den Anforderungskontext abzurufen.

Daher kommt es zu einem Deadlock, da der blockierende Thread mit dem Anforderungskontext darauf wartet, dass die asynchrone Operation abgeschlossen ist, während die asynchrone Operation versucht, den Anforderungskontext abzurufen, um abzuschließen.

ConfigureAwait

Aufrufe einer erwarteten Aufgabe erfassen standardmäßig den aktuellen Kontext und versuchen, die Ausführung im Kontext fortzusetzen, sobald sie abgeschlossen sind.

Durch die Verwendung von ConfigureAwait(false) kann dies verhindert und Deadlocks vermieden werden.

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

Dadurch können Deadlocks vermieden werden, wenn asynchroner Code gesperrt werden muss. Dies geht jedoch mit dem Verlust des Kontexts in der Fortsetzung (Code nach dem Aufruf von wait) einher.

In ASP.NET bedeutet dies, dass, wenn Ihr Code einem Aufruf folgt, await someTask.ConfigureAwait(false); zu await someTask.ConfigureAwait(false); versucht, auf Informationen aus dem Kontext zuzugreifen, z. B. HttpContext.Current.User dann sind die Informationen verloren gegangen. In diesem Fall ist HttpContext.Current . Zum Beispiel:

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

Wenn ConfigureAwait(true) verwendet wird (äquivalent dazu, überhaupt kein ConfigureAwait zu haben), werden sowohl user als auch user mit denselben Daten user2 .

Aus diesem Grund wird häufig empfohlen, ConfigureAwait(false) in Bibliothekscode zu verwenden, in dem der Kontext nicht mehr verwendet wird.

Asynchron / warten

Im Folgenden finden Sie ein einfaches Beispiel für die Verwendung von async / await, um einige zeitintensive Aufgaben in einem Hintergrundprozess auszuführen, während die Option beibehalten wird, andere Aufgaben auszuführen, die nicht auf die zeitintensiven Aufgaben warten müssen.

Wenn Sie jedoch später mit dem Ergebnis der zeitintensiven Methode arbeiten müssen, können Sie dies durch Abwarten der Ausführung tun.

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

Nachfolgend finden Sie ein einfaches Beispiel für die Verwendung eines BackgroundWorker Objekts zum Ausführen zeitintensiver Vorgänge in einem Hintergrundthread.

Du musst:

  1. Definieren Sie eine Arbeitermethode, die die zeitintensive Arbeit erledigt, und rufen Sie sie von einem Ereignishandler für das DoWork Ereignis eines BackgroundWorker .
  2. Starten Sie die Ausführung mit RunWorkerAsync . Jedes Argument , die der Arbeitnehmer Verfahren erforderlich an DoWork kann über die übergeben werden DoWorkEventArgs Parameter RunWorkerAsync .

Zusätzlich zum DoWork Ereignis definiert die BackgroundWorker Klasse zwei Ereignisse, die für die Interaktion mit der Benutzeroberfläche verwendet werden sollen. Diese sind optional.

  • Das Ereignis RunWorkerCompleted wird ausgelöst, wenn die DoWork Handler abgeschlossen sind.
  • Das ProgressChanged Ereignis wird ausgelöst, wenn die ReportProgress Methode aufgerufen wird.
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);
}

Aufgabe

Nachfolgend finden Sie ein einfaches Beispiel für die Verwendung einer Task , um einige Zeit in einem Hintergrundprozess zu erledigen.

Sie müssen nur Ihre zeitintensive Methode in einen Task.Run() Aufruf 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);
}

Faden

Im Folgenden finden Sie ein einfaches Beispiel für die Verwendung eines Thread , um einige Zeit in einem Hintergrundprozess zu erledigen.

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

Wie Sie sehen, können wir keinen Wert aus unserer TimeIntensiveMethod da Thread eine void-Methode als Parameter erwartet.

Um einen Rückgabewert von einem Thread verwenden Sie entweder ein Ereignis oder das folgende:

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

Task "run and forget" Erweiterung

In bestimmten Fällen (z. B. Protokollierung) kann es nützlich sein, die Task auszuführen und nicht auf das Ergebnis zu warten. Mit der folgenden Erweiterung können Sie task ausführen und die Ausführung des Restcodes fortsetzen:

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

Das Ergebnis wird nur innerhalb der Erweiterungsmethode erwartet. Da async / await verwendet wird, ist es möglich, eine Ausnahme async und eine optionale Methode für die Behandlung aufzurufen.

Ein Beispiel zur Verwendung der Erweiterung:

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
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow