C# Language
Exempel på asynk / vänta, bakgrundsarbetare, uppgift och tråd
Sök…
Anmärkningar
För att köra något av dessa exempel bara ringa dem så:
static void Main()
{
new Program().ProcessDataAsync();
Console.ReadLine();
}
ASP.NET Konfigurera vänta
När ASP.NET hanterar en begäran tilldelas en tråd från trådpoolen och en förfrågningskontekst skapas. Begäran-sammanhanget innehåller information om den aktuella begäran som kan nås via den statiska egenskapen HttpContext.Current
. Förfrågningskonteksten för begäran tilldelas sedan tråden som hanterar begäran.
En given förfrågningskontekst kan bara vara aktiv på en tråd åt gången .
När exekveringen når await
, returneras tråden som hanterar en begäran till trådpöljen medan den asynkrona metoden körs och förfrågningskontextet är fritt för en annan tråd att använda.
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);
}
När uppgiften är klar tilldelar trådpolen en annan tråd för att fortsätta körningen av begäran. Förfrågningskonteksten tilldelas sedan denna tråd. Detta kan vara eller inte vara den ursprungliga tråden.
Blockering
När resultatet av en async
samtal väntar på synkront kan dödlås uppstå. Till exempel kommer följande kod att resultera i en IndexSync()
när IndexSync()
anropas:
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;
}
Detta beror på att den väntade uppgiften som standard i detta fall db.Products.ToListAsync()
kommer att fånga sammanhanget (i fallet med ASP.NET förfrågningskonteksten) och försöka använda det när det har slutförts.
När hela samtalstacken är asynkron finns det inget problem eftersom den ursprungliga tråden, när en await
har nåtts, frigörs och frigör förfrågningskonteksten.
När vi blockerar synkront med Task.Result
eller Task.Wait()
(eller andra blockeringsmetoder) är den ursprungliga tråden fortfarande aktiv och behåller förfrågningskontext. Den väntade metoden fungerar fortfarande asynkront och när återuppringningen försöker att köras, dvs när den väntade uppgiften har återvänt försöker den få framförfrågningskonteksten.
Därför uppstår dödlåset eftersom medan den blockerande tråden med förfrågningskonteksten väntar på att den asynkrona operationen ska slutföras, försöker den asynkrona operationen att erhålla begärningskonteksten för att slutföra.
ConfigureAwait
Som standard kommer samtal till en väntad uppgift att fånga det aktuella sammanhanget och försöka återuppta exekvering i sammanhanget när det är klart.
Genom att använda ConfigureAwait(false)
kan detta förhindras och dödlås kan undvikas.
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;
}
Detta kan undvika dödlås när det är nödvändigt att blockera asynkron kod, men det kostar att förlora sammanhanget i fortsättningen (kod efter samtalet att vänta).
I ASP.NET betyder det att om din kod följer ett samtal för att await someTask.ConfigureAwait(false);
försöker komma åt information från sammanhanget, till exempel HttpContext.Current.User
sedan informationen har gått förlorad. I detta fall är HttpContext.Current
noll. Till exempel:
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();
}
Om ConfigureAwait(true)
används (motsvarar att det inte finns någon ConfigureAwait alls), så user2
både user
och user2
med samma data.
Av detta skäl rekommenderas det ofta att använda ConfigureAwait(false)
i bibliotekskod där sammanhanget inte längre används.
Async / vänte
Se nedan för ett enkelt exempel på hur du använder async / väntar på att göra lite tidskrävande saker i en bakgrundsprocess samtidigt som du bibehåller möjligheten att göra några andra saker som inte behöver vänta på den tidskrävande grejer för att slutföra.
Men om du behöver arbeta med resultatet av den tidsintensiva metoden senare, kan du göra detta genom att vänta på genomförandet.
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);
}
Background
Se nedan för ett enkelt exempel på hur du använder ett BackgroundWorker
objekt för att utföra tidskrävande operationer i en bakgrundstråd.
Du behöver:
- Definiera en arbetarmetod som gör det tidskrävande arbetet och ring det från en händelseshanterare för
DoWork
händelsen i enBackgroundWorker
. - Börja exekveringen med
RunWorkerAsync
. Alla argument som krävs avDoWork
kopplad tillDoWork
kan skickas in viaDoWorkEventArgs
parametern tillRunWorkerAsync
.
Förutom DoWork
händelsen definierar klassen BackgroundWorker
också två händelser som ska användas för att interagera med användargränssnittet. Dessa är valfria.
-
RunWorkerCompleted
händelsen utlöses närDoWork
hanterarna har slutfört. -
ProgressChanged
händelsen utlöses närReportProgress
metoden anropas.
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);
}
Uppgift
Se nedan för ett enkelt exempel på hur man använder en Task
att göra lite tidskrävande saker i en bakgrundsprocess.
Allt du behöver göra är att slå in din tidskrävande metod i ett 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);
}
Tråd
Se nedan för ett enkelt exempel på hur du använder en Thread
att göra lite tidskrävande saker i en bakgrundsprocess.
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.");
}
Som ni kan se kan vi inte returnera ett värde från vår TimeIntensiveMethod
eftersom Thread
förväntar sig ett ogiltigt Metod som dess parameter.
För att få ett returvärde från en Thread
använder du antingen en händelse eller följande:
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);
Uppgift "kör och glöm" förlängning
I vissa fall (t.ex. loggning) kan det vara bra att köra en uppgift och inte vänta på resultatet. Följande tillägg gör det möjligt att köra uppgiften och fortsätta exekveringen av vilkoden:
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);
}
}
}
Resultatet väntas bara inom förlängningsmetoden. Eftersom async
/ await
används är det möjligt att få ett undantag och kalla en valfri metod för att hantera den.
Ett exempel på hur du använder tillägget:
var task = Task.FromResult(0); // Or any other task from e.g. external lib.
task.RunAndForget(
e =>
{
// Something went wrong, handle it.
});