What is this?
UnityFx.Async is a set of of classes and interfaces that extend Unity3d asynchronous operations and can be used very much like Task-based Asynchronous Pattern (TAP) in .NET. At its core library defines a container (AsyncResult
) for an asynchronous operation state and result value (aka promise
or future
). In many aspects it mimics Task. For example, any AsyncResult
instance can have any number of continuations (added either explicitly via TryAddCompletionCallback
call or implicitly using async
/await
keywords). These continuations can be invoked on a captured SynchronizationContext (if any). The class inherits IAsyncResult (just like Task) and can be used for Asynchronous Programming Model (APM) implementation.
Quick Unity3d example:
IEnumerator Foo()
{
yield return AsyncResult.Delay(10);
// do something 10ms later
}
Or using .NET 4.6 and higher:
async Task Foo()
{
await AsyncResult.Delay(10);
// do something 10ms later
}
Processing a result of asynchronous operation:
void Foo(IAsyncOperation<int> op)
{
// The callback will be called even if the operation is already completed
op.AddCompletionCallback(o =>
{
if (o.IsCompletedSuccessfully)
{
Debug.Log("Result: " + (o as IAsyncOperation<int>).Result);
}
else
{
Debug.LogException(o.Exception);
}
});
}
Wrapping a Task<T> with AsyncResult
promise:
IAsyncOperation<int> Foo(Task<int> task)
{
var result = new AsyncCompletionSource<int>();
task.ContinueWith(t =>
{
if (t.IsFaulted)
{
result.SetException(task.Exception);
}
else if (t.IsCanceled)
{
result.SetCanceled();
}
else
{
result.SetResult(t.Result);
}
});
return result;
}
Please note that while AsyncResult
is designed as a lightweight and portable Task alternative, it's NOT a replacement for Task. It is recommended to use Task when possible and only switch to AsyncResult
if one of the following applies:
- .NET 3.5 compatibility is required.
- Operations should be used in Unity3d coroutines.
- Memory usage is a concern.
- You follow Asynchronous Programming Model (APM) and need IAsyncResult implementation.
Why do I need this?
Unity3d API issues
While Unity3d is a great engine, there are quite a few places where its API is not ideal. Asynchronous operations and coroutines management are the examples. While the concept of coroutines itself is great for frame-based applications, current Unity implementation is very basic and is not too consistent:
- There is no single base class/interface for yieldable entities. For example Coroutine and AsyncOperation both inherit YieldInstruction, while CustomYieldInstruction and WWW do not.
- Running a coroutine requires MonoBehaviour instance which is not always convenient.
- Unity3d built-in asynchronous operations provide very little control after they have been started, Coroutine for example doesn't even provide a way to determine if it is completed.
- There is no standard way to return a coroutine result value. While some of the AsyncOperation-derived classes define operation results, WWW uses completely different approach.
- There is no easy way of chaining coroutines, waiting for completion of a coroutine group etc.
- Error handling is problematic when using coroutines, because
yield return
statements cannot be surrounded with a try-catch block and there is no straightforward way or returning data from a coroutine.
UnityFx.Async features
- Single base interface for all kinds of library asyncronous operations: IAsyncResult. Note that it is also the base interface for the .NET Task.
- No
MonoBehaviour
needed: operations defined in the library do not need aMonoBehaviour
instance to run on. - Extended control over the operations:
IAsyncOperation
interface mimics .NET Task as much as possible. - Operation result can be returned with the generic
IAsyncOperation<T>
interface (again very similar to Task. - Chaning of operations can be easily achieved with a
ContinueWith
methods forIAsyncOperation
interface. - Yieldable/awaitable implementation of the
IAsyncOperation
interface is provided to allow easy library extension.