commandlineparser / commandline

The best C# command line parser that brings standardized *nix getopt style, for .NET. Includes F# support
MIT License
4.46k stars 473 forks source link

Parallel.ForEachAsync not working in Debug mode within the WithParsed method #912

Closed Cloxy777 closed 4 months ago

Cloxy777 commented 4 months ago

Describe the bug Parallel.ForEachAsync not working in Debug mode within the WithParsed method in CommandLineParser 2.9.1

To Reproduce Code sample - https://dotnetfiddle.net/sRLRbW

using CommandLine;
using System.Threading.Tasks;
using System;

args = new[] {"--connectionString=Server=...;"};
Parser.Default.ParseArguments<Options>(args)
    .WithParsed(async (options) =>
    {
        await Parallel.ForEachAsync(args, async (arg, _) =>
        {
            await Console.Out.WriteLineAsync(arg);
            await Console.Out.WriteLineAsync();
        });
    });

public class Options
{
    [Option('c', "connectionString", Required = true, HelpText = "Specify the connection string.")]
    public string ConnectionString { get; set; } = string.Empty;
}

Expected behavior The same output is in the Debug/Release modes.

Actual behavior No output in Debug mode.

Screenshots Debug Release

Additional Info Using Visual Studio 17.8.3

elgonzo commented 4 months ago

Not a bug. Rather, an error in your code.

WithParsed is not meant to be used with async functions, as it doesn't await them. What you observe is basically the program ending before Parallel.ForEachAsync manages to schedule and start the async iterations. Whether some of the Parallel.ForEachAsync iterations manage to be executed quicker than the main thread (and therefore the entire program) ending is mere coincidence -- a classic example of a race condition.

To fix your code, replace WithParsed with WithParsedAsync and await it.

Note that it is legal for the C# compiler to convert your async anonymous function into an Action<Options>, which is then being passed to the WithParsed method. This will practically turn your async anonymous function into a void-returning async function. This means, the async function will not produce an awaitable Task object or similar and thus not being awaitable. Not only that, but -- because of this conversion being perfectly legal -- the C# compiler will also not issue a warning about this incorrect usage of the WithParsed method (unless some code analyzer used in your dev/build envirornment happens to recognize it).

A side note: Be aware that this library seems to be dead/stale, with no noteworthy development activities since summer of 2022. Therefore, it looks to be unlikely that any bugs in the library will be fixed even if you encounter an actual bug in the library. There are other Commandline/CLI libraries out there that are actively developed and maintained; an example would be Spectre.Console.CLI, which roughly follows a similar approach like this library here.

Cloxy777 commented 4 months ago

Great explanation, thanks