C# Language
Async / await, Backgroundworker, Task- und Thread-Beispiele
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:
- Definieren Sie eine Arbeitermethode, die die zeitintensive Arbeit erledigt, und rufen Sie sie von einem Ereignishandler für das
DoWork
Ereignis einesBackgroundWorker
. - Starten Sie die Ausführung mit
RunWorkerAsync
. Jedes Argument , die der Arbeitnehmer Verfahren erforderlich anDoWork
kann über die übergeben werdenDoWorkEventArgs
ParameterRunWorkerAsync
.
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 dieDoWork
Handler abgeschlossen sind. - Das
ProgressChanged
Ereignis wird ausgelöst, wenn dieReportProgress
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.
});