Releases: alanmcgovern/ReusableTasks
v1.0.4
General
- Slightly tweaked the internal logic which protects against accidental mis-use of
ReusableTask
andReusableTaskCompletionSource
. If a mis-use scenario is encountered then anInvalidTaskReuseException
will be thrown instead ofInvalidOperationException
. This makes it easier to disambiguate the cases later based on exception type.
v1.0.3
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
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
andTrySetCancelled
toReusableTaskCompletionSource
. - 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
General
- Removes a duplicate property from an internal type, making the objects a little smaller.
v1.0.0
General
- The main change here is that everything which could be made a
struct
has been made astruct
. 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
General
The only change is to pass Deterministic=true
to the compiler.
v0.99.3
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 optimisedasync 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
General
- Moved the
AsTask
extension methods to be proper methods onReusableTask
/ReusableTask<T>
.
v0.99.0
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
Introduction.
ReusableTasks provides three classes.
ReusableTask
- a zero allocation Task-like object which can be used in place ofSystem.Threading.Tasks.Task
when declaring async methods.ReusableTask<T>
- a zero allocation Task-like object which can be used in place ofSystem.Threading.Tasks.Task<T>
when declaring async methods.ReusableTaskCompletionSource<T>
- a zero allocation TaskCompletionSource-like object, built on top ofReusableTask<T<>
, which can be used in place ofSystem.Threading.Tasks.TaskCompletionSource<T>
in scenarios where you would normally repeatedly instantiate new copies ofTaskCompletionSource<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: