C# Language
Async / await, Backgroundworker, Task and Thread Voorbeelden
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:
- Definieer een werkmethode die het tijdrovende werk doet en noem deze vanuit een gebeurtenishandler voor de
DoWork
gebeurtenis van eenBackgroundWorker
. - Start de uitvoering met
RunWorkerAsync
. Elk argument vereist door de werknemer methode bevestigd aanDoWork
kan worden doorgegeven via deDoWorkEventArgs
parameterRunWorkerAsync
.
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 deDoWork
handlers zijn voltooid. - De gebeurtenis
ProgressChanged
wordt geactiveerd wanneer de methodeReportProgress
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.
});