Skip to content

Commit

Permalink
Merge branch 'hotfix/0.7.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
Arvtesh committed Feb 14, 2018
2 parents 51d5063 + 4f1ea7d commit c25b2dd
Show file tree
Hide file tree
Showing 12 changed files with 471 additions and 104 deletions.
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/); this project adheres to [Semantic Versioning](http://semver.org/).

## [0.7.0] - 2018-20-10
## [0.7.1] - 2018-02-14

### Added
- Added possibility to store multiple exceptions in `AsyncResult`.
- Added `AggregateException` class for `net35` target.
- Added `IsEmpty` property to `AsyncResultQueue`.

### Changed
- `AsyncResult` implemenatino is changed to prevent returning null operation result when the operation is completed in some cases.

## [0.7.0] - 2018-02-10

### Added
- Added project documentation site (`docs` folder).
Expand Down
31 changes: 7 additions & 24 deletions src/DocFx/articles/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ TODO

# Code samples

1) The simpliest way of creating asyncronous operations is using `AsyncResult` static helpers. No `MonoBehaviour` is needed.
1) The simpliest way of creating asyncronous operations is using `AsyncResult` static helpers.

```csharp
var op1 = AsyncResult.FromEnumerator(GetEnum());
Expand All @@ -17,22 +17,13 @@ var op5 = AsyncResult.FromUpdateCallback<int>(c => c.SetResult(20));
2) There are several helpers that create completed operations:

```csharp
var op1 = AsyncResult.Completed;
var op2 = AsyncResult.Canceled;
var op1 = AsyncResult.CompletedOperation;
var op2 = AsyncResult.FromCanceled();
var op3 = AsyncResult.FromException(new Exception());
var op4 = AsyncResult.FromException<SomeClass>(new Exception());
var op5 = AsyncResult.FromResult<int>(20);
var op5 = AsyncResult.FromResult(20);
```

3) To execute an operation on a specific `MonoBehaviour` use `AsyncFactory`:

```csharp
var factory = new AsyncFactory(monoBehaviour);
var op1 = factory.FromEnumerator(GetEnum());
var op2 = factory.FromUpdateCallback(c => c.SetCompleted());
```

4) You can use `yield` and `await` to wait for `IAsyncOperation` instances without blocking the calling thread:
3) You can use `yield` and `await` to wait for `IAsyncOperation` instances without blocking the calling thread (obviously `await` cannot be used on .NET 3.5):

```csharp
IEnumerator TestYield()
Expand All @@ -46,7 +37,7 @@ async Task TestAwait()
}
```

5) Each `IAsyncOperation` maintains its status value (just like [Task](https://msdn.microsoft.com/ru-ru/library/system.threading.tasks.task(v=vs.110).aspx)):
4) Each `IAsyncOperation` maintains its status value (just like [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task):

```csharp
var op = AsyncResult.FromEnumerator(GetEnum());
Expand All @@ -58,15 +49,7 @@ if (op.IsCompletedSuccessfully)
}
```

6) Several operations (basically everything that derives [IAsyncResult](https://msdn.microsoft.com/en-us/library/system.iasyncresult(v=vs.110).aspx)) can be combited into single operation:

```csharp
var op1 = AsyncResult.FromUpdateCallback(c => c.SetCanceled());
var op2 = Task.Run(() => ThreadSleep(100));
var op3 = AsyncResult.WhenAll(op1, op2);
```

7) Operations can be chained together very much like [Tasks](https://msdn.microsoft.com/ru-ru/library/system.threading.tasks.task(v=vs.110).aspx):
5) Operations can be chained together very much like [Tasks](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) instances:

```csharp
var op1 = AsyncResult.Delay(TimeSpan.FromSeconds(10));
Expand Down
36 changes: 23 additions & 13 deletions src/DocFx/articles/intro.md
Original file line number Diff line number Diff line change
@@ -1,35 +1,45 @@
# What is this?

