ForEachAsyncAnalyzer 1.0.4
dotnet add package ForEachAsyncAnalyzer --version 1.0.4
NuGet\Install-Package ForEachAsyncAnalyzer -Version 1.0.4
<PackageReference Include="ForEachAsyncAnalyzer" Version="1.0.4" />
paket add ForEachAsyncAnalyzer --version 1.0.4
#r "nuget: ForEachAsyncAnalyzer, 1.0.4"
// Install ForEachAsyncAnalyzer as a Cake Addin #addin nuget:?package=ForEachAsyncAnalyzer&version=1.0.4 // Install ForEachAsyncAnalyzer as a Cake Tool #tool nuget:?package=ForEachAsyncAnalyzer&version=1.0.4
This package contains a Roslyn C# code analyzer and fixer for the following pattern where a ForEach statement is combined with an async method. The ForEach method can be a custom extension method like in the code below. ForEach is also a given method in the great MoreLinq nuget package. The MoreLinq package also has methods like Cartesian, Partition and Permutations which are really nice!
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ForEachAsyncAnalyzerTestCode
{
public static class IEnumerableExtensions
{
public static async Task ForEachAsync<T>(this IEnumerable<T> source, Func<T, Task> action)
{
if(source == null)
{
throw new ArgumentNullException(nameof(source));
}
if(action == null)
{
throw new ArgumentNullException(nameof(action));
}
foreach(var element in source)
{
await action(element).ConfigureAwait(false);
}
}
public static void ForEach<T>(this IEnumerable<T> source, Action<T> action)
{
if(source == null)
{
throw new ArgumentNullException(nameof(source));
}
if(action == null)
{
throw new ArgumentNullException(nameof(action));
}
foreach(var element in source)
{
action(element);
}
}
class Program
{
private static readonly int[] _numbers = { 1, 2, 3 };
static async Task Main(string[] args)
{
// Flavour one. This is already detected as compiler warning in CS4014.
// See https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/cs4014.
// After fixing CS4014, you can end up in flavour two (more or less).
// _numbers
// .ForEach(i => DoAsyncAction(i));
// Flavour two.
_numbers
.ForEach(async i => await DoAsyncAction(i).ConfigureAwait(false));
// Just wait for all the tasks to finish their async tasks.
await Task.Delay(2400).ConfigureAwait(false);
}
private static async Task DoAsyncAction(int i)
{
// The first number waits 700 ms, the second number 400 ms and number 3 waits 100 ms.
var waitingTime = 1000 - i * 300;
await Task.Delay(waitingTime).ConfigureAwait(false);
Console.WriteLine($"{i}, {waitingTime}");
// Throwing an exception at this point can give you a 'weird' call stack when this method is not awaited.
// throw new Exception("Just a crash.");
}
}
}
}
The code above just prints the numbers 1, 2 and 3 to the console. It depends on the case what you want: A) exactly this order B) any given order. When you want case A you will have to await the async task. I encountered some nasty bugs when this await wasn't done and for example an exception was thrown within the async method. Normally I always want to await the async task so that is why I build this analyzer.
It does detect the case above and advises you to fix it. So this line:
_numbers
.ForEach(async i => await DoAsyncAction(i).ConfigureAwait(false));
becomes this line
await _numbers
.ForEachAsync(i => DoAsyncAction(i)).ConfigureAwait(false);
Learn more about Target Frameworks and .NET Standard.
This package has 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.
Added test to improve code quality.