AwaitMultiple 6.0.0
dotnet add package AwaitMultiple --version 6.0.0
NuGet\Install-Package AwaitMultiple -Version 6.0.0
<PackageReference Include="AwaitMultiple" Version="6.0.0" />
paket add AwaitMultiple --version 6.0.0
#r "nuget: AwaitMultiple, 6.0.0"
// Install AwaitMultiple as a Cake Addin #addin nuget:?package=AwaitMultiple&version=6.0.0 // Install AwaitMultiple as a Cake Tool #tool nuget:?package=AwaitMultiple&version=6.0.0
AwaitMultiple
Await multiple tasks in parallel and get their return values with concise code.
How to use and why
AwaitMultiple can be used like this up to 16 arguments:
var (value1, value2, value3) = await Tasks(task1, task2, task3);
Add:
global using static AwaitMultiple.__Await;
Write
var (books, employees) = await Tasks(
dbConnection.GetAllAsync<Books>(),
dbConnection.GetAllAsync<Employees>());
instead of
var books = await dbConnection.GetAllAsync<Book>();
var employees = await dbConnection.GetAllAsync<Employee>();
because the latter code is not executing the employees-related task until the books-related task has finished.
Optional feature
Await tasks with and without return value in a single call:
var (books, employees) = await Tasks(
dbConnection.GetAllAsync<Books>(),
dbConnection.GetAllAsync<Employees>(),
[
dbConnection.InsertHistoryRecordAsync(),
// ... any number of tasks...
]);
or
var books = await Tasks(
dbConnection.GetAllAsync<Books>(),
[
dbConnection.InsertHistoryRecordAsync(),
// ... any number of tasks...
]);
For code consistency, you can also use:
await Tasks([
dbConnection.InsertHistoryRecordAsync(),
// ... any number of tasks...
]);
Exception handling options
By default, only the first occurring exception is thrown (and the others are caught but not re-thrown).
This is consistent with Task.WhenAll
and more parts of the C# language.
Continue if one task fails
You may want to continue if "getting value b
" fails. In that case, you could use the NuGet package TaskExceptionCatcher like this:
var (a, catchResultB, c) = await Tasks(
StartTaskAAsync(),
Catcher.Run(() => StartTaskBAsync()),
StartTaskCAsync());
if (catchResultB.Exception is { } exception)
{
// no problem!
}
else
{
var b = catchResultB.Value;
// use `b`.
}
// use `a` and `c`.
Getting all the exceptions
If you're interested in not only the first, but all the exceptions:
- set the
exceptionOption
toExceptionOption.Aggregate
- use
Task.Run
on all arguments, all tasks.
var (a, b) = await Tasks(
Task.Run(() => StartTaskAAsync()),
Task.Run(() => StartTaskBAsync()),
exceptionOption: ExceptionOption.Aggregate);
Then all errors are returned in a single AggregateException
. Its Message
property is like:
One or more errors occurred. (First exception message.) (Second exception message.)
Use the property aggregateException.InnerExceptions
for more details like StackTrace
s etc.
Why is Task.Run
needed? Because async
functions don't return a task until the first await
keyword.
If an exception occurs before that, then that function throws even before it passes an argument into Tasks
.
Configuring awaits
This section is for nerds only. Using ConfigureAwait
is NOT necessary.
AwaitMultiple uses .ConfigureAwait(false)
internally. Programmers using AwaitMultiple in their own library can use .ConfigureAwait(false)
:
var (books, employees) = await Tasks(
dbConnection.GetAllAsync<Books>(),
dbConnection.GetAllAsync<Employees>())
.ConfigureAwait(false);
More details:
- The above is slightly better for performance. Normally, after having
await
ed an async operation, the "context" is set to the same as before theawait
. That costs time. In most libraries there is no need to capture and return to a specific context, so in those library one might want to setcontinueOnCapturedContext
tofalse
. - Library writers using the above also avoid some deadlock problems for consumers that use their library wrongly.
- In app-level code you will probably not see
.ConfigureAwait(false)
being used. The performance gain is very small. - One must not use
.ConfigureAwait(false)
in a method body that interacts with UI. It is fine to never use it at all. - Writing
await task.ConfigureAwait(true);
is functionally identical toawait task;
, so you never need to use.ConfigureAwait(true)
.
Conclusion for consumers of this library:
You don't need to use ConfigureAwait
at all unless you're writing your own library code and want extra performance.
Get it
Available via NuGet.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
.NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
.NET Standard | netstandard2.0 is compatible. netstandard2.1 was computed. |
.NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
MonoAndroid | monoandroid was computed. |
MonoMac | monomac was computed. |
MonoTouch | monotouch was computed. |
Tizen | tizen40 was computed. tizen60 was computed. |
Xamarin.iOS | xamarinios was computed. |
Xamarin.Mac | xamarinmac was computed. |
Xamarin.TVOS | xamarintvos was computed. |
Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
v1.0.1: improve exception handling.
v1.0.2: improve handling cancellation exceptions and use `.ConfigureAwait(false)`.
v1.0.3: use `.ConfigureAwait(false)` on all `await`s.
v2.0.0: rename `await tasks(..)` to `await Tasks(..)` to allow using a `tasks` variable and the `Tasks` method in the same scope.
v2.1.0: [new feature] await tasks with and without return value together.
v2.1.1: delete unused method.
v3.0.0: add optional parameter `continueOnCapturedContext` (with default `true`).
v4.0.0: change the default exception handling behaviour to "throwing only the first exception" (as is usual in the C# language) and introduce the optional `exceptionOption` parameter that can be set to `ExceptionOption.Aggregate' for always getting an `AggregateException` if any task throws.
v4.0.1: add `.ConfigureAwait(continueOnCapturedContext)` at missed spots + update readme.
v5.0.0: delete the `continueOnCapturedContext` parameter and always use `false` inside the library.
v6.0.0: improve instruction for `ExceptionOption.Aggregate` in readme and rename `global using static AwaitMultiple.__Await;` (using 2 underscores to prevent IntelliSense from advising to use the `__Await` class when typing `await` when having `ExceptionOption.Aggregate` via a using of its namespace at the top of the file).