Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Asynchrone lambdas en anonieme methoden zijn krachtige functies waarmee u gemachtigden kunt maken die asynchrone bewerkingen vertegenwoordigen. Gebruik deze met API's die zijn ontworpen voor asynchrone gemachtigden. In dit artikel worden eerst de juiste patronen beschreven en wordt uitgelegd wat er fout gaat wanneer u asynchrone lambdas doorgeeft aan API's die synchrone gemachtigden verwachten.
Asynchrone lambdas toegewezen aan Action delegaten
Creëer een overload die Func<Task> accepteert en wacht op het resultaat.
public static class TimingHelperFixed
{
public static double Time(Action action, int iterations = 10)
{
var sw = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
action();
return sw.Elapsed.TotalSeconds / iterations;
}
public static async Task<double> TimeAsync(Func<Task> func, int iterations = 10)
{
var sw = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
await func();
return sw.Elapsed.TotalSeconds / iterations;
}
}
public static class ActionFixDemo
{
public static async Task Run()
{
// Now the async lambda maps to Func<Task>, and
// the timer awaits each iteration to complete.
double seconds = await TimingHelperFixed.TimeAsync(async () =>
{
await Task.Delay(100);
}, iterations: 3);
Console.WriteLine($"Async (fixed): {seconds:F4}s per iteration");
}
}
Public Module TimingHelperFixed
Public Function Time(action As Action, Optional iterations As Integer = 10) As Double
Dim sw = Stopwatch.StartNew()
For i As Integer = 0 To iterations - 1
action()
Next
Return sw.Elapsed.TotalSeconds / iterations
End Function
Public Async Function Time(func As Func(Of Task), Optional iterations As Integer = 10) As Task(Of Double)
Dim sw = Stopwatch.StartNew()
For i As Integer = 0 To iterations - 1
Await func()
Next
Return sw.Elapsed.TotalSeconds / iterations
End Function
End Module
Public Module ActionFixDemo
Public Async Function Run() As Task
' Now the async lambda maps to Func(Of Task), and
' the timer waits for each iteration to complete.
Dim seconds As Double = Await TimingHelperFixed.Time(
Async Function()
Await Task.Delay(100)
End Function, iterations:=3)
Console.WriteLine($"Async (fixed): {seconds:F4}s per iteration")
End Function
End Module
Wanneer u een asynchrone lambda doorgeeft aan een methode, controleert u het gemachtigde type van de parameter. Als de parameter Action, Action<T>, of een andere niet-waardeteruggevende gemachtigde is, schakelt u over naar een taak-teruggevende gemachtigde voor asynchrone bewerkingen.
Een asynchrone lambda kan overeenkomen met een delegate-type dat void retourneert, zoals Action naast Func<Task>. Wanneer de doelparameter een Action is, wijst de compiler de asynchrone lambda toe aan een asynchrone void-methode. De beller kan de voltooiing niet bijhouden.
Overweeg een tijdshelper die een Action:
public static class TimingHelper
{
public static double Time(Action action, int iterations = 10)
{
var sw = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
action();
return sw.Elapsed.TotalSeconds / iterations;
}
}
public static class ActionPitfallDemo
{
public static void Run()
{
// Synchronous lambda — timing is accurate.
double syncSeconds = TimingHelper.Time(() =>
{
Thread.Sleep(100);
}, iterations: 3);
Console.WriteLine($"Sync: {syncSeconds:F4}s per iteration");
// Async lambda — becomes async void, returns immediately.
double asyncSeconds = TimingHelper.Time(async () =>
{
await Task.Delay(100);
}, iterations: 3);
Console.WriteLine($"Async (buggy): {asyncSeconds:F4}s per iteration");
}
}
Public Module TimingHelper
Public Function Time(action As Action, Optional iterations As Integer = 10) As Double
Dim sw = Stopwatch.StartNew()
For i As Integer = 0 To iterations - 1
action()
Next
Return sw.Elapsed.TotalSeconds / iterations
End Function
End Module
Public Module ActionPitfallDemo
Public Sub Run()
' Synchronous lambda — timing is accurate.
Dim syncSeconds As Double = TimingHelper.Time(
Sub() Thread.Sleep(100), iterations:=3)
Console.WriteLine($"Sync: {syncSeconds:F4}s per iteration")
' Async lambda — becomes Async Sub, returns immediately.
Dim asyncSeconds As Double = TimingHelper.Time(
Async Sub() Await Task.Delay(100), iterations:=3)
Console.WriteLine($"Async (buggy): {asyncSeconds:F4}s per iteration")
End Sub
End Module
Wanneer u een synchrone lambda doorgeeft, is de gemeten tijd nauwkeurig. Met een asynchrone lambda retourneert de Action gedelegeerde zodra de eerste await uitgeeft, zodat de timer alleen het synchrone gedeelte vastlegt in plaats van de volledige bewerking.
Parallel.ForEach met asynchrone lambdas
Gebruik in .NET 6 en hoger ForEachAsync, dat een Func<TSource, CancellationToken, ValueTask> accepteert:
public static class ParallelForEachFixDemo
{
public static async Task RunAsync()
{
var sw = Stopwatch.StartNew();
await Parallel.ForEachAsync(
Enumerable.Range(0, 10),
new ParallelOptions { MaxDegreeOfParallelism = 4 },
async (i, ct) =>
{
await Task.Delay(200, ct);
});
Console.WriteLine($"Parallel.ForEachAsync (fixed): {sw.Elapsed.TotalSeconds:F2}s");
}
}
Public Module ParallelForEachFixDemo
Private Function ProcessItemAsync(i As Integer, ct As CancellationToken) As ValueTask
Return New ValueTask(Task.Delay(200, ct))
End Function
Public Async Function RunAsync() As Task
Dim sw = Stopwatch.StartNew()
Await Parallel.ForEachAsync(
Enumerable.Range(0, 10),
New ParallelOptions With {.MaxDegreeOfParallelism = 4},
AddressOf ProcessItemAsync)
Console.WriteLine($"Parallel.ForEachAsync (fixed): {sw.Elapsed.TotalSeconds:F2}s")
End Function
End Module
U kunt de items ook als taken projecteren en het volgende gebruiken WhenAll:
public static class WhenAllAlternativeDemo
{
public static async Task RunAsync()
{
var sw = Stopwatch.StartNew();
var tasks = Enumerable.Range(0, 10)
.Select(async i =>
{
await Task.Delay(200);
});
await Task.WhenAll(tasks);
Console.WriteLine($"Task.WhenAll: {sw.Elapsed.TotalSeconds:F2}s");
}
}
Public Module WhenAllAlternativeDemo
Public Async Function RunAsync() As Task
Dim sw = Stopwatch.StartNew()
Dim tasks = Enumerable.Range(0, 10).
Select(Async Function(i)
Await Task.Delay(200)
End Function)
Await Task.WhenAll(tasks)
Console.WriteLine($"Task.WhenAll: {sw.Elapsed.TotalSeconds:F2}s")
End Function
End Module
ForEach accepteert een Action<T> voor de hoofdtekstparameter. Als u een asynchrone lambda doorgeeft, creëert dit een async void-delegaat.
Parallel.ForEach retourneert zodra elke gedelegeerde het eerste rendement awaitbereikt:
public static class ParallelForEachBugDemo
{
public static void Run()
{
var sw = Stopwatch.StartNew();
Parallel.ForEach(Enumerable.Range(0, 10), async i =>
{
await Task.Delay(200);
});
// Completes almost immediately — the async lambdas are fire-and-forget.
Console.WriteLine($"Parallel.ForEach (buggy): {sw.Elapsed.TotalSeconds:F2}s");
}
}
Public Module ParallelForEachBugDemo
Public Sub Run()
Dim sw = Stopwatch.StartNew()
Parallel.ForEach(Enumerable.Range(0, 10),
Async Sub(i As Integer)
Await Task.Delay(200)
End Sub)
' Completes almost immediately — the async lambdas are fire-and-forget.
Console.WriteLine($"Parallel.ForEach (buggy): {sw.Elapsed.TotalSeconds:F2}s")
End Sub
End Module
De lus voltooit in milliseconden in plaats van de verwachte duur, omdat de asynchrone lambdas fire-and-forget-operaties worden.
Task.Factory.StartNew met asynchrone lambdas
Run verwijdert automatisch asynchrone lambdas. Het accepteert overbelastingen Func<Task> en Func<Task<TResult>> en retourneert de interne taak.
public static class StartNewFix1Demo
{
public static async Task RunAsync()
{
var sw = Stopwatch.StartNew();
await Task.Run(async () =>
{
await Task.Delay(1000);
});
Console.WriteLine($"Task.Run (fixed): {sw.Elapsed.TotalSeconds:F2}s");
}
}
Public Module StartNewFix1Demo
Public Async Function RunAsync() As Task
Dim sw = Stopwatch.StartNew()
Await Task.Run(Async Function()
Await Task.Delay(1000)
End Function)
Console.WriteLine($"Task.Run (fixed): {sw.Elapsed.TotalSeconds:F2}s")
End Function
End Module
Als u StartNew-specifieke opties nodig hebt (zoals LongRunning), roept u Unwrap aan op het resultaat:
public static class StartNewFix2Demo
{
public static async Task RunAsync()
{
var sw = Stopwatch.StartNew();
await Task.Factory.StartNew(async () =>
{
await Task.Delay(1000);
}).Unwrap();
Console.WriteLine($"StartNew + Unwrap (fixed): {sw.Elapsed.TotalSeconds:F2}s");
}
}
Public Module StartNewFix2Demo
Public Async Function RunAsync() As Task
Dim sw = Stopwatch.StartNew()
Await Task.Factory.StartNew(Async Function()
Await Task.Delay(1000)
End Function).Unwrap()
Console.WriteLine($"StartNew + Unwrap (fixed): {sw.Elapsed.TotalSeconds:F2}s")
End Function
End Module
Wanneer u een asynchrone lambda StartNewdoorgeeft, is Task<Task> het retourtype (of Task<Task<TResult>>). De buitenste taak vertegenwoordigt alleen het synchrone deel van de gedelegeerde. Deze wordt voltooid bij het eerste rendement await. De binnenste taak vertegenwoordigt de volledige asynchrone bewerking:
public static class StartNewBugDemo
{
public static async Task RunAsync()
{
var sw = Stopwatch.StartNew();
// t is Task<Task> — the outer task completes at the first yielding await.
Task<Task> t = Task.Factory.StartNew(async () =>
{
await Task.Delay(1000);
});
await t; // Awaits only the outer task.
Console.WriteLine($"StartNew (buggy): {sw.Elapsed.TotalSeconds:F2}s");
}
}
Public Module StartNewBugDemo
Public Async Function RunAsync() As Task
Dim sw = Stopwatch.StartNew()
' t is Task(Of Task) — the outer task completes at the first yielding Await.
Dim t As Task(Of Task) = Task.Factory.StartNew(Async Function()
Await Task.Delay(1000)
End Function)
Await t ' Awaits only the outer task.
Console.WriteLine($"StartNew (buggy): {sw.Elapsed.TotalSeconds:F2}s")
End Function
End Module
Als u de buitenste taak behandelt als de hele bewerking, ziet u de voltooiing voordat het asynchrone werk daadwerkelijk is voltooid.
Overzicht
Wanneer u een asynchrone lambda doorgeeft aan een methode, controleert u het gedelegeerde type van de doelparameter:
| Type gemachtigde | Asynchroon gedrag | Risico |
|---|---|---|
Func<Task>, Func<Task<T>> |
De beller ontvangt een taak die voltooiing vertegenwoordigt | Veilig |
Action, Action<T> |
Wordt async void—aanroeper kan de voltooiing niet volgen. | Hoog |
Func<TResult> waar TResult is Task |
Retourneert Task<Task>: buitenste taak vertegenwoordigt geen volledig werk |
Gemiddeld |