Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
Az aszinkron lambdas és a névtelen metódusok olyan hatékony funkciók, amelyek lehetővé teszik az aszinkron műveleteket képviselő meghatalmazottak létrehozását. Használja őket aszinkron meghatalmazottak számára tervezett API-kkal. Ez a cikk először a megfelelő mintákat mutatja be, majd elmagyarázza, mi a baj, ha aszinkron lambdákat ad át szinkron meghatalmazottakra váró API-knak.
Aszinkron lambdák, amelyeket Action delegáltakhoz rendeltek
Hozzon létre egy túlterhelést, amely elfogadja Func<Task> és várja az eredményt:
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
Amikor aszinkron lambdát ad át egy metódusnak, ellenőrizze a paraméter delegálási típusát. Ha a paraméter Action, Action<T>, vagy bármely más void típusú delegált, az aszinkron műveletekhez váltson Task típust visszaadó delegáltra.
Az aszinkron lambda ugyanúgy megfelelhet egy void-t visszaadó delegált típusnak, mint a Action és a Func<Task>. Ha a célparaméter egy Action, a fordító leképezi az aszinkron lambdát egy aszinkron üres metódusra. A hívónak nincs módja nyomon követni a befejezést.
Fontolja meg egy időzítési segédet, amely elfogadja a következőt 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
Ha szinkron lambdát ad át, a mért idő pontos. Az aszinkron lambdával a Action delegált az első await kiváltása után azonnal visszatér, így az időzítő a teljes művelet helyett csak a szinkron részt lekti.
Parallel.ForEach aszinkron lambda-kifejezések
A .NET 6-os és újabb verzióiban használja a ForEachAsync, amely elfogadja a Func<TSource, CancellationToken, ValueTask>:
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
Másik lehetőségként projektelje az elemeket tevékenységekbe, és használja a következőt 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 elfogad egy Action<T> törzsparamétert. Az aszinkron lambda átadása létrehoz egy aszinkron üres delegáltat.
Parallel.ForEach amint az egyes meghatalmazottak elérik az első hozamot await:
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
A hurok a várt időtartam helyett ezredmásodperc alatt fejeződik be, mivel az aszinkron lambdák "fire-and-forget" műveletekké válnak.
Task.Factory.StartNew aszinkron lambdas
Run automatikusan feloldja az aszinkron lambdákat. Elfogadja a Func<Task> és Func<Task<TResult>> túlterheléseket, és visszaadja a belső feladatot:
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
Ha szüksége van StartNew-specifikus beállításokra (például LongRunning), hívja meg a Unwrap-et az eredményen.
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
Ha egy aszinkron lambdát StartNew-nak ad át, a visszatérési típus Task<Task> (vagy Task<Task<TResult>>). A külső tevékenység csak a meghatalmazott szinkron részét jelöli – az első hozamnál fejeződik be await. A belső feladat a teljes aszinkron műveletet jelöli:
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
Ha a külső feladatot a teljes műveletként kezeli, megfigyelheti a befejezést, mielőtt az aszinkron munka ténylegesen befejeződik.
Összefoglalás
Ha aszinkron lambdát ad át bármely metódusnak, ellenőrizze a célparaméter delegálási típusát:
| Delegálás típusa | Aszinkron viselkedés | Kockázat |
|---|---|---|
Func<Task>, Func<Task<T>> |
A hívó a befejezést jelképező feladatot kap | Biztonságos |
Action, Action<T> |
Aszinkron üressé válik – a hívó nem tudja megfigyelni a befejezést | Magas |
Func<TResult> ahol TResult van Task |
Visszaadja Task<Task>– a külső feladat nem a teljes munkát képviseli |
Medium |