*UnityFx.Async* is a set of of classes and interfaces that extend [Unity3d](https://unity3d.com) asynchronous operations and can be used very much like [TPL](https://msdn.microsoft.com/en-us/library/dd460717(v=vs.110).aspx) in .NET. Basically it defines a generic container of an asynchronous operation result that can be accessed after the operation completes (aka `promise` or `future`).
*UnityFx.Async* is a set of of classes and interfaces that extend [Unity3d](https://unity3d.com) asynchronous operations and can be used very much like [TAP](https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming) in .NET. At its core library defines a container (`AsyncResult`) for an asynchronous operation state and result value (aka `promise` or `future`). The .NET analog is [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task).

Quick example:
Quick [Unity3d](https://unity3d.com) example:
```csharp
var op = AsyncResult.FromWebRequest(UnityWebRequest.Get("https://www.google.com"));
var op = AsyncResult.Delay(10);
yield return op;

if (op.IsCompletedSuccessfully)
{
// TODO
}
```
Or using .NET 4.6 and higher:
```csharp
var op = AsyncResult.Delay(10);
await op;

if (op.IsCompletedSuccessfully)
{
// TODO
}
```

You can use `AsyncResult` class as a cheap and portable replacement of [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task). Most of the functionality provided by the library can be used in .NET 3.5 (obviously async/await related stuff requires .NET 4.6 or higher).

# 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 not consistent at least:
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](https://docs.unity3d.com/ScriptReference/Coroutine.html) and [AsyncOperation](https://docs.unity3d.com/ScriptReference/AsyncOperation.html) both inherit [YieldInstruction](https://docs.unity3d.com/ScriptReference/YieldInstruction.html), while [CustomYieldInstruction](https://docs.unity3d.com/ScriptReference/CustomYieldInstruction.html) and [WWW](https://docs.unity3d.com/ScriptReference/WWW.html) do not.
- Running a coroutine requires [MonoBehaviour](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) instance which is not always convenient.
- Unity3d built-in asynchronous operations provide very little control after they have been started, [Coroutine](https://docs.unity3d.com/ScriptReference/Coroutine.html) 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](https://docs.unity3d.com/ScriptReference/AsyncOperation.html)-derived classes define operation results, [WWW](https://docs.unity3d.com/ScriptReference/WWW.html) uses completely inconsistent way of doing this.
- There is no standard way to return a coroutine result value. While some of the [AsyncOperation](https://docs.unity3d.com/ScriptReference/AsyncOperation.html)-derived classes define operation results, [WWW](https://docs.unity3d.com/ScriptReference/WWW.html) uses completely different approach.
- There is no easy way of chaining coroutines, waiting for completion of a coroutine group etc.
- Coroutines can't handle exceptions, because `yield return` statements cannot be surrounded with a try-catch block.
- 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](https://msdn.microsoft.com/en-us/library/system.iasyncresult(v=vs.110).aspx). Note that it is also the base interface for the .NET [Task](https://msdn.microsoft.com/en-us/library/system.threading.tasks.task(v=vs.110).aspx). This also means one can combine .NET asynchronous operations and the library operations.
- **No `MonoBehaviour` needed**: operations defined in the library can be created without specifying a `MonoBehaviour` instance to run on. There is an abstract `AsyncScheduler` class that can be used to implement custom coroutine runner logic.
- **Extended control** over the operations: `IAsyncOperation` interface mimics .NET [Task](https://msdn.microsoft.com/en-us/library/system.threading.tasks.task(v=vs.110).aspx) as much as possible.
- **Operation result** can be returned with the generic `IAsyncOperation<T>` interface (again very similar to [Task<T>](https://msdn.microsoft.com/en-us/library/dd321424(v=vs.110).aspx)).
- **Chaning of operations** can be easily achieved with a set of extension methods for `IAsyncOperation` interface.
- **Error/exception handling** is provided out-of-the-box for all predefined operations. Any exception thrown inside an operation update loop immmediately finish its execution with an error.
- **Cancellation** of an `IAsyncOperation` instance is achieved via usage of [CancellationToken](https://msdn.microsoft.com/en-us/library/system.threading.cancellationtoken(v=vs.110).aspx) values.
- **Single base interface** for all kinds of library asyncronous operations: [IAsyncResult](https://docs.microsoft.com/en-us/dotnet/api/system.iasyncresult). Note that it is also the base interface for the .NET [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task).
- **No `MonoBehaviour` needed**: operations defined in the library can be created without specifying a `MonoBehaviour` instance to run on.
- **Extended control** over the operations: `IAsyncOperation` interface mimics .NET [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) as much as possible.
- **Operation result** can be returned with the generic `IAsyncOperation<T>` interface (again very similar to [Task<T>](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1).
- **Chaning of operations** can be easily achieved with a `ContinueWith` methods for `IAsyncOperation` interface.
- **Yieldable/awaitable** implementation of the `IAsyncOperation` interface is provided to allow easy library extension.
2 changes: 1 addition & 1 deletion src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ public void TrySetException_ThrowsIfExceptionIsNull()
var op = new AsyncResult();

// Act/Assert
Assert.Throws<ArgumentNullException>(() => op.TrySetException(null, false));
Assert.Throws<ArgumentNullException>(() => op.TrySetException(default(Exception), false));
}

[Fact]
Expand Down
Loading

0 comments on commit c25b2dd

Please sign in to comment.