Table of Contents

Getting Started

To use this library, simply include Icicle.dll in your project or grab it from NuGet, and add this to the top of each .cs file that needs it:

using Icicle;

Now a TaskScope can be created.

A fully parallel scope looks like this,


// within a using block, create a scope and configure it
using TaskScope scope = new TaskScope.WhenAll(); // run all tasks at the same time
// now add tasks to the scope to run
ResultHandle t1 = scope.Add(async ct => await Task.Delay(TimeSpan.FromSeconds(1), ct));
ResultHandle t2 = scope.Add(async ct => await Task.Delay(TimeSpan.FromSeconds(1), ct));
ResultHandle t3 = scope.Add(async ct => await Task.Delay(TimeSpan.FromSeconds(1), ct));
// now run them all; should run for around a second
RunToken token = await scope.Run();

Values can also be returned from the added tasks,


using TaskScope scope = new TaskScope.WhenAll();
// added tasks
ResultHandle<string> t1 = scope.Add(async ct =>
{
    await Task.Delay(TimeSpan.FromSeconds(1), ct);
    return "Hello";
});
ResultHandle<string> t2 = scope.Add(async ct =>
{
    await Task.Delay(TimeSpan.FromSeconds(1), ct);
    return "World";
});
// now run them all; should run for around a second
var token = await scope.Run();
// set the value to "Hello World"
$"{t1.Value(token)} {t2.Value(token)}".Should().Be("Hello World");

The usage rules for TaskScope are as follows,

  1. Add can be called as many times as required up until Run has completed (nested calls to Add are supported)

    
    using TaskScope scope = new TaskScope.WhenAll();
    // keep adding
    ResultHandle t1 = scope.Add(async ct => await Task.Delay(TimeSpan.FromSeconds(1), ct));
    ResultHandle t2 = scope.Add(async ct => await Task.Delay(TimeSpan.FromSeconds(1), ct));
    ResultHandle t3 = scope.Add(async ct =>
    {
        await Task.Delay(TimeSpan.FromSeconds(1), ct);
        // and nest as well
        _ = scope.Add(async ct => await Task.Delay(TimeSpan.FromSeconds(1), ct));
    });
    // run them
    RunToken token = await scope.Run();
    // cannot call Add or Run from this point on
    Assert.Throws<TaskScopeCompletedException>(
        () => scope.Add(async ct => await Task.Delay(TimeSpan.FromSeconds(1), ct))
    );
    
    
  2. Run can only be called once, and must be called

This enforces the following semantics,

  • All suspended tasks are only started on Run and conform to the particular execution semantics of the TaskScope instance and its configuration. For example, a windowSize if provided to TaskScope.WhenAll will run at most windowSize tasks at a time

  • Any Fault will trigger cancellation

  • Cancellation applies to tasks that are started, all other tasks that have not started never start

  • Values can be accessed from ResultHandle<T> and ResultHandle after Run

    
    using TaskScope scope = new TaskScope.WhenAll();
    ResultHandle<string> t1 = scope.Add(async ct =>
    {
        await Task.Delay(TimeSpan.FromSeconds(1), ct);
        return "Hello";
    });
    RunToken token = await scope.Run(); // get token here
    string result = t1.Value(
        // pass it here
        token
    );
    result.Should().Be("Hello");