RJCP.Threading 0.3.0

dotnet add package RJCP.Threading --version 0.3.0
                    
NuGet\Install-Package RJCP.Threading -Version 0.3.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="RJCP.Threading" Version="0.3.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="RJCP.Threading" Version="0.3.0" />
                    
Directory.Packages.props
<PackageReference Include="RJCP.Threading" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add RJCP.Threading --version 0.3.0
                    
#r "nuget: RJCP.Threading, 0.3.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package RJCP.Threading@0.3.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=RJCP.Threading&version=0.3.0
                    
Install as a Cake Addin
#tool nuget:?package=RJCP.Threading&version=0.3.0
                    
Install as a Cake Tool

RJCP Thread Library

The RJCP.Threading library was introduced due to a problem with .NET not having an implementation of ITask<T> which allows for covariant interfaces.

It is based on the information by Extending the Async Methods in C#.

1. Features

1.1. Task Interface

1.1.1. Why is this needed

.NET 4.x introduced the Task<T> type, which represents operations that can run on different contexts that return values at some later point in time. In .NET 4.5, they keyword async and await were introduced that provide an easy to read asynchronous programming model with the language realizing this in the background using state machines.

While this model brought significant improvements to the language, it was no longer possible to define interfaces using covariant types. For example, the language does not allow:

namespace RJCP.Threading.Tasks.Covariance
{
    public interface ILineReader<out T> where T : ILine
    {
        Task<T> GetLineAsync();
    }
}

The code above will not compile, unless the out keyword is removed. This prevents code such as the following from being written:

namespace RJCP.Threading.Tasks
{
    using System;
    using System.Threading.Tasks;

    public interface ILine {
        string Text { get; }
    }

    public interface ILineReader<T> where T : ILine {
        Task<T> GetLineAsync();
    }

    public class Line : ILine {
        public string Text { get { return "Text"; } }
    }

    public class LineReader : ILineReader<Line> {
        public async Task<Line> GetLineAsync() {
            await Task.Delay(1);
            return new Line();
        }
    }

    public static class LineModule {
        public static async Task PrintLine() {
            // This line has the error:
            //  CS0266: Cannot implicitly convert type 'RJCP.Threading.Tasks.LineReader'
            //  to 'RJCP.Threading.Tasks.ILineReader<RJCP.Threading.Tasks.ILine>'. An
            //  explicit conversion exists (are you missing a cast?)
            ILineReader<ILine> reader = new LineReader();
            ILine line = await reader.GetLineAsync();

            Console.WriteLine("{0}", line.Text);
        }
    }
}

The error is raised because the interface ILineReader doesn't have a covariant type T. It must be ILineReader<Line> for the code to compile. But then if you have a new class LineReader2 that has T : Line2 : ILine, it can't be assigned to reader as the types are again incompatible.

1.1.2. Introducing an Interface to allow Covariance

However, with the existence of an interface ITask, we can now make the type T covariant, and the method works as expected.

namespace RJCP.Threading.Tasks {
    using System;
    using System.Threading.Tasks;

    public interface ILine {
        string Text { get; }
    }

    public interface ILineReader<out T> where T : ILine {
        ITask<T> GetLineAsync();
    }

    public class Line : ILine {
        public string Text { get { return "Text"; } }
    }

    public class LineReader : ILineReader<Line> {
        public async ITask<Line> GetLineAsync() {
            await Task.Delay(1);
            return new Line();
        }
    }

    public static class LineModule {
        public static async ITask PrintLine() {
            ILineReader<ILine> reader = new LineReader();
            ILine line = await reader.GetLineAsync();

            Console.WriteLine("{0}", line.Text);
        }
    }
}

Where possible, avoid the usage of the ITask as it is slower.

1.2. Task Group

A TaskGroup is a simple collection to reduce boiler-plate code when waiting on multiple tasks.

Create a TaskGroup and RegisterTask(Task) to have the task group be able to wait on the tasks.

1.3. Processes

The core of the functionality is in the RunProcess task. The easiest way is to create a RunProcess class through one of the static methods:

  • Run(string command, params string[] arguments)
  • RunFrom(string command, string workDir, params string[] arguments)
  • RunAsync(string command, params string[] arguments)
  • RunAsync(string command, string[] arguments, CancellationToken token)
  • RunFromAsync(string command, string workDir, params string[] arguments)
  • RunFromAsync(string command, string workDir, string[] arguments, CancellationToken token)

The static methods return a RunProcess or a Task<RunProcess> that can be awaited on. The process runs until completion, and then the result is returned.

You can instantiate the class with one of the constructors, and then call Execute or await with ExecuteAsync, which blocks until the process exits. You can register to delegates before executing the process to get feedback.

1.3.1. Unit Testing

Through object inheritance, it is possible to construct a RunProcess object, and pass it to a method that executes the process, by simulation only.

It works by providing your own class and providing a simulation delegate.

internal class GetDirSimProcess : RunProcess
{
    private static int GetDirSim(RunProcess process, string command, string arguments, CancellationToken token)
    {
        GetDirSimProcess p = (GetDirSimProcess)process;

        p.LogStdOut(" Volume in drive C has no label.");
        p.LogStdOut(" Volume Serial Number is 3A2G-7Z2W");
        p.LogStdOut(string.Empty);
        p.LogStdOut($" Directory of {p.WorkingDirectory}");
        p.LogStdOut(string.Empty);
        p.LogStdOut("06/08/2021  18:31    <DIR>          .");
        p.LogStdOut("06/08/2021  18:31    <DIR>          ..");
        p.LogStdOut("25/05/2021  17:59            29,048 testhost.dll");
        p.LogStdOut("25/05/2021  18:00           149,360 testhost.exe");
        p.LogStdOut("              49 File(s)      6,918,578 bytes");
        p.LogStdOut("              17 Dir(s)  830,622,584,832 bytes free");
        return 0;
    }

    public GetDirSimProcess(string command, string workDir, string arguments)
        : base(GetDirSim, command, workDir, arguments) { }
}
1.3.2. Executable

A wrapper class Executable is written to abstract away RunProcess, that one can build classes based on tool behaviour. Using RunProcess simulation for testing can be used to test multiple different versions of tools in your test suite, without needing the actual tools (only the inputs and outputs copied into the test code).

1.3.3. Conclusion

It is then possible to create tools that can automatically detect their location, and either execute function (like a GIT tool, that embeds in the class how to find the git binary, and then the command lines needed to execute).

2. Release History

2.1. Version 0.3.0

Features:

  • Process: A wrapper RunProcess that asynchronously handles processes and their output (DOTNET-1086)
  • Executable: Create tools around RunProcess with the Executable class (DOTNET-1088)

2.2. Version 0.2.1

Bugfixes:

  • TaskGroup: Remove asynchronous behaviour of TaskCompletionSource (fails on Linux) (DOTNET-970)

Quality:

  • Add README.md reference to NuGet package (DOTNET-815)
  • Tasks: Fix unnecessary guard around HashSet (DOTNET-833)
  • Upgrade from .NET Standard 2.1 to .NET 6.0 (DOTNET-936, DOTNET-941, DOTNET-942)

2.3. Version 0.2.0

  • Initial Version
Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  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 is compatible.  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.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Framework net462 is compatible.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.3.0 126 3/26/2026
0.2.1 299 3/9/2024
0.2.0 287 6/9/2023