Skip to content

Releases: alanmcgovern/ReusableTasks

v1.0.4

17 Nov 11:38
Compare
Choose a tag to compare

General

  • Slightly tweaked the internal logic which protects against accidental mis-use of ReusableTask and ReusableTaskCompletionSource. If a mis-use scenario is encountered then an InvalidTaskReuseException will be thrown instead of InvalidOperationException. This makes it easier to disambiguate the cases later based on exception type.

v1.0.3

13 Nov 00:03
Compare
Choose a tag to compare

General

  • Fixed an issue where the 'ForceAsynchronousContinuations' bool would be lost when resetting the ResultHolder. This only affected the new AsyncProducerConsumerQueue implementation. There's no real problem with this either way.

v1.0.2

11 Nov 14:13
Compare
Choose a tag to compare

General

  • Removed an accidental double-lock on the same object in the same method.
  • Improved the performance of tasks which complete synchronously by ensuring they do not need to access shared state.
  • Internal structs are marked 'readonly' now, which helps ensure the compiler will not generate defensive copies.
  • Added the ability to force continuations attached to ReusableTaskCompletionSource.Task to execute asynchronously.
  • Added TrySetResult, TrySetException and TrySetCancelled to ReusableTaskCompletionSource.
  • Added an implementation of an amortised allocation-free asynchronous Producer/Consumer queue, which supports a single reader and a single writer. If a CancellationToken is used, then there will be some allocations due to the internal workings of the token.

v1.0.1

02 Nov 19:22
Compare
Choose a tag to compare

General

  • Removes a duplicate property from an internal type, making the objects a little smaller.

v1.0.0

01 Nov 18:29
Compare
Choose a tag to compare

General

  • The main change here is that everything which could be made a struct has been made a struct. This allows some basic sanity checking to be added to the library without affecting the ability to deliver zero allocation async/await methods.

v0.99.4

30 Oct 10:53
Compare
Choose a tag to compare
v0.99.4 Pre-release
Pre-release

General

The only change is to pass Deterministic=true to the compiler.

v0.99.3

30 Oct 10:44
Compare
Choose a tag to compare
v0.99.3 Pre-release
Pre-release

General

  • Hide the built in implementation of AsyncVoidMethodBuilder as it will not be used in the way I had originally intended. If people want an optimised async void experience they will have to copy/paste this class into their own project and compile it alongside the rest of their source code.

  • Expose StateMachineCache<T> publicly to allow people to declare an optimised AsyncVoidMethodBuilder in their project

v0.99.1

29 Oct 10:41
Compare
Choose a tag to compare
v0.99.1 Pre-release
Pre-release

General

  • Moved the AsTask extension methods to be proper methods on ReusableTask/ReusableTask<T>.

v0.99.0

29 Oct 02:20
Compare
Choose a tag to compare
v0.99.0 Pre-release
Pre-release

General

Slight optimisation when executing the continuation. Previously it was always executed asynchronously be marshalling to the ThreadPool or captured SynchronizationContext. Now it will not marshal to the threadpool a second time if it is already on the threadpool. Similarly, SynchronizationContext.Post won't be used to marshal the continuation if the code is executing on that SynchronizationContext already.

v0.0.1

28 Oct 18:11
Compare
Choose a tag to compare
v0.0.1 Pre-release
Pre-release

Introduction.

ReusableTasks provides three classes.

  • ReusableTask - a zero allocation Task-like object which can be used in place of System.Threading.Tasks.Task when declaring async methods.
  • ReusableTask<T> - a zero allocation Task-like object which can be used in place of System.Threading.Tasks.Task<T> when declaring async methods.
  • ReusableTaskCompletionSource<T> - a zero allocation TaskCompletionSource-like object, built on top of ReusableTask<T<>, which can be used in place of System.Threading.Tasks.TaskCompletionSource<T> in scenarios where you would normally repeatedly instantiate new copies of TaskCompletionSource<T>.

Migration examples:

Change your async method declarations as follows:

async Task Old ()
{
    await Task.Delay (100); // Do Stuff
}

async ReusableTask New ()
{
    await Task.Delay (100); // Do Stuff
}

async Task<int> Old ()
{
    await Task.Delay (100); // Do Stuff
    return 5;
}

async ReusableTask<int> New ()
{
    await Task.Delay (100); // Do Stuff
    return 5;
}

Limitations

The caching strategy employed by ReusableTask means you cannot await a task twice. This kind of code would either deadlock or cause corruption of the ReusableTask cache:

public async ReusableTask<int> CalculateAge ()
{
    await Task.Delay (100); // Do Stuff
    return 42;
}

async void CorruptCache ()
{
    var task = CalculateAge  ();
    Task.Run (async () => await task);
    Task.Run (async () => await task);
}

ReusableTaskCompletionSource<int> has the same limitation, the returned task should not have multiple concurrent awaits:

public async void CorruptTCS ()
{
    var tcs = new ReusableTaskCompletionSource<int> ();
    
    Task.Run (async () => await tcs.Task);
    Task.Run (async () => await tcs.Task);

    tcs.SetResult (5);
}

async void SafelyReuseTCS ()
{
    var tcs = new ReusableTaskCompletionSource<int> ();

    async void SetResultEverySecond ()
    {
        for (int i = 0; i < 100; i ++) {
            await Task.Delay (1000);
            tcs.SetResult (i);
        }
    }

    SetResultEverySecond ();
    while (true) {
         Console.WriteLine ("Current iteration is: {0}", await tcs.Task);
    }
}

NOTE: In real code you should ensure that the await completes before you invoke SetResult, SetException or SetCanceled a second time. This ensures that you never have two concurrent await calls.

public async ReusableTask<int> CalculateAge ()
{
    return 42;
}

async void SafelyAwait ()
{
    var task = CalculateAge  ().AsTask ();
    await task;
    await task;
}

Real world benefits

MonoTorrent is currently testing a prototype of this approach and the benefits are pretty noticeable. More detailed profiling results are on the MonoTorrent pull request, but the latest results at the time of writing are that total allocations reduced from 980MB to 210MB while downloading two 2GB torrents concurrently:

Task:
Task

ReusableTask:
ReusableTask