diff --git a/.appveyor.yml b/.appveyor.yml index 1b90810..38f57a8 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -35,7 +35,7 @@ dotnet_csproj: version: '$(GitVersion_MajorMinorPatch)' package_version: '$(GitVersion_NuGetVersionV2)' assembly_version: '$(GitVersion_AssemblySemVer)' - file_version: '$(GitVersion_MajorMinorPatch).$(GitVersion_CommitsSinceVersionSource)' + file_version: '$(GitVersion_AssemblySemVer)' informational_version: '$(GitVersion_InformationalVersion)' deploy: diff --git a/CHANGELOG.md b/CHANGELOG.md index c9f842b..236752b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,27 @@ 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.9.8] - 2018.11.09 + +### Added +- `AsyncResult` is now Task-like type and can be used as `async` method result value (requires C# 7.2). +- Added new `AsyncResult.FromAction` overloads. +- Added new `SynchronizationContext` extension methods (`PostAsync`, `InvokeAsync` etc). +- Added extension methods for `Socket`, `WebRequest`, `Stream` BCL classes. + +### Changed +- Moved BCL extension methods to namespace `UnityFx.Async.Extensions` (previously they were in namespace `UnityFx.Async`). + +### Fixed +- Fixed `AsyncResult` completion callbacks to be called event if `OnCompleted` throws. +- Fixed exception not been set for `AsyncResult.FaultedOperation` and `AsyncResult.CanceledOperation`. +- Disabled `MovieTexture` helpers for iOS/Android (as it is not supported on mobiles). + +### Removed +- Removed `AsyncResultQueue`. +- Removed `AsyncLazy`. + ----------------------- ## [0.9.7] - 2018.09.27 diff --git a/README.md b/README.md index 020c8cd..039ccfa 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,14 @@ Channel | UnityFx.Async | AppVeyor | [![Build status](https://ci.appveyor.com/api/projects/status/hfmq9vow53al7tpd/branch/master?svg=true)](https://ci.appveyor.com/project/Arvtesh/unityfx-async/branch/master) [![AppVeyor tests](https://img.shields.io/appveyor/tests/Arvtesh/unityFx-async.svg)](https://ci.appveyor.com/project/Arvtesh/unityfx-async/build/tests) NuGet | [![NuGet](https://img.shields.io/nuget/v/UnityFx.Async.svg)](https://www.nuget.org/packages/UnityFx.Async) Github | [![GitHub release](https://img.shields.io/github/release/Arvtesh/UnityFx.Async.svg?logo=github)](https://github.com/Arvtesh/UnityFx.Async/releases) -Unity Asset Store | [![Asynchronous operations for Unity](https://img.shields.io/badge/tools-v0.9.7-green.svg)](https://assetstore.unity.com/packages/tools/asynchronous-operations-for-unity-96696) +Unity Asset Store | [![Asynchronous operations for Unity](https://img.shields.io/badge/tools-v0.9.8-green.svg)](https://assetstore.unity.com/packages/tools/asynchronous-operations-for-unity-96696) -**Required Unity 5.4 or higher.** +**Requires Unity 5.4 or higher.** **If you enjoy using the library - please, [rate and review](https://assetstore.unity.com/packages/tools/asynchronous-operations-for-unity-96696) it on the Asset Store!** +**Please ask any questions and leave feedback at the [Unity forums](https://forum.unity.com/threads/asynchronous-operations-for-unity-free.522989/).** + ## Synopsis *UnityFx.Async* introduces effective and portable asynchronous operations that can be used very much like [Tasks](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) in .NET or [Promises](https://developers.google.com/web/fundamentals/primers/promises) in JS. [AsyncResult](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncResult.html) class is an implementation of a generic asynchronous operation (aka `promise` or `future`). In many aspects it mimics [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) (for example, it can be used with `async`/`await` operators, supports continuations and synchronization context capturing) while maintaining Unity/net35 compatibility. It is a great foundation toolset for any Unity project. @@ -27,15 +29,17 @@ The table below summarizes differences berween *UnityFx.Async* and other popular | Stat | UnityFx.Async | [C-Sharp-Promise](https://github.com/Real-Serious-Games/C-Sharp-Promise) | [TPL](https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl) | | :--- | :---: | :---: | :---: | | Thread-safe | ✔️ | - | ✔️ | -| Can capture synchronization context | ✔️ | - | ✔️ | | .NET 3.5 compilance | ✔️ | ✔️ | -️️ | +| Supports [SynchronizationContext](https://docs.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext) capturing | ✔️ | - | ✔️ | | Supports continuations | ✔️ | ✔️ | ✔️ | | Supports Unity coroutines | ️️✔️ | - | - | -| Supports `async` / `await` | ✔️ | - | ✔️ | -| Supports `promise`-like continuations | ✔️ | ✔️ | - | +| Supports [async / await](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/index) | ✔️ | - | ✔️ | +| Supports [promise](https://www.promisejs.org/)-like continuations | ✔️ | ✔️ | - | | Supports cancellation | ✔️ | -️ | ✔️ | | Supports progress reporting | ✔️ | ✔️ | ✔️ | | Supports child operations | - | - | ✔️ | +| Supports [Task-like types](https://github.com/dotnet/roslyn/blob/master/docs/features/task-types.md) (requires C# 7.2) | ✔️ | - | ✔️ | +| Supports [ExecutionContext](https://docs.microsoft.com/en-us/dotnet/api/system.threading.executioncontext) flow | - | - | ✔️ | | Minimum operation data size for 32-bit systems (in bytes) | 32+ | 36+ | 40+ | | Minimum number of allocations per continuation | ~1 | 5+ | 2+ | @@ -55,7 +59,7 @@ git submodule -q update --init The binaries are available as a [NuGet package](https://www.nuget.org/packages/UnityFx.Async). See [here](http://docs.nuget.org/docs/start-here/using-the-package-manager-console) for instructions on installing a package via nuget. One can also download them directly from [Github releases](https://github.com/Arvtesh/UnityFx.Async/releases). Unity3d users can import corresponding [Unity Asset Store package](https://assetstore.unity.com/packages/tools/asynchronous-operations-for-unity-96696) using the editor. ### Unity dependencies -The library core (`UnityFx.Async.dll`) does not depend on Unity and can be used in any .NET projects (via assembly or [NuGet](https://www.nuget.org/packages/UnityFx.Async) reference). All Unity-specific stuff depends on the core and is included in [Unity Asset Store package](https://assetstore.unity.com/packages/tools/asynchronous-operations-for-unity-96696). +The library core (`UnityFx.Async.dll`) does not depend on Unity and can be used in any .NET project (via assembly or [NuGet](https://www.nuget.org/packages/UnityFx.Async) reference). All Unity-specific stuff depends on the core and is included in [Asset Store package](https://assetstore.unity.com/packages/tools/asynchronous-operations-for-unity-96696). ## Understanding the concepts The topics below are just a quick summary of problems and the proposed solutions. For more details on the topic please see useful links at the end of this document. @@ -439,7 +443,7 @@ Most common way of creating own asynchronous operation is instantiating `AsyncCo * `AsyncResult`: an asynchronous operation without a result value. * `AsyncResult`: an asynchronous operation with a result value. -The sample code below demostrates creating a delay operation (in fact library provides one, this is just a simplified example): +The sample code below demostrates creating a delay operation (in fact the library provides one, this is just a simplified example): ```csharp public class TimerDelayResult : AsyncResult { @@ -479,10 +483,9 @@ public class TimerDelayResult : AsyncResult ``` ### Unity3d helpers -The library consists of 3 major parts: +As stated abovethe library include 2 main parts: * Core tools (defined in `UnityFx.Async.dll` assembly, do not depend on Unity3d); -* Unity3d-specific tools (defined as a collection of C# scripts located in `Assets/Plugins/UnityFx.Async` if installed as an Asset Store package, require Unity3d to compile/execute). -* Unity3d samples (defined as a collection of C# scripts located in `Assets/UnityFx.Async` if installed as an Asset Store package, require Unity3d to compile/execute). +* Unity3d-specific tools (defined as a collection of C# scripts if installed as an Asset Store package, require Unity3d to compile/execute). Everything described before (unless specified otherwise) does not require Unity and can be used in any application. The Unity-specific stuff is located in 3 classes: * `AsyncUtility`. Defines helper methods for accessing main thread in Unity, running coroutines without actually using a `MonoBehaviour` and waiting for native Unity asynchronous operations outside of coroutines. @@ -492,11 +495,11 @@ Everything described before (unless specified otherwise) does not require Unity For example, one can throw a few lines of code to be executed on a main thread using: ```csharp // Sends a delegate to the main thread and blocks calling thread until it is executed. -AsyncUtility.SendToMainThread(args => Debug.Log("On the main thread."), null); +AsyncUtility.SendToMainThread(() => Debug.Log("On the main thread.")); // Posts a delegate to the main thread and returns immediately. Returns an asynchronous operation that can be used to track the delegate execution. -AsyncUtility.PostToMainThread(args => Debug.Log("On the main thread."), null); +AsyncUtility.PostToMainThread(() => Debug.Log("On the main thread.")); // If calling thread is the main thread executes the delegate synchronously, otherwise posts it to the main thread. Returns an asynchronous operation that can be used to track the delegate execution. -AsyncUtility.InvokeOnMainThread(args => Debug.Log("On the main thread."), null); +AsyncUtility.InvokeOnMainThread(() => Debug.Log("On the main thread.")); ``` ## Comparison to .NET Tasks @@ -516,7 +519,6 @@ TPL | UnityFx.Async | Notes - | [IAsyncCancellable](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.IAsyncCancellable.html) | A cancellable operation. - | [IAsyncContinuation](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.IAsyncContinuation.html) | A generic non-delegate operation continuation. - | [IAsyncUpdatable](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.IAsyncUpdatable.html), [IAsyncUpdateSource](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.IAsyncUpdateSource.html) | A consumer and provider sides for frame update notifications. -- | [AsyncResultQueue<T>](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncResultQueue-1.html) | A FIFO queue of asynchronous operations executed sequentially. Please note that the library is NOT a replacement for [Tasks](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) or [TPL](https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl). As a general rule it is recommended to use [Tasks](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) and only switch to *UnityFx.Async* if one of the following applies: - .NET 3.5/[Unity3d](https://unity3d.com) compatibility is required. @@ -524,7 +526,7 @@ Please note that the library is NOT a replacement for [Tasks](https://docs.micro - An extendable [IAsyncResult](https://docs.microsoft.com/en-us/dotnet/api/system.iasyncresult) implementation is needed. ## Motivation -The project was initially created to help author with his [Unity3d](https://unity3d.com) projects. Unity's [AsyncOperation](https://docs.unity3d.com/ScriptReference/AsyncOperation.html) and similar can only be used in coroutines, cannot be extended and mostly do not return result or error information, .NET 3.5 does not provide much help either and even with .NET 4.6 support compatibility requirements often do not allow using [Tasks](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task). When I caught myself writing the same asynchronous operation wrappers in each project I decided to share my experience to the best of human kind. +The project was initially created to help author with his [Unity3d](https://unity3d.com) projects. Unity's [AsyncOperation](https://docs.unity3d.com/ScriptReference/AsyncOperation.html) and similar can only be used in coroutines, cannot be extended and mostly do not return result or error information, .NET 3.5 does not provide much help either and even with .NET 4.6 support compatibility requirements often do not allow using [Tasks](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) (and they are quite expensive). When I caught myself writing the same asynchronous operation wrappers in each project I decided to share my experience to the best of human kind. ## Documentation Please see the links below for extended information on the product: @@ -554,7 +556,7 @@ The project uses [SemVer](https://semver.org/) versioning pattern. For the versi Please see the [![license](https://img.shields.io/github/license/Arvtesh/UnityFx.Async.svg)](LICENSE.md) for details. ## Acknowledgments -Working on this project is a great experience. Please see below list of sources of my inspiration (in no particular order): +Working on this project is a great experience. Please see below a list of my inspiration sources (in no particular order): * [.NET reference source](https://referencesource.microsoft.com/mscorlib/System/threading/Tasks/Task.cs.html). A great source of knowledge and good programming practices. * [C-Sharp-Promise](https://github.com/Real-Serious-Games/C-Sharp-Promise). Another great C# promise library with excellent documentation. * [UniRx](https://github.com/neuecc/UniRx). A deeply reworked [Rx.NET](https://github.com/Reactive-Extensions/Rx.NET) port to Unity. diff --git a/src/UnityFx.Async.AssetStore/Assets/Plugins/UnityFx.Async/README.txt b/src/UnityFx.Async.AssetStore/Assets/Plugins/UnityFx.Async/README.txt index 8302ce4..7633b7c 100644 --- a/src/UnityFx.Async.AssetStore/Assets/Plugins/UnityFx.Async/README.txt +++ b/src/UnityFx.Async.AssetStore/Assets/Plugins/UnityFx.Async/README.txt @@ -1,8 +1,5 @@ SUMMARY -The package provides lightweight Task-like asynchronous operations (promises) for Unity3d. - -PUBLISHER INFORMATION -https://www.linkedin.com/in/alexander-bogarsukov-93b9682/ +Lightweight Task-like asynchronous operations (promises) for Unity3d. LICENSE https://github.com/Arvtesh/UnityFx.Async/blob/master/LICENSE.md diff --git a/src/UnityFx.Async.AssetStore/Assets/Plugins/UnityFx.Async/Scripts/AsyncUtility.cs b/src/UnityFx.Async.AssetStore/Assets/Plugins/UnityFx.Async/Scripts/AsyncUtility.cs index 5b5192d..875bea3 100644 --- a/src/UnityFx.Async.AssetStore/Assets/Plugins/UnityFx.Async/Scripts/AsyncUtility.cs +++ b/src/UnityFx.Async.AssetStore/Assets/Plugins/UnityFx.Async/Scripts/AsyncUtility.cs @@ -11,6 +11,7 @@ using System.Threading; using UnityEngine; using UnityEngine.Networking; +using UnityFx.Async.Extensions; namespace UnityFx.Async { diff --git a/src/UnityFx.Async.AssetStore/Assets/Plugins/UnityFx.Async/Scripts/AsyncWww.cs b/src/UnityFx.Async.AssetStore/Assets/Plugins/UnityFx.Async/Scripts/AsyncWww.cs index fdf60c6..d7e7062 100644 --- a/src/UnityFx.Async.AssetStore/Assets/Plugins/UnityFx.Async/Scripts/AsyncWww.cs +++ b/src/UnityFx.Async.AssetStore/Assets/Plugins/UnityFx.Async/Scripts/AsyncWww.cs @@ -204,7 +204,7 @@ public static IAsyncOperation GetTexture(string url, bool nonReadable return result; } -#if !UNITY_2018_2_OR_NEWER +#if !UNITY_2018_2_OR_NEWER && !UNITY_IOS && !UNITY_ANDROID /// /// Creates an asyncronous operation optimized for downloading a via HTTP GET. @@ -254,7 +254,7 @@ public static T GetResult(UnityWebRequest request) where T : class { return ((DownloadHandlerAudioClip)request.downloadHandler).audioClip as T; } -#if !UNITY_5 && !UNITY_2018_2_OR_NEWER +#if !UNITY_5 && !UNITY_2018_2_OR_NEWER && !UNITY_IOS && !UNITY_ANDROID else if (request.downloadHandler is DownloadHandlerMovieTexture) { return ((DownloadHandlerMovieTexture)request.downloadHandler).movieTexture as T; @@ -298,21 +298,24 @@ public static T GetResult(WWW request) where T : class { return request.GetAudioClip() as T; } -#if !UNITY_2018_2_OR_NEWER - else if (typeof(T) == typeof(MovieTexture)) - { - return request.GetMovieTexture() as T; - } -#endif #else else if (typeof(T) == typeof(AudioClip)) { return request.audioClip as T; } +#endif +#if !UNITY_2018_2_OR_NEWER && !UNITY_IOS && !UNITY_ANDROID +#if UNITY_5_6_OR_NEWER + else if (typeof(T) == typeof(MovieTexture)) + { + return request.GetMovieTexture() as T; + } +#else else if (typeof(T) == typeof(MovieTexture)) { return request.movie as T; } +#endif #endif else if (typeof(T) == typeof(byte[])) { @@ -327,5 +330,5 @@ public static T GetResult(WWW request) where T : class } #endif + } } -} diff --git a/src/UnityFx.Async.AssetStore/Assets/Plugins/UnityFx.Async/Scripts/UnityExtensions.cs b/src/UnityFx.Async.AssetStore/Assets/Plugins/UnityFx.Async/Scripts/UnityExtensions.cs index 0990e47..fc8eddb 100644 --- a/src/UnityFx.Async.AssetStore/Assets/Plugins/UnityFx.Async/Scripts/UnityExtensions.cs +++ b/src/UnityFx.Async.AssetStore/Assets/Plugins/UnityFx.Async/Scripts/UnityExtensions.cs @@ -120,7 +120,13 @@ public AsyncOperationAwaiter(AsyncOperation op) /// Gets a value indicating whether the underlying operation is completed. /// /// The operation completion flag. - public bool IsCompleted => _op.isDone; + public bool IsCompleted + { + get + { + return _op.isDone; + } + } /// /// Returns the source result value. @@ -216,7 +222,13 @@ public UnityWebRequestAwaiter(UnityWebRequest op) /// Gets a value indicating whether the underlying operation is completed. /// /// The operation completion flag. - public bool IsCompleted => _op.isDone; + public bool IsCompleted + { + get + { + return _op.isDone; + } + } /// /// Returns the source result value. @@ -314,7 +326,13 @@ public WwwAwaiter(WWW op) /// Gets a value indicating whether the underlying operation is completed. /// /// The operation completion flag. - public bool IsCompleted => _op.isDone; + public bool IsCompleted + { + get + { + return _op.isDone; + } + } /// /// Returns the source result value. diff --git a/src/UnityFx.Async.AssetStore/UnityFx.Async.AssetStore.csproj b/src/UnityFx.Async.AssetStore/UnityFx.Async.AssetStore.csproj index 43552dc..1bbd0bc 100644 --- a/src/UnityFx.Async.AssetStore/UnityFx.Async.AssetStore.csproj +++ b/src/UnityFx.Async.AssetStore/UnityFx.Async.AssetStore.csproj @@ -17,7 +17,7 @@ false true ../CodingConventions/Cs/CsharpRules.ruleset - 6 + 4 diff --git a/src/UnityFx.Async.Tests/Tests/AsyncResultQueueTests.cs b/src/UnityFx.Async.Tests/Tests/AsyncResultQueueTests.cs deleted file mode 100644 index 9c2acf8..0000000 --- a/src/UnityFx.Async.Tests/Tests/AsyncResultQueueTests.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Alexander Bogarsukov. -// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. - -using System; -using System.Threading; -using System.Threading.Tasks; -using Xunit; - -namespace UnityFx.Async -{ - public class AsyncResultQueueTests - { - [Fact] - public void DefaultConstructor_InitializesEmptyQueue() - { - // Arrange/Act - var queue = new AsyncResultQueue(); - - // Assert - Assert.True(queue.IsEmpty); - Assert.False(queue.Suspended); - Assert.Empty(queue); - Assert.Equal(0, queue.MaxCount); - Assert.Null(queue.Current); - } - - [Fact] - public void Add_AddsNewOperation() - { - // Arrage - var queue = new AsyncResultQueue(); - var op = new AsyncResult(); - - // Act - var result = queue.Add(op); - - // Assert - Assert.True(result); - Assert.False(queue.IsEmpty); - Assert.NotEmpty(queue); - Assert.Equal(op, queue.Current); - Assert.Equal(AsyncOperationStatus.Running, op.Status); - } - - [Fact] - public void Add_ThrowsOnCompletedOperations() - { - // Arrage - var queue = new AsyncResultQueue(); - var op = AsyncResult.CompletedOperation; - - // Act/Assert - Assert.Throws(() => queue.Add(op)); - } - - [Fact] - public void Clear_RemovesAllOperations() - { - // Arrage - var op = new AsyncResult(); - var op2 = new AsyncResult(); - var queue = new AsyncResultQueue() { op, op2 }; - - // Act - queue.Clear(); - - // Assert - Assert.True(queue.IsEmpty); - Assert.Empty(queue); - Assert.Null(queue.Current); - } - } -} diff --git a/src/UnityFx.Async.Tests/Tests/PromiseChainTests.cs b/src/UnityFx.Async.Tests/Tests/PromiseChainTests.cs new file mode 100644 index 0000000..94f7987 --- /dev/null +++ b/src/UnityFx.Async.Tests/Tests/PromiseChainTests.cs @@ -0,0 +1,95 @@ +// Copyright (c) Alexander Bogarsukov. +// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace UnityFx.Async.Promises +{ + public class PromiseChainTests + { + [Fact] + public async Task Catch_CalledIfThenThrows() + { + // Arrange + var op = AsyncResult.Delay(10); + var thenOp = op.Then(() => throw new Exception()); + var catchCalled = false; + var catchOp = thenOp.Catch(e => catchCalled = true); + + // Act + await catchOp; + + // Assert + Assert.True(thenOp.IsFaulted); + Assert.IsType(thenOp.Exception); + Assert.True(catchOp.IsCompletedSuccessfully); + Assert.True(catchCalled); + } + + [Fact] + public async Task Catch_CalledIfThenFails() + { + // Arrange + var op = AsyncResult.Delay(10); + var thenOp = op.Then(() => AsyncResult.FaultedOperation); + var catchCalled = false; + var catchOp = thenOp.Catch(e => catchCalled = true); + + // Act + await catchOp; + + // Assert + Assert.True(thenOp.IsFaulted); + Assert.IsType(thenOp.Exception); + Assert.True(catchOp.IsCompletedSuccessfully); + Assert.True(catchCalled); + } + + [Fact] + public async Task Then_NotCalledIfPreviousThenThrows() + { + // Arrange + var op = AsyncResult.Delay(10); + var thenOp = op.Then(() => throw new Exception()); + var thenCalled = false; + var thenOp2 = thenOp.Then(() => thenCalled = true); + var catchCalled = false; + var catchOp = thenOp2.Catch(e => catchCalled = true); + + // Act + await catchOp; + + // Assert + Assert.True(thenOp.IsFaulted); + Assert.True(thenOp2.IsFaulted); + Assert.True(catchOp.IsCompletedSuccessfully); + Assert.True(catchCalled); + Assert.False(thenCalled); + } + + [Fact] + public async Task Then_NotCalledIfPreviousThenFails() + { + // Arrange + var op = AsyncResult.Delay(10); + var thenOp = op.Then(() => AsyncResult.FaultedOperation); + var thenCalled = false; + var thenOp2 = thenOp.Then(() => thenCalled = true); + var catchCalled = false; + var catchOp = thenOp2.Catch(e => catchCalled = true); + + // Act + await catchOp; + + // Assert + Assert.True(thenOp.IsFaulted); + Assert.True(thenOp2.IsFaulted); + Assert.True(catchOp.IsCompletedSuccessfully); + Assert.True(catchCalled); + Assert.False(thenCalled); + } + } +} diff --git a/src/UnityFx.Async.Tests/UnityFx.Async.Tests.csproj b/src/UnityFx.Async.Tests/UnityFx.Async.Tests.csproj index 439a6f3..9e1b61a 100644 --- a/src/UnityFx.Async.Tests/UnityFx.Async.Tests.csproj +++ b/src/UnityFx.Async.Tests/UnityFx.Async.Tests.csproj @@ -7,10 +7,13 @@ - + - - + + + all + runtime; build; native; contentfiles; analyzers + diff --git a/src/UnityFx.Async/Implementation/Public/AsyncCompletionSource.cs b/src/UnityFx.Async/Api/AsyncCompletionSource.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Public/AsyncCompletionSource.cs rename to src/UnityFx.Async/Api/AsyncCompletionSource.cs diff --git a/src/UnityFx.Async/Implementation/Public/AsyncCompletionSource{TResult}.cs b/src/UnityFx.Async/Api/AsyncCompletionSource{TResult}.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Public/AsyncCompletionSource{TResult}.cs rename to src/UnityFx.Async/Api/AsyncCompletionSource{TResult}.cs diff --git a/src/UnityFx.Async/Api/AsyncExtensions.cs b/src/UnityFx.Async/Api/AsyncExtensions.cs new file mode 100644 index 0000000..0d78855 --- /dev/null +++ b/src/UnityFx.Async/Api/AsyncExtensions.cs @@ -0,0 +1,1490 @@ +// Copyright (c) Alexander Bogarsukov. +// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Threading; +#if !NET35 +using System.Threading.Tasks; +#endif + +namespace UnityFx.Async +{ + /// + /// Extension methods for and related classes. + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static class AsyncExtensions + { + #region data + +#if !NET35 + + private static Action _cancelHandler; + +#endif + + #endregion + + #region IAsyncOperation + + #region Common + + /// + /// Throws if the specified operation is faulted/canceled. + /// + public static void ThrowIfNonSuccess(this IAsyncOperation op) + { + var status = op.Status; + + if (status == AsyncOperationStatus.Faulted) + { + if (!AsyncResult.TryThrowException(op.Exception)) + { + // Should never get here. Exception should never be null in faulted state. + throw new Exception(); + } + } + else if (status == AsyncOperationStatus.Canceled) + { + if (!AsyncResult.TryThrowException(op.Exception)) + { + throw new OperationCanceledException(); + } + } + } + +#if !NET35 + + /// + /// Registers a that can be used to cancel the specified operation. + /// + /// An operation to register for. + /// A cancellation token that can be used to cancel the operation. + /// Thrown if the target operation does not support cancellation. + /// Returns the target operation. + public static IAsyncOperation WithCancellation(this IAsyncOperation op, CancellationToken cancellationToken) + { + if (cancellationToken.CanBeCanceled && !op.IsCompleted) + { + if (cancellationToken.IsCancellationRequested) + { + op.Cancel(); + } + else + { + if (_cancelHandler == null) + { + _cancelHandler = args => (args as IAsyncCancellable).Cancel(); + } + + cancellationToken.Register(_cancelHandler, op, false); + } + } + + return op; + } + +#endif + + #endregion + + #region Wait + + /// + /// Waits for the to complete execution. After that rethrows the operation exception (if any). + /// + /// The operation to wait for. + /// Thrown is the operation is disposed. + /// + /// + public static void Wait(this IAsyncOperation op) + { + if (!op.IsCompleted) + { + op.AsyncWaitHandle.WaitOne(); + } + + ThrowIfNonSuccess(op); + } + + /// + /// Waits for the to complete execution within a specified number of milliseconds. After that rethrows the operation exception (if any). + /// + /// The operation to wait for. + /// The number of milliseconds to wait, or (-1) to wait indefinitely. + /// if the operation completed execution within the allotted time; otherwise, . + /// is a negative number other than -1. + /// Thrown is the operation is disposed. + /// + /// + public static bool Wait(this IAsyncOperation op, int millisecondsTimeout) + { + var result = true; + + if (!op.IsCompleted) + { + result = op.AsyncWaitHandle.WaitOne(millisecondsTimeout); + } + + if (result) + { + ThrowIfNonSuccess(op); + } + + return result; + } + + /// + /// Waits for the to complete execution within a specified time interval. After that rethrows the operation exception (if any). + /// + /// The operation to wait for. + /// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. + /// if the operation completed execution within the allotted time; otherwise, . + /// is a negative number other than -1 milliseconds, or is greater than . + /// Thrown is the operation is disposed. + /// + /// + public static bool Wait(this IAsyncOperation op, TimeSpan timeout) + { + var result = true; + + if (!op.IsCompleted) + { + result = op.AsyncWaitHandle.WaitOne(timeout); + } + + if (result) + { + ThrowIfNonSuccess(op); + } + + return result; + } + +#if !NET35 + + /// + /// Waits for the to complete execution. After that rethrows the operation exception (if any). + /// The wait terminates if a cancellation token is canceled before the operation completes. + /// + /// The operation to wait for. + /// A cancellation token to observe while waiting for the operation to complete. + /// Thrown is the operation is disposed. + /// The was canceled. + /// + public static void Wait(this IAsyncOperation op, CancellationToken cancellationToken) + { + WaitInternal(op, cancellationToken); + ThrowIfNonSuccess(op); + } + + /// + /// Waits for the to complete execution within a specified number of milliseconds. After that + /// rethrows the operation exception (if any). The wait terminates if a timeout interval elapses or a cancellation token is + /// canceled before the operation completes. + /// + /// The operation to wait for. + /// The number of milliseconds to wait, or (-1) to wait indefinitely. + /// A cancellation token to observe while waiting for the operation to complete. + /// if the operation completed execution within the allotted time; otherwise, . + /// is a negative number other than -1. + /// The was canceled. + /// Thrown is the operation is disposed. + /// + public static bool Wait(this IAsyncOperation op, int millisecondsTimeout, CancellationToken cancellationToken) + { + if (WaitInternal(op, millisecondsTimeout, cancellationToken)) + { + ThrowIfNonSuccess(op); + return true; + } + + return false; + } + + /// + /// Waits for the to complete execution within a specified time interval. After that rethrows + /// the operation exception (if any). The wait terminates if a timeout interval elapses or a cancellation token is canceled + /// before the operation completes. + /// + /// The operation to wait for. + /// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. + /// A cancellation token to observe while waiting for the operation to complete. + /// if the operation completed execution within the allotted time; otherwise, . + /// is a negative number other than -1 milliseconds, or is greater than . + /// The was canceled. + /// Thrown is the operation is disposed. + /// + public static bool Wait(this IAsyncOperation op, TimeSpan timeout, CancellationToken cancellationToken) + { + if (WaitInternal(op, timeout, cancellationToken)) + { + ThrowIfNonSuccess(op); + return true; + } + + return false; + } + +#endif + + #endregion + + #region Join + + /// + /// Waits for the to complete execution. After that rethrows the operation exception (if any). + /// + /// The operation to join. + /// Thrown is the operation is disposed. + /// + /// + /// + public static void Join(this IAsyncOperation op) + { + if (!op.IsCompleted) + { + op.AsyncWaitHandle.WaitOne(); + } + + ThrowIfNonSuccess(op); + } + + /// + /// Waits for the to complete execution within a specified number of milliseconds. After that rethrows the operation exception (if any). + /// + /// The operation to wait for. + /// The number of milliseconds to wait, or (-1) to wait indefinitely. + /// is a negative number other than -1. + /// Thrown if the operation did not completed within . + /// Thrown is the operation is disposed. + /// + /// + /// + public static void Join(this IAsyncOperation op, int millisecondsTimeout) + { + var result = true; + + if (!op.IsCompleted) + { + result = op.AsyncWaitHandle.WaitOne(millisecondsTimeout); + } + + if (result) + { + ThrowIfNonSuccess(op); + } + else + { + throw new TimeoutException(); + } + } + + /// + /// Waits for the to complete execution within a specified timeout. After that rethrows the operation exception (if any). + /// + /// The operation to wait for. + /// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. + /// is a negative number other than -1 milliseconds, or is greater than . + /// Thrown if the operation did not completed within . + /// Thrown is the operation is disposed. + /// + /// + /// + public static void Join(this IAsyncOperation op, TimeSpan timeout) + { + var result = true; + + if (!op.IsCompleted) + { + result = op.AsyncWaitHandle.WaitOne(timeout); + } + + if (result) + { + ThrowIfNonSuccess(op); + } + else + { + throw new TimeoutException(); + } + } + + /// + /// Waits for the to complete execution. After that rethrows the operation exception (if any). + /// + /// The operation to join. + /// The operation result. + /// Thrown is the operation is disposed. + /// + /// + /// + public static TResult Join(this IAsyncOperation op) + { + if (!op.IsCompleted) + { + op.AsyncWaitHandle.WaitOne(); + } + + return op.Result; + } + + /// + /// Waits for the to complete execution within a specified number of milliseconds. After that rethrows the operation exception (if any). + /// + /// The operation to wait for. + /// The number of milliseconds to wait, or (-1) to wait indefinitely. + /// The operation result. + /// is a negative number other than -1. + /// Thrown if the operation did not completed within . + /// Thrown is the operation is disposed. + /// + /// + /// + public static TResult Join(this IAsyncOperation op, int millisecondsTimeout) + { + var result = true; + + if (!op.IsCompleted) + { + result = op.AsyncWaitHandle.WaitOne(millisecondsTimeout); + } + + if (!result) + { + throw new TimeoutException(); + } + + return op.Result; + } + + /// + /// Waits for the to complete execution within a specified timeout. After that rethrows the operation exception (if any). + /// + /// The operation to wait for. + /// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. + /// The operation result. + /// is a negative number other than -1 milliseconds, or is greater than . + /// Thrown if the operation did not completed within . + /// Thrown is the operation is disposed. + /// + /// + /// + public static TResult Join(this IAsyncOperation op, TimeSpan timeout) + { + var result = true; + + if (!op.IsCompleted) + { + result = op.AsyncWaitHandle.WaitOne(timeout); + } + + if (!result) + { + throw new TimeoutException(); + } + + return op.Result; + } + +#if !NET35 + + /// + /// Waits for the to complete execution. After that rethrows the operation exception (if any). The wait terminates + /// if a cancellation token is canceled before the operation completes. + /// + /// The operation to join. + /// A cancellation token to observe while waiting for the operation to complete. + /// The was canceled. + /// Thrown is the operation is disposed. + /// + public static void Join(this IAsyncOperation op, CancellationToken cancellationToken) + { + WaitInternal(op, cancellationToken); + ThrowIfNonSuccess(op); + } + + /// + /// Waits for the to complete execution within a specified number of milliseconds. After that rethrows the operation exception (if any). + /// The wait terminates if a timeout interval elapses or a cancellation token is canceled before the operation completes. + /// + /// The operation to wait for. + /// The number of milliseconds to wait, or (-1) to wait indefinitely. + /// A cancellation token to observe while waiting for the operation to complete. + /// is a negative number other than -1. + /// Thrown if the operation did not completed within . + /// The was canceled. + /// Thrown is the operation is disposed. + /// + public static void Join(this IAsyncOperation op, int millisecondsTimeout, CancellationToken cancellationToken) + { + if (WaitInternal(op, millisecondsTimeout, cancellationToken)) + { + ThrowIfNonSuccess(op); + } + else + { + throw new TimeoutException(); + } + } + + /// + /// Waits for the to complete execution within a specified timeout. After that rethrows the operation exception (if any). + /// The wait terminates if a timeout interval elapses or a cancellation token is canceled before the operation completes. + /// + /// The operation to wait for. + /// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. + /// A cancellation token to observe while waiting for the operation to complete. + /// is a negative number other than -1 milliseconds, or is greater than . + /// Thrown if the operation did not completed within . + /// The was canceled. + /// Thrown is the operation is disposed. + /// + public static void Join(this IAsyncOperation op, TimeSpan timeout, CancellationToken cancellationToken) + { + if (WaitInternal(op, timeout, cancellationToken)) + { + ThrowIfNonSuccess(op); + } + else + { + throw new TimeoutException(); + } + } + + /// + /// Waits for the to complete execution. After that rethrows the operation exception (if any). + /// The wait terminates if a cancellation token is canceled before the operation completes. + /// + /// The operation to join. + /// A cancellation token to observe while waiting for the operation to complete. + /// The operation result. + /// The was canceled. + /// Thrown is the operation is disposed. + /// + public static TResult Join(this IAsyncOperation op, CancellationToken cancellationToken) + { + WaitInternal(op, cancellationToken); + return op.Result; + } + + /// + /// Waits for the to complete execution within a specified number of milliseconds. After that rethrows the operation exception (if any). + /// The wait terminates if a timeout interval elapses or a cancellation token is canceled before the operation completes. + /// + /// The operation to wait for. + /// The number of milliseconds to wait, or (-1) to wait indefinitely. + /// A cancellation token to observe while waiting for the operation to complete. + /// The operation result. + /// is a negative number other than -1. + /// Thrown if the operation did not completed within . + /// The was canceled. + /// Thrown is the operation is disposed. + /// + public static TResult Join(this IAsyncOperation op, int millisecondsTimeout, CancellationToken cancellationToken) + { + if (!WaitInternal(op, millisecondsTimeout, cancellationToken)) + { + throw new TimeoutException(); + } + + return op.Result; + } + + /// + /// Waits for the to complete execution within a specified timeout. After that rethrows the operation exception (if any). + /// The wait terminates if a timeout interval elapses or a cancellation token is canceled before the operation completes. + /// + /// The operation to wait for. + /// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. + /// A cancellation token to observe while waiting for the operation to complete. + /// The operation result. + /// is a negative number other than -1 milliseconds, or is greater than . + /// Thrown if the operation did not completed within . + /// The was canceled. + /// Thrown is the operation is disposed. + /// + public static TResult Join(this IAsyncOperation op, TimeSpan timeout, CancellationToken cancellationToken) + { + if (!WaitInternal(op, timeout, cancellationToken)) + { + throw new TimeoutException(); + } + + return op.Result; + } + +#endif + + #endregion + + #region ContinueWith + + /// + /// Creates a continuation that executes when the target completes. + /// + /// The operation to continue. + /// An action to run when the completes. + /// Thrown if the is . + /// An operation that is executed after completes. + /// + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action action) + { + return ContinueWith(op, action, AsyncContinuationOptions.None); + } + + /// + /// Creates a continuation that executes when the target completes. + /// + /// The operation to continue. + /// An action to run when the completes. + /// Options for when the is executed. + /// Thrown if the is . + /// An operation that is executed after completes. + /// + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action action, AsyncContinuationOptions options) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return new ContinueWithResult(op, options, action, null); + } + + /// + /// Creates a continuation that executes when the target completes. + /// + /// The operation to continue. + /// An action to run when the completes. + /// A user-defined state object that is passed as second argument to . + /// Thrown if the is . + /// An operation that is executed after completes. + /// + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action action, object userState) + { + return ContinueWith(op, action, userState, AsyncContinuationOptions.None); + } + + /// + /// Creates a continuation that executes when the target completes. + /// + /// The operation to continue. + /// An action to run when the completes. + /// A user-defined state object that is passed as second argument to . + /// Options for when the is executed. + /// Thrown if the is . + /// An operation that is executed after completes. + /// + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action action, object userState, AsyncContinuationOptions options) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return new ContinueWithResult(op, options, action, userState); + } + + /// + /// Creates a continuation that executes when the target completes. + /// + /// The operation to continue. + /// An action to run when the completes. + /// Thrown if the is . + /// An operation that is executed after completes. + /// + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func action) + { + return ContinueWith(op, action, AsyncContinuationOptions.None); + } + + /// + /// Creates a continuation that executes when the target completes. + /// + /// The operation to continue. + /// An action to run when the completes. + /// Options for when the is executed. + /// Thrown if the is . + /// An operation that is executed after completes. + /// + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func action, AsyncContinuationOptions options) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return new ContinueWithResult(op, options, action, null); + } + + /// + /// Creates a continuation that executes when the target completes. + /// + /// The operation to continue. + /// An action to run when the completes. + /// A user-defined state object that is passed as second argument to . + /// Thrown if the is . + /// An operation that is executed after completes. + /// + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func action, object userState) + { + return ContinueWith(op, action, userState, AsyncContinuationOptions.None); + } + + /// + /// Creates a continuation that executes when the target completes. + /// + /// The operation to continue. + /// An action to run when the completes. + /// A user-defined state object that is passed as second argument to . + /// Options for when the is executed. + /// Thrown if the is . + /// An operation that is executed after completes. + /// + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func action, object userState, AsyncContinuationOptions options) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return new ContinueWithResult(op, options, action, userState); + } + + /// + /// Creates a continuation that executes when the target completes. + /// + /// The operation to continue. + /// An action to run when the completes. + /// Thrown if the is . + /// An operation that is executed after completes. + /// + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action> action) + { + return ContinueWith(op, action, AsyncContinuationOptions.None); + } + + /// + /// Creates a continuation that executes when the target completes. + /// + /// The operation to continue. + /// An action to run when the completes. + /// Options for when the is executed. + /// Thrown if the is . + /// An operation that is executed after completes. + /// + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action> action, AsyncContinuationOptions options) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return new ContinueWithResult(op, options, action, null); + } + + /// + /// Creates a continuation that executes when the target completes. + /// + /// The operation to continue. + /// An action to run when the completes. + /// A user-defined state object that is passed as second argument to . + /// Thrown if the is . + /// An operation that is executed after completes. + /// + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action, object> action, object userState) + { + return ContinueWith(op, action, userState, AsyncContinuationOptions.None); + } + + /// + /// Creates a continuation that executes when the target completes. + /// + /// The operation to continue. + /// An action to run when the completes. + /// A user-defined state object that is passed as second argument to . + /// Options for when the is executed. + /// Thrown if the is . + /// An operation that is executed after completes. + /// + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action, object> action, object userState, AsyncContinuationOptions options) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return new ContinueWithResult(op, options, action, userState); + } + + /// + /// Creates a continuation that executes when the target completes. + /// + /// The operation to continue. + /// An action to run when the completes. + /// Thrown if the is . + /// An operation that is executed after completes. + /// + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func, TNewResult> action) + { + return ContinueWith(op, action, AsyncContinuationOptions.None); + } + + /// + /// Creates a continuation that executes when the target completes. + /// + /// The operation to continue. + /// An action to run when the completes. + /// Options for when the is executed. + /// Thrown if the is . + /// An operation that is executed after completes. + /// + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func, TNewResult> action, AsyncContinuationOptions options) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return new ContinueWithResult(op, options, action, null); + } + + /// + /// Creates a continuation that executes when the target completes. + /// + /// The operation to continue. + /// An action to run when the completes. + /// A user-defined state object that is passed as second argument to . + /// Thrown if the is . + /// An operation that is executed after completes. + /// + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func, object, TNewResult> action, object userState) + { + return ContinueWith(op, action, userState, AsyncContinuationOptions.None); + } + + /// + /// Creates a continuation that executes when the target completes. + /// + /// The operation to continue. + /// An action to run when the completes. + /// A user-defined state object that is passed as second argument to . + /// Options for when the is executed. + /// Thrown if the is . + /// An operation that is executed after completes. + /// + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func, object, TNewResult> action, object userState, AsyncContinuationOptions options) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return new ContinueWithResult(op, options, action, userState); + } + + #endregion + + #region Unwrap + + /// + /// Creates a proxy that represents the asynchronous operation of a IAsyncOperation<IAsyncOperation>. + /// + /// The source operation. + /// The unwrapped operation. + /// + public static IAsyncOperation Unwrap(this IAsyncOperation op) + { + return new UnwrapResult(op); + } + + /// + /// Creates a proxy that represents the asynchronous operation of a IAsyncOperation<IAsyncOperation<TResult>>. + /// + /// The source operation. + /// The unwrapped operation. + /// + public static IAsyncOperation Unwrap(this IAsyncOperation> op) + { + return new UnwrapResult(op); + } + + #endregion + + #region ToTask + +#if !NET35 + + /// + /// Creates a instance matching the source . + /// + /// The target operation. + /// + public static Task ToTask(this IAsyncOperation op) + { + var status = op.Status; + + if (status == AsyncOperationStatus.RanToCompletion) + { + return Task.CompletedTask; + } + else if (status == AsyncOperationStatus.Faulted) + { + return Task.FromException(op.Exception); + } + else if (status == AsyncOperationStatus.Canceled) + { + return Task.FromCanceled(new CancellationToken(true)); + } + else + { + var tcs = new TaskCompletionSource(); + + op.AddCompletionCallback( + asyncOp => + { + status = op.Status; + + if (status == AsyncOperationStatus.RanToCompletion) + { + tcs.TrySetResult(null); + } + else if (status == AsyncOperationStatus.Faulted) + { + tcs.TrySetException(op.Exception); + } + else if (status == AsyncOperationStatus.Canceled) + { + tcs.TrySetCanceled(); + } + }, + null); + + return tcs.Task; + } + } + + /// + /// Creates a instance matching the source . + /// + /// The target operation. + /// + public static Task ToTask(this IAsyncOperation op) + { + var status = op.Status; + + if (status == AsyncOperationStatus.RanToCompletion) + { + return Task.FromResult(op.Result); + } + else if (status == AsyncOperationStatus.Faulted) + { + return Task.FromException(op.Exception); + } + else if (status == AsyncOperationStatus.Canceled) + { + return Task.FromCanceled(new CancellationToken(true)); + } + else + { + var tcs = new TaskCompletionSource(); + + op.AddCompletionCallback( + asyncOp => + { + status = op.Status; + + if (status == AsyncOperationStatus.RanToCompletion) + { + tcs.TrySetResult(op.Result); + } + else if (status == AsyncOperationStatus.Faulted) + { + tcs.TrySetException(op.Exception); + } + else if (status == AsyncOperationStatus.Canceled) + { + tcs.TrySetCanceled(); + } + }, + null); + + return tcs.Task; + } + } + +#endif + + #endregion + + #region GetAwaiter/ConfigureAwait + +#if !NET35 + + /// + /// Returns the operation awaiter. This method is intended for compiler use only. + /// + /// The operation to await. + /// An object that can be used to await the operation. + public static CompilerServices.AsyncAwaiter GetAwaiter(this IAsyncOperation op) + { + return new CompilerServices.AsyncAwaiter(op); + } + + /// + /// Returns the operation awaiter. This method is intended for compiler use only. + /// + /// The operation to await. + /// An object that can be used to await the operation. + public static CompilerServices.AsyncAwaiter GetAwaiter(this IAsyncOperation op) + { + return new CompilerServices.AsyncAwaiter(op); + } + + /// + /// Configures an awaiter used to await this operation. + /// + /// The operation to await. + /// If attempts to marshal the continuation back to the original context captured. + /// An object that can be used to await the operation. + public static CompilerServices.AsyncAwaitable ConfigureAwait(this IAsyncOperation op, bool continueOnCapturedContext) + { + return new CompilerServices.AsyncAwaitable(op, continueOnCapturedContext ? SynchronizationContext.Current : null); + } + + /// + /// Configures an awaiter used to await this operation. + /// + /// The operation to await. + /// If attempts to marshal the continuation back to the original context captured. + /// An object that can be used to await the operation. + public static CompilerServices.AsyncAwaitable ConfigureAwait(this IAsyncOperation op, bool continueOnCapturedContext) + { + return new CompilerServices.AsyncAwaitable(op, continueOnCapturedContext ? SynchronizationContext.Current : null); + } + + /// + /// Configures an awaiter used to await this operation. + /// + /// The operation to await. + /// Specifies continuation options. + /// An object that can be used to await the operation. + public static CompilerServices.AsyncAwaitable ConfigureAwait(this IAsyncOperation op, AsyncContinuationContext continuationContext) + { + return new CompilerServices.AsyncAwaitable(op, AsyncResult.GetSynchronizationContext(continuationContext)); + } + + /// + /// Configures an awaiter used to await this operation. + /// + /// The operation to await. + /// Specifies continuation options. + /// An object that can be used to await the operation. + public static CompilerServices.AsyncAwaitable ConfigureAwait(this IAsyncOperation op, AsyncContinuationContext continuationContext) + { + return new CompilerServices.AsyncAwaitable(op, AsyncResult.GetSynchronizationContext(continuationContext)); + } + +#endif + + #endregion + + #endregion + + #region IAsyncOperationEvents + + /// + /// Adds a completion callback to be executed after the operation has completed. If the operation is already completed + /// the is called synchronously. + /// + /// + /// The is invoked on a thread that registered the continuation (if it has a attached). + /// Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The operation to schedule continuation for. + /// The callback to be executed when the operation has completed. + /// Thrown if is . + /// Thrown is the operation has been disposed. + public static void AddCompletionCallback(this IAsyncOperationEvents op, Action callback) + { + op.AddCompletionCallback(callback, SynchronizationContext.Current); + } + + /// + /// Adds a completion callback to be executed after the operation has completed. If the operation is already completed + /// the is invoked on a context specified via . + /// + /// + /// The is invoked on a specified. + /// Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The operation to schedule continuation for. + /// The callback to be executed when the operation has completed. + /// Identifier of a to schedule callback on. + /// Thrown if is . + /// Thrown is the operation has been disposed. + public static void AddCompletionCallback(this IAsyncOperationEvents op, Action callback, AsyncContinuationContext continuationContext) + { + op.AddCompletionCallback(callback, AsyncResult.GetSynchronizationContext(continuationContext)); + } + + /// + /// Adds a completion callback to be executed after the operation has completed. If the operation is already completed + /// the is called synchronously. + /// + /// + /// The is invoked on a thread that registered the continuation (if it has a attached). + /// Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The operation to schedule continuation for. + /// The callback to be executed when the operation has completed. + /// Thrown if is . + /// Thrown is the operation has been disposed. + public static void AddCompletionCallback(this IAsyncOperationEvents op, IAsyncContinuation callback) + { + op.AddCompletionCallback(callback, SynchronizationContext.Current); + } + + /// + /// Adds a completion callback to be executed after the operation has completed. If the operation is already completed + /// the is invoked on a context specified via . + /// + /// + /// The is invoked on a specified. + /// Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The operation to schedule continuation for. + /// The callback to be executed when the operation has completed. + /// Identifier of a to schedule callback on. + /// Thrown if is . + /// Thrown is the operation has been disposed. + public static void AddCompletionCallback(this IAsyncOperationEvents op, IAsyncContinuation callback, AsyncContinuationContext continuationContext) + { + op.AddCompletionCallback(callback, AsyncResult.GetSynchronizationContext(continuationContext)); + } + + /// + /// Adds a callback to be executed when the operation progress has changed. If the operation is already completed + /// the is called synchronously. + /// + /// + /// The is invoked on a thread that registered the callback (if it has a attached). + /// Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The operation to schedule continuation for. + /// The callback to be executed when the operation progress has changed. + /// Thrown if is . + /// Thrown is the operation has been disposed. + public static void AddProgressCallback(this IAsyncOperationEvents op, Action callback) + { + op.AddProgressCallback(callback, SynchronizationContext.Current); + } + + /// + /// Adds a callback to be executed when the operation progress has changed. If the operation is already completed + /// the is invoked on a context specified via . + /// + /// + /// The is invoked on a specified. + /// Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The operation to schedule continuation for. + /// The callback to be executed when the operation progress has changed. + /// Identifier of a to schedule callback on. + /// Thrown if is . + /// Thrown is the operation has been disposed. + public static void AddProgressCallback(this IAsyncOperationEvents op, Action callback, AsyncContinuationContext continuationContext) + { + op.AddProgressCallback(callback, AsyncResult.GetSynchronizationContext(continuationContext)); + } + +#if !NET35 + + /// + /// Adds a callback to be executed when the operation progress has changed. If the operation is already completed + /// the is called synchronously. + /// + /// + /// The is invoked on a thread that registered the callback (if it has a attached). + /// Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The operation to schedule continuation for. + /// The callback to be executed when the operation progress has changed. + /// Thrown if is . + /// Thrown is the operation has been disposed. + public static void AddProgressCallback(this IAsyncOperationEvents op, IProgress callback) + { + op.AddProgressCallback(callback, SynchronizationContext.Current); + } + + /// + /// Adds a callback to be executed when the operation progress has changed. If the operation is already completed + /// the is invoked on a context specified via . + /// + /// + /// The is invoked on a specified. + /// Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The operation to schedule continuation for. + /// The callback to be executed when the operation progress has changed. + /// Identifier of a to schedule callback on. + /// Thrown if is . + /// Thrown is the operation has been disposed. + public static void AddProgressCallback(this IAsyncOperationEvents op, IProgress callback, AsyncContinuationContext continuationContext) + { + op.AddProgressCallback(callback, AsyncResult.GetSynchronizationContext(continuationContext)); + } + +#endif + + #endregion + + #region IAsyncCompletionSource + + /// + /// Sets the operation progress value in range [0, 1]. + /// + /// The completion source instance. + /// The operation progress in range [0, 1]. + /// Thrown if is not in range [0, 1]. + /// Thrown if the progress value cannot be set. + /// Thrown is the operation is disposed. + /// + public static void SetProgress(this IAsyncCompletionSource completionSource, float progress) + { + if (!completionSource.TrySetProgress(progress)) + { + throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); + } + } + + /// + /// Sets the operation progress value in range [0, 1]. + /// + /// The completion source instance. + /// The operation progress in range [0, 1]. + /// Thrown if is not in range [0, 1]. + /// Thrown if the progress value cannot be set. + /// Thrown is the operation is disposed. + /// + public static void SetProgress(this IAsyncCompletionSource completionSource, float progress) + { + if (!completionSource.TrySetProgress(progress)) + { + throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); + } + } + + /// + /// Transitions the underlying into the state. + /// + /// The completion source instance. + /// Thrown if the transition fails. + /// Thrown is the operation is disposed. + /// + /// + /// + public static void SetCanceled(this IAsyncCompletionSource completionSource) + { + if (!completionSource.TrySetCanceled()) + { + throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); + } + } + + /// + /// Transitions the underlying into the state. + /// + /// The completion source instance. + /// Thrown if the transition fails. + /// Thrown is the operation is disposed. + /// + /// + /// + public static void SetCanceled(this IAsyncCompletionSource completionSource) + { + if (!completionSource.TrySetCanceled()) + { + throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); + } + } + + /// + /// Attempts to transition the underlying into the state. + /// + /// The completion source instance. + /// An exception message. + /// Thrown if is . + /// Thrown is the operation is disposed. + /// Returns if the attemp was successfull; otherwise. + /// + /// + /// + public static bool TrySetException(this IAsyncCompletionSource completionSource, string message) + { + return completionSource.TrySetException(new Exception(message)); + } + + /// + /// Attempts to transition the underlying into the state. + /// + /// The completion source instance. + /// An exception message. + /// Thrown if is . + /// Thrown is the operation is disposed. + /// Returns if the attemp was successfull; otherwise. + /// + /// + /// + public static bool TrySetException(this IAsyncCompletionSource completionSource, string message) + { + return completionSource.TrySetException(new Exception(message)); + } + + /// + /// Transitions the underlying into the state. + /// + /// The completion source instance. + /// An exception message. + /// Thrown if is . + /// Thrown if the transition fails. + /// Thrown is the operation is disposed. + /// + /// + /// + public static void SetException(this IAsyncCompletionSource completionSource, string message) + { + if (!completionSource.TrySetException(new Exception(message))) + { + throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); + } + } + + /// + /// Transitions the underlying into the state. + /// + /// The completion source instance. + /// An exception message. + /// Thrown if is . + /// Thrown if the transition fails. + /// Thrown is the operation is disposed. + /// + /// + /// + public static void SetException(this IAsyncCompletionSource completionSource, string message) + { + if (!completionSource.TrySetException(new Exception(message))) + { + throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); + } + } + + /// + /// Transitions the underlying into the state. + /// + /// The completion source instance. + /// An exception that caused the operation to end prematurely. + /// Thrown if is . + /// Thrown if the transition fails. + /// Thrown is the operation is disposed. + /// + /// + /// + public static void SetException(this IAsyncCompletionSource completionSource, Exception exception) + { + if (!completionSource.TrySetException(exception)) + { + throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); + } + } + + /// + /// Transitions the underlying into the state. + /// + /// The completion source instance. + /// An exception that caused the operation to end prematurely. + /// Thrown if is . + /// Thrown if the transition fails. + /// Thrown is the operation is disposed. + /// + /// + /// + public static void SetException(this IAsyncCompletionSource completionSource, Exception exception) + { + if (!completionSource.TrySetException(exception)) + { + throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); + } + } + + /// + /// Transitions the underlying into the state. + /// + /// The completion source instance. + /// Exceptions that caused the operation to end prematurely. + /// Thrown if is . + /// Thrown if the transition fails. + /// Thrown is the operation is disposed. + /// + /// + /// + public static void SetExceptions(this IAsyncCompletionSource completionSource, IEnumerable exceptions) + { + if (!completionSource.TrySetExceptions(exceptions)) + { + throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); + } + } + + /// + /// Transitions the underlying into the state. + /// + /// The completion source instance. + /// Exceptions that caused the operation to end prematurely. + /// Thrown if is . + /// Thrown if the transition fails. + /// Thrown is the operation is disposed. + /// + /// + /// + public static void SetExceptions(this IAsyncCompletionSource completionSource, IEnumerable exceptions) + { + if (!completionSource.TrySetExceptions(exceptions)) + { + throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); + } + } + + /// + /// Transitions the underlying into the state. + /// + /// The completion source instance. + /// Thrown if the transition fails. + /// Thrown is the operation is disposed. + /// + /// + /// + public static void SetCompleted(this IAsyncCompletionSource completionSource) + { + if (!completionSource.TrySetCompleted()) + { + throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); + } + } + + /// + /// Transitions the underlying into the state. + /// + /// The completion source instance. + /// The operation result. + /// Thrown if the transition fails. + /// Thrown is the operation is disposed. + /// + /// + /// + public static void SetResult(this IAsyncCompletionSource completionSource, TResult result) + { + if (!completionSource.TrySetResult(result)) + { + throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); + } + } + + #endregion + + #region implementation + +#if !NET35 + + private static void WaitInternal(IAsyncOperation op, CancellationToken cancellationToken) + { + if (!op.IsCompleted) + { + if (cancellationToken.CanBeCanceled) + { + cancellationToken.ThrowIfCancellationRequested(); + + var index = WaitHandle.WaitAny(new WaitHandle[] { op.AsyncWaitHandle, cancellationToken.WaitHandle }); + + if (index == 1) + { + throw new OperationCanceledException(); + } + } + else + { + op.AsyncWaitHandle.WaitOne(); + } + } + } + + private static bool WaitInternal(IAsyncOperation op, int millisecondsTimeout, CancellationToken cancellationToken) + { + var result = true; + + if (!op.IsCompleted) + { + if (cancellationToken.CanBeCanceled) + { + cancellationToken.ThrowIfCancellationRequested(); + + var index = WaitHandle.WaitAny(new WaitHandle[] { op.AsyncWaitHandle, cancellationToken.WaitHandle }, millisecondsTimeout); + + if (index == WaitHandle.WaitTimeout) + { + result = false; + } + else if (index == 1) + { + throw new OperationCanceledException(); + } + } + else + { + result = op.AsyncWaitHandle.WaitOne(millisecondsTimeout); + } + } + + return result; + } + + private static bool WaitInternal(IAsyncOperation op, TimeSpan timeout, CancellationToken cancellationToken) + { + var result = true; + + if (!op.IsCompleted) + { + if (cancellationToken.CanBeCanceled) + { + cancellationToken.ThrowIfCancellationRequested(); + + var index = WaitHandle.WaitAny(new WaitHandle[] { op.AsyncWaitHandle, cancellationToken.WaitHandle }, timeout); + + if (index == WaitHandle.WaitTimeout) + { + result = false; + } + else if (index == 1) + { + throw new OperationCanceledException(); + } + } + else + { + result = op.AsyncWaitHandle.WaitOne(timeout); + } + } + + return result; + } + +#endif + + #endregion + } +} diff --git a/src/UnityFx.Async/Implementation/Public/AsyncResult.Events.cs b/src/UnityFx.Async/Api/AsyncResult.Events.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Public/AsyncResult.Events.cs rename to src/UnityFx.Async/Api/AsyncResult.Events.cs diff --git a/src/UnityFx.Async/Implementation/Public/AsyncResult.Helpers.cs b/src/UnityFx.Async/Api/AsyncResult.Helpers.cs similarity index 97% rename from src/UnityFx.Async/Implementation/Public/AsyncResult.Helpers.cs rename to src/UnityFx.Async/Api/AsyncResult.Helpers.cs index a33adc3..e2c4373 100644 --- a/src/UnityFx.Async/Implementation/Public/AsyncResult.Helpers.cs +++ b/src/UnityFx.Async/Api/AsyncResult.Helpers.cs @@ -412,7 +412,7 @@ public static AsyncResult FromAction(Action action, T state) } catch (Exception e) { - return new AsyncResult(e, state); + return new AsyncResult(e, null); } } @@ -440,7 +440,34 @@ public static AsyncResult FromAction(SendOrPostCallback callback, object state) } catch (Exception e) { - return new AsyncResult(e, state); + return new AsyncResult(e, null); + } + } + + /// + /// Creates a completed that represents result of the specified. + /// + /// The delegate to execute. + /// Arguments of the . + /// Thrown if is . + /// A completed operation that represents the result. + /// + /// + public static AsyncResult FromAction(Delegate callback, object[] args) + { + if (callback == null) + { + throw new ArgumentNullException(nameof(callback)); + } + + try + { + var result = callback.DynamicInvoke(args); + return new AsyncResult(result, null); + } + catch (Exception e) + { + return new AsyncResult(e, null); } } @@ -489,11 +516,11 @@ public static AsyncResult FromAction(Func actio try { var result = action(state); - return new AsyncResult(result, state); + return new AsyncResult(result, null); } catch (Exception e) { - return new AsyncResult(e, state); + return new AsyncResult(e, null); } } @@ -513,22 +540,22 @@ public static AsyncResult FromTask(Task task) throw new ArgumentNullException(nameof(task)); } - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + var result = new AsyncResult(AsyncOperationStatus.Running); task.ContinueWith( t => { if (t.IsFaulted) { - result.SetException(t.Exception); + result.TrySetException(t.Exception); } else if (t.IsCanceled) { - result.SetCanceled(); + result.TrySetCanceled(); } else { - result.SetCompleted(); + result.TrySetCompleted(); } }, TaskContinuationOptions.ExecuteSynchronously); @@ -550,22 +577,22 @@ public static AsyncResult FromTask(Task task) throw new ArgumentNullException(nameof(task)); } - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + var result = new AsyncResult(AsyncOperationStatus.Running); task.ContinueWith( t => { if (t.IsFaulted) { - result.SetException(t.Exception); + result.TrySetException(t.Exception); } else if (t.IsCanceled) { - result.SetCanceled(); + result.TrySetCanceled(); } else { - result.SetResult(t.Result); + result.TrySetResult(t.Result); } }, TaskContinuationOptions.ExecuteSynchronously); @@ -623,7 +650,7 @@ public static AsyncResult FromObservable(IObservable observable) throw new ArgumentNullException(nameof(observable)); } - return new AsyncObservableResult(observable); + return new FromObservableResult(observable); } #endif diff --git a/src/UnityFx.Async/Implementation/Public/AsyncResult.cs b/src/UnityFx.Async/Api/AsyncResult.cs similarity index 97% rename from src/UnityFx.Async/Implementation/Public/AsyncResult.cs rename to src/UnityFx.Async/Api/AsyncResult.cs index a3926ad..055fb6e 100644 --- a/src/UnityFx.Async/Implementation/Public/AsyncResult.cs +++ b/src/UnityFx.Async/Api/AsyncResult.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Globalization; using System.Linq; +using System.Runtime.CompilerServices; #if !NET35 using System.Runtime.ExceptionServices; #endif @@ -38,6 +39,9 @@ namespace UnityFx.Async /// /// [DebuggerDisplay("{DebuggerDisplay,nq}")] +#if !NET35 + [AsyncMethodBuilder(typeof(CompilerServices.AsyncResultMethodBuilder))] +#endif public partial class AsyncResult : IAsyncOperation, IAsyncContinuation, IEnumerator { #region data @@ -396,7 +400,7 @@ protected internal bool TrySetCanceled(bool completedSynchronously) } else if (!IsCompleted) { - AsyncExtensions.SpinUntilCompleted(this); + SpinUntilCompleted(); } return false; @@ -497,7 +501,7 @@ protected internal bool TrySetException(Exception exception, bool completedSynch } else if (!IsCompleted) { - AsyncExtensions.SpinUntilCompleted(this); + SpinUntilCompleted(); } return false; @@ -587,7 +591,7 @@ protected internal bool TrySetExceptions(IEnumerable exceptions, bool } else if (!IsCompleted) { - AsyncExtensions.SpinUntilCompleted(this); + SpinUntilCompleted(); } return false; @@ -621,7 +625,7 @@ protected internal bool TrySetCompleted(bool completedSynchronously) } else if (!IsCompleted) { - AsyncExtensions.SpinUntilCompleted(this); + SpinUntilCompleted(); } return false; @@ -721,6 +725,9 @@ protected virtual float GetProgress() /// /// Called when the progress value has changed. Default implementation does nothing. /// + /// + /// Throwing an exception in this method results in unspecified behaviour. + /// /// /// /// @@ -732,6 +739,9 @@ protected virtual void OnProgressChanged() /// Called when the operation state has changed. Default implementation does nothing. /// /// The new status value. + /// + /// Throwing an exception in this method results in unspecified behaviour. + /// /// /// /// @@ -766,6 +776,9 @@ protected virtual void OnCancel() /// /// Called when the operation is completed. Default implementation does nothing. /// + /// + /// Throwing an exception in this method results in unspecified behaviour. + /// /// /// /// @@ -780,7 +793,7 @@ protected virtual void OnCompleted() /// Releases unmanaged resources used by the object. /// /// - /// Unlike most of the members of , this method is not thread-safe. + /// Unlike most of the members of , this method is not thread-safe. Do not throw exceptions in . /// /// A value that indicates whether this method is being called due to a call to . /// @@ -1296,9 +1309,9 @@ private string DebuggerDisplay { result += " (" + ((int)(GetProgress() * 100)).ToString(CultureInfo.InvariantCulture) + "%)"; } - else if ((status == AsyncOperationStatus.Faulted || status == AsyncOperationStatus.Canceled) && _exception != null) + else if (status == AsyncOperationStatus.Faulted || status == AsyncOperationStatus.Canceled) { - result += " (" + _exception.GetType().Name + ')'; + result += " (" + (_exception != null ? _exception.GetType().Name : "null") + ')'; } if (IsDisposed) @@ -1312,16 +1325,18 @@ private string DebuggerDisplay private AsyncResult(int flags) { - if (flags == StatusFaulted) + var status = flags & _statusMask; + + if (status == StatusFaulted) { _exception = new Exception(); } - else if (flags == StatusCanceled) + else if (status == StatusCanceled) { _exception = new OperationCanceledException(); } - if ((flags & _statusMask) > StatusRunning) + if (status > StatusRunning) { _callback = _callbackCompletionSentinel; _flags = flags | _flagCompletedSynchronously; @@ -1339,15 +1354,35 @@ private void NotifyCompleted(AsyncOperationStatus status) OnProgressChanged(); OnStatusChanged(status); OnCompleted(); - - InvokeCallbacks(); } finally { _waitHandle?.Set(); + InvokeCallbacks(); } } + private void SpinUntilCompleted() + { +#if NET35 + + while ((_flags & _flagCompleted) == 0) + { + Thread.SpinWait(1); + } + +#else + + var sw = new SpinWait(); + + while ((_flags & _flagCompleted) == 0) + { + sw.SpinOnce(); + } + +#endif + } + #endregion } } diff --git a/src/UnityFx.Async/Implementation/Public/AsyncResult{TResult}.cs b/src/UnityFx.Async/Api/AsyncResult{TResult}.cs similarity index 98% rename from src/UnityFx.Async/Implementation/Public/AsyncResult{TResult}.cs rename to src/UnityFx.Async/Api/AsyncResult{TResult}.cs index fccb6bb..6852b28 100644 --- a/src/UnityFx.Async/Implementation/Public/AsyncResult{TResult}.cs +++ b/src/UnityFx.Async/Api/AsyncResult{TResult}.cs @@ -17,6 +17,9 @@ namespace UnityFx.Async /// Type of the operation result value. /// /// +#if !NET35 + [AsyncMethodBuilder(typeof(CompilerServices.AsyncResultMethodBuilder<>))] +#endif public class AsyncResult : AsyncResult, IAsyncOperation { #region data @@ -308,7 +311,7 @@ public IDisposable Subscribe(IObserver observer) throw new ArgumentNullException(nameof(observer)); } - var result = new AsyncObservableSubscription(this, observer); + var result = new ObservableSubscription(this, observer); AddCompletionCallback(result, null); return result; } diff --git a/src/UnityFx.Async/Implementation/Public/Helpers/AsyncUpdateSource.cs b/src/UnityFx.Async/Api/AsyncUpdateSource.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Public/Helpers/AsyncUpdateSource.cs rename to src/UnityFx.Async/Api/AsyncUpdateSource.cs diff --git a/src/UnityFx.Async/Implementation/Public/CompilerServices/AsyncAwaitable.cs b/src/UnityFx.Async/Api/CompilerServices/AsyncAwaitable.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Public/CompilerServices/AsyncAwaitable.cs rename to src/UnityFx.Async/Api/CompilerServices/AsyncAwaitable.cs diff --git a/src/UnityFx.Async/Implementation/Public/CompilerServices/AsyncAwaitable{TResult}.cs b/src/UnityFx.Async/Api/CompilerServices/AsyncAwaitable{TResult}.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Public/CompilerServices/AsyncAwaitable{TResult}.cs rename to src/UnityFx.Async/Api/CompilerServices/AsyncAwaitable{TResult}.cs diff --git a/src/UnityFx.Async/Implementation/Public/CompilerServices/AsyncAwaiter.cs b/src/UnityFx.Async/Api/CompilerServices/AsyncAwaiter.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Public/CompilerServices/AsyncAwaiter.cs rename to src/UnityFx.Async/Api/CompilerServices/AsyncAwaiter.cs diff --git a/src/UnityFx.Async/Implementation/Public/CompilerServices/AsyncAwaiter{TResult}.cs b/src/UnityFx.Async/Api/CompilerServices/AsyncAwaiter{TResult}.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Public/CompilerServices/AsyncAwaiter{TResult}.cs rename to src/UnityFx.Async/Api/CompilerServices/AsyncAwaiter{TResult}.cs diff --git a/src/UnityFx.Async/Api/CompilerServices/AsyncMethodBuilderAttribute.cs b/src/UnityFx.Async/Api/CompilerServices/AsyncMethodBuilderAttribute.cs new file mode 100644 index 0000000..dbae5df --- /dev/null +++ b/src/UnityFx.Async/Api/CompilerServices/AsyncMethodBuilderAttribute.cs @@ -0,0 +1,32 @@ +// Copyright (c) Alexander Bogarsukov. +// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; + +namespace System.Runtime.CompilerServices +{ +#if !NET35 + + /// + /// Indicates the type of the async method builder that should be used by a language compiler to + /// build the attributed type when used as the return type of an async method. + /// + /// + public sealed class AsyncMethodBuilderAttribute : Attribute + { + /// + /// Gets the of the associated builder. + /// + public Type BuilderType { get; } + + /// + /// Initializes a new instance of the class. + /// + public AsyncMethodBuilderAttribute(Type builderType) + { + BuilderType = builderType; + } + } + +#endif +} diff --git a/src/UnityFx.Async/Api/CompilerServices/AsyncResultMethodBuilder.cs b/src/UnityFx.Async/Api/CompilerServices/AsyncResultMethodBuilder.cs new file mode 100644 index 0000000..9400a14 --- /dev/null +++ b/src/UnityFx.Async/Api/CompilerServices/AsyncResultMethodBuilder.cs @@ -0,0 +1,208 @@ +// Copyright (c) Alexander Bogarsukov. +// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace UnityFx.Async.CompilerServices +{ +#if !NET35 + + /// + /// Provides a builder for asynchronous methods that return . This type is intended for compiler use only. + /// + /// + /// is a value type, and thus it is copied by value. Prior to being copied, + /// one of its , , or members must be accessed, + /// or else the copies may end up building distinct instances. + /// + /// + public struct AsyncResultMethodBuilder + { + #region data + + private AsyncResult _op; + private Action _continuation; + + #endregion + + #region interface + + /// + /// Gets the for this builder. + /// + /// The representing the builder's asynchronous operation. + /// The builder is not initialized. + [DebuggerHidden] + public AsyncResult Task + { + get + { + // Is this code really needed? _op should always be initialized when this is called. + if (_op == null) + { + _op = new AsyncResult(); + } + + return _op; + } + } + + /// + /// Completes the in the state. + /// + /// The builder is not initialized. + /// The operation has already completed. + /// + [DebuggerHidden] + public void SetResult() + { + if (_op == null) + { + _op = AsyncResult.CompletedOperation; + } + else if (!_op.TrySetCompleted()) + { + throw new InvalidOperationException(); + } + } + + /// + /// Completes the in the state with the specified . + /// + /// The to use to fault the operation. + /// The is . + /// The builder is not initialized. + /// The operation has already completed. + /// + [DebuggerHidden] + public void SetException(Exception exception) + { + if (_op == null) + { + _op = AsyncResult.FromException(exception); + } + else if (!_op.TrySetException(exception)) + { + throw new InvalidOperationException(); + } + } + + /// + /// Initiates the builder's execution with the associated state machine. + /// + /// Specifies the type of the state machine. + /// The state machine instance, passed by reference. + [DebuggerHidden] + public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine + { + stateMachine.MoveNext(); + } + + /// + /// Associates the builder with the state machine it represents. + /// + /// The heap-allocated state machine object. + [DebuggerHidden] + public void SetStateMachine(IAsyncStateMachine stateMachine) + { + } + + /// + /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. + /// + /// Specifies the type of the awaiter. + /// Specifies the type of the state machine. + /// The awaiter passed by reference. + /// The state machine passed by reference. + /// + [DebuggerHidden] + public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine + { + if (_continuation == null) + { + OnFirstAwait(ref stateMachine); + } + + awaiter.OnCompleted(_continuation); + } + + /// + /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. + /// + /// Specifies the type of the awaiter. + /// Specifies the type of the state machine. + /// The awaiter passed by reference. + /// The state machine passed by reference. + /// + [DebuggerHidden] + public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine + { + if (_continuation == null) + { + OnFirstAwait(ref stateMachine); + } + + awaiter.UnsafeOnCompleted(_continuation); + } + + /// + /// Initializes a new . + /// + /// The initialized . + [DebuggerHidden] + public static AsyncResultMethodBuilder Create() + { + return default(AsyncResultMethodBuilder); + } + + #endregion + + #region implementation + + private class MoveNextRunner : AsyncResult where TStateMachine : IAsyncStateMachine + { + private TStateMachine _stateMachine; + + public MoveNextRunner() + : base(AsyncOperationStatus.Running) + { + } + + public void SetStateMachine(ref TStateMachine stateMachine) + { + _stateMachine = stateMachine; + } + + [DebuggerHidden] + public void Run() + { + Debug.Assert(!IsCompleted); + _stateMachine.MoveNext(); + } + } + + /// + /// First await handler. Boxes the and initializes continuation action. + /// + /// Type of the state machine instance. + /// The parent state machine. + private void OnFirstAwait(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine + { + // MoveNextRunner acts as a heap-allocated shell of the state machine + task. + var runner = new MoveNextRunner(); + + // Init all members references so they are shared between this instance and the boxed one. + _continuation = runner.Run; + _op = new AsyncResult(AsyncOperationStatus.Running); + + // Copy the state machine into the runner (boxing). This should be done after _continuation and _op is initialized. + runner.SetStateMachine(ref stateMachine); + } + + #endregion + } + +#endif +} diff --git a/src/UnityFx.Async/Api/CompilerServices/AsyncResultMethodBuilder{TResult}.cs b/src/UnityFx.Async/Api/CompilerServices/AsyncResultMethodBuilder{TResult}.cs new file mode 100644 index 0000000..ec5bc38 --- /dev/null +++ b/src/UnityFx.Async/Api/CompilerServices/AsyncResultMethodBuilder{TResult}.cs @@ -0,0 +1,214 @@ +// Copyright (c) Alexander Bogarsukov. +// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace UnityFx.Async.CompilerServices +{ +#if !NET35 + + /// + /// Provides a builder for asynchronous methods that return . This type is intended for compiler use only. + /// + /// + /// is a value type, and thus it is copied by value. Prior to being copied, + /// one of its , , or members must be accessed, + /// or else the copies may end up building distinct instances. + /// + /// + public struct AsyncResultMethodBuilder + { + #region data + + private AsyncResult _op; + private Action _continuation; + + #endregion + + #region interface + + /// + /// Gets the for this builder. + /// + /// The representing the builder's asynchronous operation. + /// The builder is not initialized. + [DebuggerHidden] + public AsyncResult Task + { + get + { + if (_op == null) + { + _op = new AsyncResult(); + } + + return _op; + } + } + + /// + /// Completes the in the state. + /// + /// The result to use to complete the operation. + /// The builder is not initialized. + /// The operation has already completed. + [DebuggerHidden] + public void SetResult(TResult result) + { + if (_op == null) + { + _op = GetTaskForResult(result); + } + else if (!_op.TrySetResult(result)) + { + throw new InvalidOperationException(); + } + } + + /// + /// Completes the in the state with the specified . + /// + /// The to use to fault the operation. + /// The is . + /// The builder is not initialized. + /// The operation has already completed. + [DebuggerHidden] + public void SetException(Exception exception) + { + if (_op == null) + { + _op = AsyncResult.FromException(exception); + } + else if (!_op.TrySetException(exception)) + { + throw new InvalidOperationException(); + } + } + + /// + /// Initiates the builder's execution with the associated state machine. + /// + /// Specifies the type of the state machine. + /// The state machine instance, passed by reference. + [DebuggerHidden] + public void Start(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine + { + stateMachine.MoveNext(); + } + + /// + /// Associates the builder with the state machine it represents. + /// + /// The heap-allocated state machine object. + [DebuggerHidden] + public void SetStateMachine(IAsyncStateMachine stateMachine) + { + } + + /// + /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. + /// + /// Specifies the type of the awaiter. + /// Specifies the type of the state machine. + /// The awaiter passed by reference. + /// The state machine passed by reference. + [DebuggerHidden] + public void AwaitOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : INotifyCompletion where TStateMachine : IAsyncStateMachine + { + if (_continuation == null) + { + OnFirstAwait(ref stateMachine); + } + + awaiter.OnCompleted(_continuation); + } + + /// + /// Schedules the specified state machine to be pushed forward when the specified awaiter completes. + /// + /// Specifies the type of the awaiter. + /// Specifies the type of the state machine. + /// The awaiter passed by reference. + /// The state machine passed by reference. + [DebuggerHidden] + public void AwaitUnsafeOnCompleted(ref TAwaiter awaiter, ref TStateMachine stateMachine) where TAwaiter : ICriticalNotifyCompletion where TStateMachine : IAsyncStateMachine + { + if (_continuation == null) + { + OnFirstAwait(ref stateMachine); + } + + awaiter.UnsafeOnCompleted(_continuation); + } + + /// + /// Initializes a new . + /// + /// The initialized . + [DebuggerHidden] + public static AsyncResultMethodBuilder Create() + { + return default(AsyncResultMethodBuilder); + } + + #endregion + + #region implementation + + private class MoveNextRunner : AsyncResult where TStateMachine : IAsyncStateMachine + { + private TStateMachine _stateMachine; + + public MoveNextRunner() + : base(AsyncOperationStatus.Running) + { + } + + public void SetStateMachine(ref TStateMachine stateMachine) + { + _stateMachine = stateMachine; + } + + [DebuggerHidden] + public void Run() + { + Debug.Assert(!IsCompleted); + _stateMachine.MoveNext(); + } + } + + /// + /// First await handler. Boxes the and initializes continuation action. + /// + /// Type of the state machine instance. + /// The parent state machine. + private void OnFirstAwait(ref TStateMachine stateMachine) where TStateMachine : IAsyncStateMachine + { + // MoveNextRunner acts as a heap-allocated shell of the state machine + task. + var runner = new MoveNextRunner(); + + // Init all members references so they are shared between this instance and the boxed one. + _continuation = runner.Run; + _op = runner; + + // Copy the state machine into the runner (boxing). This should be done after _continuation and _op is initialized. + runner.SetStateMachine(ref stateMachine); + } + + /// + /// Gets a task matching the result value specified. + /// + /// The result value. + /// The completed task. + private AsyncResult GetTaskForResult(TResult result) + { + return AsyncResult.FromResult(result); + } + + #endregion + } + +#endif +} diff --git a/src/UnityFx.Async/Api/Extensions/IAsyncResultExtensions.cs b/src/UnityFx.Async/Api/Extensions/IAsyncResultExtensions.cs new file mode 100644 index 0000000..0c9fb91 --- /dev/null +++ b/src/UnityFx.Async/Api/Extensions/IAsyncResultExtensions.cs @@ -0,0 +1,255 @@ +// Copyright (c) Alexander Bogarsukov. +// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; +using System.Collections; +using System.ComponentModel; +using System.Threading; + +namespace UnityFx.Async.Extensions +{ + /// + /// Extension methods for . + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static class IAsyncResultExtensions + { + #region interface + + /// + /// Creates an that completes when the specified operation completes. + /// + /// The operation to convert to enumerator. + /// An enumerator that represents the operation. + public static IEnumerator ToEnum(this IAsyncResult op) + { + if (op is IEnumerator e) + { + return e; + } + + return new TaskEnumerator(op); + } + + /// + /// Spins until the operation has completed. + /// + /// The operation to wait for. + public static void SpinUntilCompleted(this IAsyncResult op) + { +#if NET35 + + while (!op.IsCompleted) + { + Thread.SpinWait(1); + } + +#else + + var sw = new SpinWait(); + + while (!op.IsCompleted) + { + sw.SpinOnce(); + } + +#endif + } + + /// + /// Spins until the operation has completed within a specified timeout. + /// + /// The operation to wait for. + /// The number of milliseconds to wait, or (-1) to wait indefinitely. + /// is a negative number other than -1. + /// Returns if the operation was completed within the specified time interfval; otherwise. + public static bool SpinUntilCompleted(this IAsyncResult op, int millisecondsTimeout) + { + if (millisecondsTimeout < -1) + { + throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout), millisecondsTimeout, Messages.FormatError_InvalidTimeout()); + } + + if (millisecondsTimeout == Timeout.Infinite) + { + SpinUntilCompleted(op); + return true; + } + + return SpinUntilCompletedInternal(op, TimeSpan.FromMilliseconds(millisecondsTimeout)); + } + + /// + /// Spins until the operation has completed within a specified timeout. + /// + /// The operation to wait for. + /// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. + /// is a negative number other than -1 milliseconds, or is greater than . + /// Returns if the operation was completed within the specified time interfval; otherwise. + public static bool SpinUntilCompleted(this IAsyncResult op, TimeSpan timeout) + { + var totalMilliseconds = (long)timeout.TotalMilliseconds; + + if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(timeout), timeout, Messages.FormatError_InvalidTimeout()); + } + + if (totalMilliseconds == Timeout.Infinite) + { + SpinUntilCompleted(op); + return true; + } + + return SpinUntilCompletedInternal(op, timeout); + } + +#if !NET35 + + /// + /// Spins until the operation has completed or until canceled. + /// + /// The operation to wait for. + /// A cancellation token that can be used to cancel wait operation. + /// The was canceled. + /// + public static void SpinUntilCompleted(this IAsyncResult op, CancellationToken cancellationToken) + { + var sw = new SpinWait(); + + while (!op.IsCompleted) + { + cancellationToken.ThrowIfCancellationRequested(); + sw.SpinOnce(); + } + } + + /// + /// Spins until the operation has completed within a specified timeout or until canceled. + /// + /// The operation to wait for. + /// The number of milliseconds to wait, or (-1) to wait indefinitely. + /// A cancellation token that can be used to cancel wait operation. + /// is a negative number other than -1. + /// The was canceled. + /// Returns if the operation was completed within the specified time interfval; otherwise. + public static bool SpinUntilCompleted(this IAsyncResult op, int millisecondsTimeout, CancellationToken cancellationToken) + { + if (millisecondsTimeout < -1) + { + throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout), millisecondsTimeout, Messages.FormatError_InvalidTimeout()); + } + + if (millisecondsTimeout == Timeout.Infinite) + { + SpinUntilCompleted(op, cancellationToken); + return true; + } + + return SpinUntilCompletedInternal(op, TimeSpan.FromMilliseconds(millisecondsTimeout), cancellationToken); + } + + /// + /// Spins until the operation has completed within a specified timeout or until canceled. + /// + /// The operation to wait for. + /// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. + /// A cancellation token that can be used to cancel wait operation. + /// is a negative number other than -1 milliseconds, or is greater than . + /// The was canceled. + /// Returns if the operation was completed within the specified time interfval; otherwise. + public static bool SpinUntilCompleted(this IAsyncResult op, TimeSpan timeout, CancellationToken cancellationToken) + { + var totalMilliseconds = (long)timeout.TotalMilliseconds; + + if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(timeout), timeout, Messages.FormatError_InvalidTimeout()); + } + + if (totalMilliseconds == Timeout.Infinite) + { + SpinUntilCompleted(op, cancellationToken); + return true; + } + + return SpinUntilCompletedInternal(op, timeout, cancellationToken); + } + +#endif + + #endregion + + #region implementation + + private class TaskEnumerator : IEnumerator + { + private readonly IAsyncResult _op; + + public TaskEnumerator(IAsyncResult task) => _op = task; + public object Current => null; + public bool MoveNext() => !_op.IsCompleted; + public void Reset() => throw new NotSupportedException(); + } + +#if !NET35 + + private static bool SpinUntilCompletedInternal(IAsyncResult op, TimeSpan timeout, CancellationToken cancellationToken) + { + var endTime = DateTime.Now + timeout; + var sw = new SpinWait(); + + while (!op.IsCompleted) + { + if (DateTime.Now > endTime) + { + return false; + } + + cancellationToken.ThrowIfCancellationRequested(); + sw.SpinOnce(); + } + + return true; + } + +#endif + + private static bool SpinUntilCompletedInternal(IAsyncResult op, TimeSpan timeout) + { + var endTime = DateTime.Now + timeout; + +#if NET35 + + while (!op.IsCompleted) + { + if (DateTime.Now > endTime) + { + return false; + } + + Thread.SpinWait(1); + } + +#else + + var sw = new SpinWait(); + + while (!op.IsCompleted) + { + if (DateTime.Now > endTime) + { + return false; + } + + sw.SpinOnce(); + } + +#endif + + return true; + } + + #endregion + } +} diff --git a/src/UnityFx.Async/Implementation/Public/Extensions/AsyncExtensions.Observables.cs b/src/UnityFx.Async/Api/Extensions/IObservableExtensions.cs similarity index 69% rename from src/UnityFx.Async/Implementation/Public/Extensions/AsyncExtensions.Observables.cs rename to src/UnityFx.Async/Api/Extensions/IObservableExtensions.cs index 5bc6a72..a59c8db 100644 --- a/src/UnityFx.Async/Implementation/Public/Extensions/AsyncExtensions.Observables.cs +++ b/src/UnityFx.Async/Api/Extensions/IObservableExtensions.cs @@ -2,12 +2,17 @@ // Licensed under the MIT license. See the LICENSE.md file in the project root for more information. using System; +using System.ComponentModel; -namespace UnityFx.Async +namespace UnityFx.Async.Extensions { #if !NET35 - partial class AsyncExtensions + /// + /// Extension methods for . + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static class IObservableExtensions { /// /// Creates a instance that can be used to track the source observable. @@ -17,7 +22,7 @@ partial class AsyncExtensions /// Returns an instance that can be used to track the observable. public static IAsyncOperation ToAsync(this IObservable observable) { - return new AsyncObservableResult(observable); + return new FromObservableResult(observable); } } diff --git a/src/UnityFx.Async/Api/Extensions/SocketExtensions.cs b/src/UnityFx.Async/Api/Extensions/SocketExtensions.cs new file mode 100644 index 0000000..e55b02d --- /dev/null +++ b/src/UnityFx.Async/Api/Extensions/SocketExtensions.cs @@ -0,0 +1,366 @@ +// Copyright (c) Alexander Bogarsukov. +// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Net; +using System.Net.Sockets; + +namespace UnityFx.Async.Extensions +{ + /// + /// Extension methods for class. + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static class SocketExtensions + { + #region interface + + /// + /// Begins an asynchronous operation to accept an incoming connection attempt from a specified socket and + /// receives the first block of data sent by the client application. + /// + /// The target socket. + /// The accepted object. This value may be . + /// The maximum number of bytes to receive. + /// An error occurred when attempting to access the . + /// Thrown if the has been closed. + /// Returns representing the asynchronous operation. + public static IAsyncOperation AcceptAsync(this Socket socket, Socket acceptSocket, int receiveSize) + { + var op = new ApmResult(socket); + socket.BeginAccept(acceptSocket, receiveSize, OnAcceptCompleted, op); + return op; + } + + /// + /// Begins an asynchronous operation to accept an incoming connection attempt and receives the first block of data + /// sent by the client application. + /// + /// The target socket. + /// The maximum number of bytes to receive. + /// An error occurred when attempting to access the . + /// Thrown if the has been closed. + /// Returns representing the asynchronous operation. + public static IAsyncOperation AcceptAsync(this Socket socket, int receiveSize) + { + var op = new ApmResult(socket); + socket.BeginAccept(receiveSize, OnAcceptCompleted, op); + return op; + } + + /// + /// Begins an asynchronous operation to accept an incoming connection attempt. + /// + /// The target socket. + /// An error occurred when attempting to access the . + /// Thrown if the has been closed. + /// Returns representing the asynchronous operation. + public static IAsyncOperation AcceptAsync(this Socket socket) + { + var op = new ApmResult(socket); + socket.BeginAccept(OnAcceptCompleted, op); + return op; + } + + /// + /// Begins an asynchronous request for a remote host connection. + /// + /// The target socket. + /// An that represents the remote host. + /// Thrown if is . + /// The is listening. + /// An error occurred when attempting to access the . + /// Thrown if the has been closed. + /// Returns representing the asynchronous operation. + public static IAsyncOperation ConnectAsync(this Socket socket, EndPoint remoteEP) + { + var op = new ApmResult(socket); + socket.BeginConnect(remoteEP, OnConnectCompleted, op); + return op; + } + + /// + /// Begins an asynchronous request for a remote host connection. The host is specified by an and a port number. + /// + /// The target socket. + /// The of the remote host. + /// The port number of the remote host. + /// Thrown if is . + /// Thrown if number is invalid. + /// The is listening. + /// An error occurred when attempting to access the . + /// Thrown if the has been closed. + /// Returns representing the asynchronous operation. + public static IAsyncOperation ConnectAsync(this Socket socket, IPAddress address, int port) + { + var op = new ApmResult(socket); + socket.BeginConnect(address, port, OnConnectCompleted, op); + return op; + } + + /// + /// Begins an asynchronous request for a remote host connection. The host is specified by a host name and a port number. + /// + /// The target socket. + /// The name of the remote host. + /// The port number of the remote host. + /// Thrown if is . + /// Thrown if number is invalid. + /// The is listening. + /// An error occurred when attempting to access the . + /// Thrown if the has been closed. + /// Returns representing the asynchronous operation. + public static IAsyncOperation ConnectAsync(this Socket socket, string host, int port) + { + var op = new ApmResult(socket); + socket.BeginConnect(host, port, OnConnectCompleted, op); + return op; + } + + /// + /// Begins an asynchronous request for a remote host connection. The host is specified by an array and a port number. + /// + /// The target socket. + /// At least one , designating the remote host. + /// The port number of the remote host. + /// Thrown if is . + /// Thrown if number is invalid. + /// The is listening. + /// An error occurred when attempting to access the . + /// Thrown if the has been closed. + /// Returns representing the asynchronous operation. + public static IAsyncOperation ConnectAsync(this Socket socket, IPAddress[] addresses, int port) + { + var op = new ApmResult(socket); + socket.BeginConnect(addresses, port, OnConnectCompleted, op); + return op; + } + + /// + /// Begins an asynchronous request to disconnect from a remote endpoint. + /// + /// The target socket. + /// if this socket can be reused after the connection is closed; otherwise, . + /// An error occurred when attempting to access the . + /// The operating system is Windows 2000 or earlier, and this method requires Windows XP. + /// Thrown if the has been closed. + /// Returns representing the asynchronous operation. + public static IAsyncOperation DisconnectAsync(this Socket socket, bool reuseSocket) + { + var op = new ApmResult(socket); + socket.BeginDisconnect(reuseSocket, OnDisconnectCompleted, op); + return op; + } + + /// + /// Sends data asynchronously to a connected . + /// + /// The target socket. + /// An array of bytes that is the storage location for the received data. + /// A bitwise combination of the values. + /// Thrown if is . + /// An error occurred when attempting to access the . + /// Thrown if the has been closed. + /// Returns representing the asynchronous operation. + public static IAsyncOperation SendAsync(this Socket socket, IList> buffers, SocketFlags socketFlags) + { + var op = new ApmResult(socket); + socket.BeginSend(buffers, socketFlags, OnSendCompleted, op); + return op; + } + + /// + /// Sends data asynchronously to a connected . + /// + /// The target socket. + /// An array of bytes that contains the data to send. + /// The zero-based position in the at which to begin sending data. + /// The number of bytes to send. + /// A bitwise combination of the values. + /// Thrown if is . + /// Thrown if is less than 0 or is less + /// than the length of or is less than 0 or is greater + /// than the length of minus the value of the parameter. + /// An error occurred when attempting to access the . + /// Thrown if the has been closed. + /// Returns representing the asynchronous operation. + public static IAsyncOperation SendAsync(this Socket socket, byte[] buffer, int offset, int size, SocketFlags socketFlags) + { + var op = new ApmResult(socket); + socket.BeginSend(buffer, offset, size, socketFlags, OnSendCompleted, op); + return op; + } + + /// + /// Sends the file to a connected object using the flag. + /// + /// The target socket. + /// A string that contains the path and name of the file to send. This parameter can be . + /// The file was not found. + /// An error occurred when attempting to access the . + /// The operating system is not Windows NT or later or rhe is not connected to a remote host. + /// Thrown if the has been closed. + /// Returns representing the asynchronous operation. + public static IAsyncOperation SendFileAsync(this Socket socket, string fileName) + { + var op = new ApmResult(socket); + socket.BeginSendFile(fileName, OnSendFileCompleted, op); + return op; + } + + /// + /// Sends the file to a connected object. + /// + /// The target socket. + /// A string that contains the path and name of the file to send. This parameter can be . + /// A byte array that contains data to be sent before the file is sent. This parameter can be . + /// A byte array that contains data to be sent after the file is sent. This parameter can be . + /// A bitwise combination of values. + /// The file was not found. + /// An error occurred when attempting to access the . + /// The operating system is not Windows NT or later or rhe is not connected to a remote host. + /// Thrown if the has been closed. + /// Returns representing the asynchronous operation. + public static IAsyncOperation SendFileAsync(this Socket socket, string fileName, byte[] preBuffer, byte[] postBuffer, TransmitFileOptions flags) + { + var op = new ApmResult(socket); + socket.BeginSendFile(fileName, preBuffer, postBuffer, flags, OnSendFileCompleted, op); + return op; + } + + /// + /// Begins to asynchronously receive data from a connected . + /// + /// The target socket. + /// An array of bytes that is the storage location for the received data. + /// A bitwise combination of the values. + /// Thrown if is . + /// An error occurred when attempting to access the . + /// Thrown if the has been closed. + /// Returns representing the asynchronous operation. + public static IAsyncOperation ReceiveAsync(this Socket socket, IList> buffers, SocketFlags socketFlags) + { + var op = new ApmResult(socket); + socket.BeginReceive(buffers, socketFlags, OnReceiveCompleted, op); + return op; + } + + /// + /// Begins to asynchronously receive data from a connected . + /// + /// The target socket. + /// An array of bytes that is the storage location for the received data. + /// The zero-based position in the at which to store the received data. + /// The number of bytes to receive. + /// A bitwise combination of the values. + /// Thrown if is . + /// Thrown if is less than 0 or is greater + /// than the length of or is less than 0 or is greater + /// than the length of minus the value of the parameter. + /// An error occurred when attempting to access the . + /// Thrown if the has been closed. + /// Returns representing the asynchronous operation. + public static IAsyncOperation ReceiveAsync(this Socket socket, byte[] buffer, int offset, int size, SocketFlags socketFlags) + { + var op = new ApmResult(socket); + socket.BeginReceive(buffer, offset, size, socketFlags, OnReceiveCompleted, op); + return op; + } + + #endregion + + #region implementation + + private static void OnAcceptCompleted(IAsyncResult asyncResult) + { + var op = (ApmResult)asyncResult.AsyncState; + + try + { + op.TrySetResult(op.Source.EndAccept(asyncResult)); + } + catch (Exception e) + { + op.TrySetException(e); + } + } + + private static void OnConnectCompleted(IAsyncResult asyncResult) + { + var op = (ApmResult)asyncResult.AsyncState; + + try + { + op.Source.EndConnect(asyncResult); + op.TrySetCompleted(); + } + catch (Exception e) + { + op.TrySetException(e); + } + } + + private static void OnDisconnectCompleted(IAsyncResult asyncResult) + { + var op = (ApmResult)asyncResult.AsyncState; + + try + { + op.Source.EndDisconnect(asyncResult); + op.TrySetCompleted(); + } + catch (Exception e) + { + op.TrySetException(e); + } + } + + private static void OnSendCompleted(IAsyncResult asyncResult) + { + var op = (ApmResult)asyncResult.AsyncState; + + try + { + op.TrySetResult(op.Source.EndSend(asyncResult)); + } + catch (Exception e) + { + op.TrySetException(e); + } + } + + private static void OnSendFileCompleted(IAsyncResult asyncResult) + { + var op = (ApmResult)asyncResult.AsyncState; + + try + { + op.Source.EndSendFile(asyncResult); + op.TrySetCompleted(); + } + catch (Exception e) + { + op.TrySetException(e); + } + } + + private static void OnReceiveCompleted(IAsyncResult asyncResult) + { + var op = (ApmResult)asyncResult.AsyncState; + + try + { + op.TrySetResult(op.Source.EndReceive(asyncResult)); + } + catch (Exception e) + { + op.TrySetException(e); + } + } + + #endregion + } +} diff --git a/src/UnityFx.Async/Api/Extensions/StreamExtensions.cs b/src/UnityFx.Async/Api/Extensions/StreamExtensions.cs new file mode 100644 index 0000000..73eff9b --- /dev/null +++ b/src/UnityFx.Async/Api/Extensions/StreamExtensions.cs @@ -0,0 +1,98 @@ +// Copyright (c) Alexander Bogarsukov. +// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; +using System.ComponentModel; +using System.IO; + +namespace UnityFx.Async.Extensions +{ + /// + /// Extension methods for class. + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static class StreamExtensions + { + #region interface + + /// + /// Asynchronously reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + /// + /// The stream to read data from. + /// The buffer to write the data into. + /// The byte offset in at which to begin writing data from the stream. + /// The maximum number of bytes to read. + /// Thrown if is . + /// Thrown if or is negative. + /// Thrown if the sum of and is larger than the length. + /// Thrown if the stream does not support reading. + /// Thrown if the stream is currently in use by a previous read operation. + /// Thrown if the stream has been disposed. + /// An that represents the asynchronous read operation. The value of the result + /// parameter contains the total number of bytes read into the buffer. The result value can be less than the number of bytes requested + /// if the number of bytes currently available is less than the requested number, or it can be 0 (zero) if the end of the stream + /// has been reached. + public static IAsyncOperation ReadAsync(this Stream stream, byte[] buffer, int offset, int count) + { + var op = new ApmResult(stream); + stream.BeginRead(buffer, offset, count, OnReadCompleted, op); + return op; + } + + /// + /// Asynchronously writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + /// + /// The stream to write data to. + /// The buffer to write data from. + /// The zero-based byte offset in from which to begin copying bytes to the stream. + /// The maximum number of bytes to write. + /// Thrown if is . + /// Thrown if or is negative. + /// Thrown if the sum of and is larger than the length. + /// Thrown if the stream does not support writing. + /// Thrown if the stream is currently in use by a previous write operation. + /// Thrown if the stream has been disposed. + /// An that represents the asynchronous write operation. + public static IAsyncOperation WriteAsync(this Stream stream, byte[] buffer, int offset, int count) + { + var op = new ApmResult(stream); + stream.BeginWrite(buffer, offset, count, OnWriteCompleted, op); + return op; + } + + #endregion + + #region implementation + + private static void OnReadCompleted(IAsyncResult asyncResult) + { + var op = (ApmResult)asyncResult.AsyncState; + + try + { + op.TrySetResult(op.Source.EndRead(asyncResult)); + } + catch (Exception e) + { + op.TrySetException(e); + } + } + + private static void OnWriteCompleted(IAsyncResult asyncResult) + { + var op = (ApmResult)asyncResult.AsyncState; + + try + { + op.Source.EndWrite(asyncResult); + op.TrySetCompleted(); + } + catch (Exception e) + { + op.TrySetException(e); + } + } + + #endregion + } +} diff --git a/src/UnityFx.Async/Api/Extensions/SynchronizationContextExtensions.cs b/src/UnityFx.Async/Api/Extensions/SynchronizationContextExtensions.cs new file mode 100644 index 0000000..c97bbd7 --- /dev/null +++ b/src/UnityFx.Async/Api/Extensions/SynchronizationContextExtensions.cs @@ -0,0 +1,287 @@ +// Copyright (c) Alexander Bogarsukov. +// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; +using System.ComponentModel; +using System.Threading; + +namespace UnityFx.Async.Extensions +{ + /// + /// Extension methods for . + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static class SynchronizationContextExtensions + { + #region data + + private static SendOrPostCallback _actionCallback; + + #endregion + + #region interface + + /// + /// Dispatches an synchronous message to a synchronization context. + /// + /// The target context. + /// The delegate to invoke. + /// Thrown if is . + public static void Send(this SynchronizationContext context, Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (_actionCallback == null) + { + _actionCallback = ActionCallback; + } + + context.Post(_actionCallback, action); + } + + /// + /// Dispatches an synchronous message to a synchronization context. + /// + /// The target context. + /// The delegate to invoke. + /// Thrown if is . + /// Returns result of the call. + public static T Send(this SynchronizationContext context, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var result = default(T); + + context.Send( + state => + { + result = ((Func)state)(); + }, + action); + + return result; + } + + /// + /// Dispatches an asynchronous message to a synchronization context. + /// + /// The target context. + /// The delegate to invoke. + /// Thrown if is . + public static void Post(this SynchronizationContext context, Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (_actionCallback == null) + { + _actionCallback = ActionCallback; + } + + context.Post(_actionCallback, action); + } + + /// + /// Dispatches an asynchronous message to a synchronization context. + /// + /// The target context. + /// The delegate to invoke. + /// Thrown if is . + /// An that can be used to track the operation status. + public static IAsyncOperation PostAsync(this SynchronizationContext context, Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var op = new ActionResult(action); + + context.Post( + state => + { + ((ActionResult)state).Start(); + }, + op); + + return op; + } + + /// + /// Dispatches an asynchronous message to a synchronization context. + /// + /// The target context. + /// The delegate to invoke. + /// User-defined state. + /// Thrown if is . + /// An that can be used to track the operation status. + public static IAsyncOperation PostAsync(this SynchronizationContext context, SendOrPostCallback d, object state) + { + if (d == null) + { + throw new ArgumentNullException(nameof(d)); + } + + var op = new ActionResult(d, state); + + context.Post( + s => + { + ((ActionResult)s).Start(); + }, + op); + + return op; + } + + /// + /// Dispatches an asynchronous message to a synchronization context. + /// + /// The target context. + /// The delegate to invoke. + /// Thrown if is . + /// An that can be used to track the operation status. + public static IAsyncOperation PostAsync(this SynchronizationContext context, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var op = new ActionResult(action); + + context.Post( + state => + { + ((ActionResult)state).Start(); + }, + op); + + return op; + } + + /// + /// Dispatches a message to a synchronization context. + /// + /// The target context. + /// The delegate to invoke. + /// Thrown if is . + public static void Invoke(this SynchronizationContext context, Action action) + { + if (context == SynchronizationContext.Current) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + action.Invoke(); + } + else + { + context.Post(_actionCallback, action); + } + } + + /// + /// Dispatches a message to a synchronization context. + /// + /// The target context. + /// The delegate to invoke. + /// Thrown if is . + /// An that can be used to track the operation status. + public static IAsyncOperation InvokeAsync(this SynchronizationContext context, Action action) + { + if (context == SynchronizationContext.Current) + { + return AsyncResult.FromAction(action); + } + else + { + return PostAsync(context, action); + } + } + + /// + /// Dispatches a message to a synchronization context. + /// + /// The target context. + /// The delegate to invoke. + /// User-defined state. + /// Thrown if is . + public static void Invoke(this SynchronizationContext context, SendOrPostCallback d, object state) + { + if (context == SynchronizationContext.Current) + { + if (d == null) + { + throw new ArgumentNullException(nameof(d)); + } + + d.Invoke(state); + } + else + { + context.Post(d, state); + } + } + + /// + /// Dispatches a message to a synchronization context. + /// + /// The target context. + /// The delegate to invoke. + /// User-defined state. + /// Thrown if is . + /// An that can be used to track the operation status. + public static IAsyncOperation InvokeAsync(this SynchronizationContext context, SendOrPostCallback d, object state) + { + if (context == SynchronizationContext.Current) + { + return AsyncResult.FromAction(d, state); + } + else + { + return PostAsync(context, d, state); + } + } + + /// + /// Dispatches a message to a synchronization context. + /// + /// The target context. + /// The delegate to invoke. + /// Thrown if is . + /// An that can be used to track the operation status. + public static IAsyncOperation InvokeAsync(this SynchronizationContext context, Func action) + { + if (context == SynchronizationContext.Current) + { + return AsyncResult.FromAction(action); + } + else + { + return PostAsync(context, action); + } + } + + #endregion + + #region implementation + + private static void ActionCallback(object args) + { + ((Action)args).Invoke(); + } + + #endregion + } +} diff --git a/src/UnityFx.Async/Api/Extensions/TaskExtensions.cs b/src/UnityFx.Async/Api/Extensions/TaskExtensions.cs new file mode 100644 index 0000000..39e5358 --- /dev/null +++ b/src/UnityFx.Async/Api/Extensions/TaskExtensions.cs @@ -0,0 +1,42 @@ +// Copyright (c) Alexander Bogarsukov. +// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; +using System.ComponentModel; +#if !NET35 +using System.Threading.Tasks; +#endif + +namespace UnityFx.Async.Extensions +{ +#if !NET35 + + /// + /// Extension methods for and . + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static class TaskExtensions + { + /// + /// Creates an instance that completes when the specified completes. + /// + /// The task to convert to . + /// An that represents the . + public static AsyncResult ToAsync(this Task task) + { + return AsyncResult.FromTask(task); + } + + /// + /// Creates an instance that completes when the specified completes. + /// + /// The task to convert to . + /// An that represents the . + public static AsyncResult ToAsync(this Task task) + { + return AsyncResult.FromTask(task); + } + } + +#endif +} diff --git a/src/UnityFx.Async/Api/Extensions/WebRequestExtensions.cs b/src/UnityFx.Async/Api/Extensions/WebRequestExtensions.cs new file mode 100644 index 0000000..b1db555 --- /dev/null +++ b/src/UnityFx.Async/Api/Extensions/WebRequestExtensions.cs @@ -0,0 +1,79 @@ +// Copyright (c) Alexander Bogarsukov. +// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; +using System.ComponentModel; +using System.IO; +using System.Net; + +namespace UnityFx.Async.Extensions +{ + /// + /// Extension methods for class. + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static class WebRequestExtensions + { + #region interface + + /// + /// Returns a for writing data to the Internet resource as an asynchronous operation. + /// + /// The source . + /// Thrown if an attempt is made to access the method, when the method is not overridden in a descendant class. + /// Returns representing the asynchronous operation. + public static IAsyncOperation GetRequestStreamAsync(this WebRequest webRequest) + { + var op = new ApmResult(webRequest); + webRequest.BeginGetRequestStream(OnGetRequestStreamCompleted, op); + return op; + } + + /// + /// Begins an asynchronous request for an Internet resource. + /// + /// The source . + /// Thrown if an attempt is made to access the method, when the method is not overridden in a descendant class. + /// Returns representing the asynchronous operation. + public static IAsyncOperation GetResponseAsync(this WebRequest webRequest) + { + var op = new ApmResult(webRequest); + webRequest.BeginGetResponse(OnGetResponseCompleted, op); + return op; + } + + #endregion + + #region implementation + + private static void OnGetRequestStreamCompleted(IAsyncResult asyncResult) + { + var op = (ApmResult)asyncResult.AsyncState; + + try + { + op.TrySetResult(op.Source.EndGetRequestStream(asyncResult)); + } + catch (Exception e) + { + op.TrySetException(e); + } + } + + private static void OnGetResponseCompleted(IAsyncResult asyncResult) + { + var op = (ApmResult)asyncResult.AsyncState; + + try + { + op.TrySetResult(op.Source.EndGetResponse(asyncResult)); + } + catch (Exception e) + { + op.TrySetException(e); + } + } + + #endregion + } +} diff --git a/src/UnityFx.Async/Implementation/Public/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Promises/AsyncExtensions.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Public/Extensions/AsyncExtensions.Promises.cs rename to src/UnityFx.Async/Api/Promises/AsyncExtensions.cs diff --git a/src/UnityFx.Async/Implementation/Private/Common/AssemblyInfo.cs b/src/UnityFx.Async/Implementation/AssemblyInfo.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Common/AssemblyInfo.cs rename to src/UnityFx.Async/Implementation/AssemblyInfo.cs diff --git a/src/UnityFx.Async/Implementation/Private/Callbacks/CallbackData.cs b/src/UnityFx.Async/Implementation/Callbacks/CallbackData.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Callbacks/CallbackData.cs rename to src/UnityFx.Async/Implementation/Callbacks/CallbackData.cs diff --git a/src/UnityFx.Async/Implementation/Private/Callbacks/CallbackUtility.cs b/src/UnityFx.Async/Implementation/Callbacks/CallbackUtility.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Callbacks/CallbackUtility.cs rename to src/UnityFx.Async/Implementation/Callbacks/CallbackUtility.cs diff --git a/src/UnityFx.Async/Implementation/Private/Callbacks/IAsyncCallbackCollection.cs b/src/UnityFx.Async/Implementation/Callbacks/IAsyncCallbackCollection.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Callbacks/IAsyncCallbackCollection.cs rename to src/UnityFx.Async/Implementation/Callbacks/IAsyncCallbackCollection.cs diff --git a/src/UnityFx.Async/Implementation/Private/Callbacks/ListCallbackCollection.cs b/src/UnityFx.Async/Implementation/Callbacks/ListCallbackCollection.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Callbacks/ListCallbackCollection.cs rename to src/UnityFx.Async/Implementation/Callbacks/ListCallbackCollection.cs diff --git a/src/UnityFx.Async/Implementation/Private/Callbacks/MultiContextCallbackCollection.cs b/src/UnityFx.Async/Implementation/Callbacks/MultiContextCallbackCollection.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Callbacks/MultiContextCallbackCollection.cs rename to src/UnityFx.Async/Implementation/Callbacks/MultiContextCallbackCollection.cs diff --git a/src/UnityFx.Async/Implementation/Private/Callbacks/SingleContextCallbackCollection.cs b/src/UnityFx.Async/Implementation/Callbacks/SingleContextCallbackCollection.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Callbacks/SingleContextCallbackCollection.cs rename to src/UnityFx.Async/Implementation/Callbacks/SingleContextCallbackCollection.cs diff --git a/src/UnityFx.Async/Implementation/Private/Common/Messages.cs b/src/UnityFx.Async/Implementation/Common/Messages.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Common/Messages.cs rename to src/UnityFx.Async/Implementation/Common/Messages.cs diff --git a/src/UnityFx.Async/Implementation/Private/Common/VoidResult.cs b/src/UnityFx.Async/Implementation/Common/VoidResult.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Common/VoidResult.cs rename to src/UnityFx.Async/Implementation/Common/VoidResult.cs diff --git a/src/UnityFx.Async/Implementation/Private/Continuations/Promises/CatchResult{T,TException}.cs b/src/UnityFx.Async/Implementation/Promises/CatchResult{T,TException}.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Continuations/Promises/CatchResult{T,TException}.cs rename to src/UnityFx.Async/Implementation/Promises/CatchResult{T,TException}.cs diff --git a/src/UnityFx.Async/Implementation/Private/Continuations/Promises/DoneResult{T}.cs b/src/UnityFx.Async/Implementation/Promises/DoneResult{T}.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Continuations/Promises/DoneResult{T}.cs rename to src/UnityFx.Async/Implementation/Promises/DoneResult{T}.cs diff --git a/src/UnityFx.Async/Implementation/Private/Continuations/Promises/FinallyResult{T}.cs b/src/UnityFx.Async/Implementation/Promises/FinallyResult{T}.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Continuations/Promises/FinallyResult{T}.cs rename to src/UnityFx.Async/Implementation/Promises/FinallyResult{T}.cs diff --git a/src/UnityFx.Async/Implementation/Private/Continuations/Promises/RebindResult{T,U}.cs b/src/UnityFx.Async/Implementation/Promises/RebindResult{T,U}.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Continuations/Promises/RebindResult{T,U}.cs rename to src/UnityFx.Async/Implementation/Promises/RebindResult{T,U}.cs diff --git a/src/UnityFx.Async/Implementation/Private/Continuations/Promises/ThenAllResult{T,U}.cs b/src/UnityFx.Async/Implementation/Promises/ThenAllResult{T,U}.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Continuations/Promises/ThenAllResult{T,U}.cs rename to src/UnityFx.Async/Implementation/Promises/ThenAllResult{T,U}.cs diff --git a/src/UnityFx.Async/Implementation/Private/Continuations/Promises/ThenAnyResult{T,U}.cs b/src/UnityFx.Async/Implementation/Promises/ThenAnyResult{T,U}.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Continuations/Promises/ThenAnyResult{T,U}.cs rename to src/UnityFx.Async/Implementation/Promises/ThenAnyResult{T,U}.cs diff --git a/src/UnityFx.Async/Implementation/Private/Continuations/Promises/ThenResult{T,U}.cs b/src/UnityFx.Async/Implementation/Promises/ThenResult{T,U}.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Continuations/Promises/ThenResult{T,U}.cs rename to src/UnityFx.Async/Implementation/Promises/ThenResult{T,U}.cs diff --git a/src/UnityFx.Async/Implementation/Public/Extensions/AsyncExtensions.Continuations.cs b/src/UnityFx.Async/Implementation/Public/Extensions/AsyncExtensions.Continuations.cs deleted file mode 100644 index debf775..0000000 --- a/src/UnityFx.Async/Implementation/Public/Extensions/AsyncExtensions.Continuations.cs +++ /dev/null @@ -1,305 +0,0 @@ -// Copyright (c) Alexander Bogarsukov. -// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. - -using System; -using System.Threading; - -namespace UnityFx.Async -{ - partial class AsyncExtensions - { - #region ContinueWith - - /// - /// Creates a continuation that executes when the target completes. - /// - /// The operation to continue. - /// An action to run when the completes. - /// Thrown if the is . - /// An operation that is executed after completes. - /// - public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action action) - { - return ContinueWith(op, action, AsyncContinuationOptions.None); - } - - /// - /// Creates a continuation that executes when the target completes. - /// - /// The operation to continue. - /// An action to run when the completes. - /// Options for when the is executed. - /// Thrown if the is . - /// An operation that is executed after completes. - /// - public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action action, AsyncContinuationOptions options) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - return new ContinueWithResult(op, options, action, null); - } - - /// - /// Creates a continuation that executes when the target completes. - /// - /// The operation to continue. - /// An action to run when the completes. - /// A user-defined state object that is passed as second argument to . - /// Thrown if the is . - /// An operation that is executed after completes. - /// - public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action action, object userState) - { - return ContinueWith(op, action, userState, AsyncContinuationOptions.None); - } - - /// - /// Creates a continuation that executes when the target completes. - /// - /// The operation to continue. - /// An action to run when the completes. - /// A user-defined state object that is passed as second argument to . - /// Options for when the is executed. - /// Thrown if the is . - /// An operation that is executed after completes. - /// - public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action action, object userState, AsyncContinuationOptions options) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - return new ContinueWithResult(op, options, action, userState); - } - - /// - /// Creates a continuation that executes when the target completes. - /// - /// The operation to continue. - /// An action to run when the completes. - /// Thrown if the is . - /// An operation that is executed after completes. - /// - public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func action) - { - return ContinueWith(op, action, AsyncContinuationOptions.None); - } - - /// - /// Creates a continuation that executes when the target completes. - /// - /// The operation to continue. - /// An action to run when the completes. - /// Options for when the is executed. - /// Thrown if the is . - /// An operation that is executed after completes. - /// - public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func action, AsyncContinuationOptions options) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - return new ContinueWithResult(op, options, action, null); - } - - /// - /// Creates a continuation that executes when the target completes. - /// - /// The operation to continue. - /// An action to run when the completes. - /// A user-defined state object that is passed as second argument to . - /// Thrown if the is . - /// An operation that is executed after completes. - /// - public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func action, object userState) - { - return ContinueWith(op, action, userState, AsyncContinuationOptions.None); - } - - /// - /// Creates a continuation that executes when the target completes. - /// - /// The operation to continue. - /// An action to run when the completes. - /// A user-defined state object that is passed as second argument to . - /// Options for when the is executed. - /// Thrown if the is . - /// An operation that is executed after completes. - /// - public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func action, object userState, AsyncContinuationOptions options) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - return new ContinueWithResult(op, options, action, userState); - } - - /// - /// Creates a continuation that executes when the target completes. - /// - /// The operation to continue. - /// An action to run when the completes. - /// Thrown if the is . - /// An operation that is executed after completes. - /// - public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action> action) - { - return ContinueWith(op, action, AsyncContinuationOptions.None); - } - - /// - /// Creates a continuation that executes when the target completes. - /// - /// The operation to continue. - /// An action to run when the completes. - /// Options for when the is executed. - /// Thrown if the is . - /// An operation that is executed after completes. - /// - public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action> action, AsyncContinuationOptions options) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - return new ContinueWithResult(op, options, action, null); - } - - /// - /// Creates a continuation that executes when the target completes. - /// - /// The operation to continue. - /// An action to run when the completes. - /// A user-defined state object that is passed as second argument to . - /// Thrown if the is . - /// An operation that is executed after completes. - /// - public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action, object> action, object userState) - { - return ContinueWith(op, action, userState, AsyncContinuationOptions.None); - } - - /// - /// Creates a continuation that executes when the target completes. - /// - /// The operation to continue. - /// An action to run when the completes. - /// A user-defined state object that is passed as second argument to . - /// Options for when the is executed. - /// Thrown if the is . - /// An operation that is executed after completes. - /// - public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action, object> action, object userState, AsyncContinuationOptions options) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - return new ContinueWithResult(op, options, action, userState); - } - - /// - /// Creates a continuation that executes when the target completes. - /// - /// The operation to continue. - /// An action to run when the completes. - /// Thrown if the is . - /// An operation that is executed after completes. - /// - public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func, TNewResult> action) - { - return ContinueWith(op, action, AsyncContinuationOptions.None); - } - - /// - /// Creates a continuation that executes when the target completes. - /// - /// The operation to continue. - /// An action to run when the completes. - /// Options for when the is executed. - /// Thrown if the is . - /// An operation that is executed after completes. - /// - public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func, TNewResult> action, AsyncContinuationOptions options) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - return new ContinueWithResult(op, options, action, null); - } - - /// - /// Creates a continuation that executes when the target completes. - /// - /// The operation to continue. - /// An action to run when the completes. - /// A user-defined state object that is passed as second argument to . - /// Thrown if the is . - /// An operation that is executed after completes. - /// - public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func, object, TNewResult> action, object userState) - { - return ContinueWith(op, action, userState, AsyncContinuationOptions.None); - } - - /// - /// Creates a continuation that executes when the target completes. - /// - /// The operation to continue. - /// An action to run when the completes. - /// A user-defined state object that is passed as second argument to . - /// Options for when the is executed. - /// Thrown if the is . - /// An operation that is executed after completes. - /// - public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func, object, TNewResult> action, object userState, AsyncContinuationOptions options) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - return new ContinueWithResult(op, options, action, userState); - } - - #endregion - - #region Unwrap - - /// - /// Creates a proxy that represents the asynchronous operation of a IAsyncOperation<IAsyncOperation>. - /// - /// The source operation. - /// The unwrapped operation. - /// - public static IAsyncOperation Unwrap(this IAsyncOperation op) - { - return new UnwrapResult(op); - } - - /// - /// Creates a proxy that represents the asynchronous operation of a IAsyncOperation<IAsyncOperation<TResult>>. - /// - /// The source operation. - /// The unwrapped operation. - /// - public static IAsyncOperation Unwrap(this IAsyncOperation> op) - { - return new UnwrapResult(op); - } - - #endregion - } -} diff --git a/src/UnityFx.Async/Implementation/Public/Extensions/AsyncExtensions.Tasks.cs b/src/UnityFx.Async/Implementation/Public/Extensions/AsyncExtensions.Tasks.cs deleted file mode 100644 index a270eb9..0000000 --- a/src/UnityFx.Async/Implementation/Public/Extensions/AsyncExtensions.Tasks.cs +++ /dev/null @@ -1,216 +0,0 @@ -// Copyright (c) Alexander Bogarsukov. -// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. - -using System; -using System.Runtime.CompilerServices; -using System.Threading; -#if !NET35 -using System.Threading.Tasks; -#endif - -namespace UnityFx.Async -{ -#if !NET35 - - partial class AsyncExtensions - { - #region GetAwaiter/ConfigureAwait - - /// - /// Returns the operation awaiter. This method is intended for compiler use only. - /// - /// The operation to await. - /// An object that can be used to await the operation. - public static CompilerServices.AsyncAwaiter GetAwaiter(this IAsyncOperation op) - { - return new CompilerServices.AsyncAwaiter(op); - } - - /// - /// Returns the operation awaiter. This method is intended for compiler use only. - /// - /// The operation to await. - /// An object that can be used to await the operation. - public static CompilerServices.AsyncAwaiter GetAwaiter(this IAsyncOperation op) - { - return new CompilerServices.AsyncAwaiter(op); - } - - /// - /// Configures an awaiter used to await this operation. - /// - /// The operation to await. - /// If attempts to marshal the continuation back to the original context captured. - /// An object that can be used to await the operation. - public static CompilerServices.AsyncAwaitable ConfigureAwait(this IAsyncOperation op, bool continueOnCapturedContext) - { - return new CompilerServices.AsyncAwaitable(op, continueOnCapturedContext ? SynchronizationContext.Current : null); - } - - /// - /// Configures an awaiter used to await this operation. - /// - /// The operation to await. - /// If attempts to marshal the continuation back to the original context captured. - /// An object that can be used to await the operation. - public static CompilerServices.AsyncAwaitable ConfigureAwait(this IAsyncOperation op, bool continueOnCapturedContext) - { - return new CompilerServices.AsyncAwaitable(op, continueOnCapturedContext ? SynchronizationContext.Current : null); - } - - /// - /// Configures an awaiter used to await this operation. - /// - /// The operation to await. - /// Specifies continuation options. - /// An object that can be used to await the operation. - public static CompilerServices.AsyncAwaitable ConfigureAwait(this IAsyncOperation op, AsyncContinuationContext continuationContext) - { - return new CompilerServices.AsyncAwaitable(op, AsyncResult.GetSynchronizationContext(continuationContext)); - } - - /// - /// Configures an awaiter used to await this operation. - /// - /// The operation to await. - /// Specifies continuation options. - /// An object that can be used to await the operation. - public static CompilerServices.AsyncAwaitable ConfigureAwait(this IAsyncOperation op, AsyncContinuationContext continuationContext) - { - return new CompilerServices.AsyncAwaitable(op, AsyncResult.GetSynchronizationContext(continuationContext)); - } - - #endregion - - #region ToTask - - /// - /// Creates a instance matching the source . - /// - /// The target operation. - /// - public static Task ToTask(this IAsyncOperation op) - { - var status = op.Status; - - if (status == AsyncOperationStatus.RanToCompletion) - { - return Task.CompletedTask; - } - else if (status == AsyncOperationStatus.Faulted) - { - return Task.FromException(op.Exception); - } - else if (status == AsyncOperationStatus.Canceled) - { - return Task.FromCanceled(new CancellationToken(true)); - } - else - { - var tcs = new TaskCompletionSource(); - - op.AddCompletionCallback( - asyncOp => - { - status = op.Status; - - if (status == AsyncOperationStatus.RanToCompletion) - { - tcs.TrySetResult(null); - } - else if (status == AsyncOperationStatus.Faulted) - { - tcs.TrySetException(op.Exception); - } - else if (status == AsyncOperationStatus.Canceled) - { - tcs.TrySetCanceled(); - } - }, - null); - - return tcs.Task; - } - } - - /// - /// Creates a instance matching the source . - /// - /// The target operation. - /// - public static Task ToTask(this IAsyncOperation op) - { - var status = op.Status; - - if (status == AsyncOperationStatus.RanToCompletion) - { - return Task.FromResult(op.Result); - } - else if (status == AsyncOperationStatus.Faulted) - { - return Task.FromException(op.Exception); - } - else if (status == AsyncOperationStatus.Canceled) - { - return Task.FromCanceled(new CancellationToken(true)); - } - else - { - var tcs = new TaskCompletionSource(); - - op.AddCompletionCallback( - asyncOp => - { - status = op.Status; - - if (status == AsyncOperationStatus.RanToCompletion) - { - tcs.TrySetResult(op.Result); - } - else if (status == AsyncOperationStatus.Faulted) - { - tcs.TrySetException(op.Exception); - } - else if (status == AsyncOperationStatus.Canceled) - { - tcs.TrySetCanceled(); - } - }, - null); - - return tcs.Task; - } - } - - #endregion - - #region ToAsync - - /// - /// Creates an instance that completes when the specified completes. - /// - /// The task to convert to . - /// An that represents the . - public static AsyncResult ToAsync(this Task task) - { - return AsyncResult.FromTask(task); - } - - /// - /// Creates an instance that completes when the specified completes. - /// - /// The task to convert to . - /// An that represents the . - public static AsyncResult ToAsync(this Task task) - { - return AsyncResult.FromTask(task); - } - - #endregion - - #region implementation - #endregion - } - -#endif -} diff --git a/src/UnityFx.Async/Implementation/Public/Extensions/AsyncExtensions.Wait.cs b/src/UnityFx.Async/Implementation/Public/Extensions/AsyncExtensions.Wait.cs deleted file mode 100644 index 20dd438..0000000 --- a/src/UnityFx.Async/Implementation/Public/Extensions/AsyncExtensions.Wait.cs +++ /dev/null @@ -1,531 +0,0 @@ -// Copyright (c) Alexander Bogarsukov. -// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. - -using System; -using System.Threading; - -namespace UnityFx.Async -{ - partial class AsyncExtensions - { - #region Wait - - /// - /// Waits for the to complete execution. After that rethrows the operation exception (if any). - /// - /// The operation to wait for. - /// Thrown is the operation is disposed. - /// - /// - public static void Wait(this IAsyncOperation op) - { - if (!op.IsCompleted) - { - op.AsyncWaitHandle.WaitOne(); - } - - ThrowIfNonSuccess(op); - } - - /// - /// Waits for the to complete execution within a specified number of milliseconds. After that rethrows the operation exception (if any). - /// - /// The operation to wait for. - /// The number of milliseconds to wait, or (-1) to wait indefinitely. - /// if the operation completed execution within the allotted time; otherwise, . - /// is a negative number other than -1. - /// Thrown is the operation is disposed. - /// - /// - public static bool Wait(this IAsyncOperation op, int millisecondsTimeout) - { - var result = true; - - if (!op.IsCompleted) - { - result = op.AsyncWaitHandle.WaitOne(millisecondsTimeout); - } - - if (result) - { - ThrowIfNonSuccess(op); - } - - return result; - } - - /// - /// Waits for the to complete execution within a specified time interval. After that rethrows the operation exception (if any). - /// - /// The operation to wait for. - /// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - /// if the operation completed execution within the allotted time; otherwise, . - /// is a negative number other than -1 milliseconds, or is greater than . - /// Thrown is the operation is disposed. - /// - /// - public static bool Wait(this IAsyncOperation op, TimeSpan timeout) - { - var result = true; - - if (!op.IsCompleted) - { - result = op.AsyncWaitHandle.WaitOne(timeout); - } - - if (result) - { - ThrowIfNonSuccess(op); - } - - return result; - } - -#if !NET35 - - /// - /// Waits for the to complete execution. After that rethrows the operation exception (if any). - /// The wait terminates if a cancellation token is canceled before the operation completes. - /// - /// The operation to wait for. - /// A cancellation token to observe while waiting for the operation to complete. - /// Thrown is the operation is disposed. - /// The was canceled. - /// - public static void Wait(this IAsyncOperation op, CancellationToken cancellationToken) - { - WaitInternal(op, cancellationToken); - ThrowIfNonSuccess(op); - } - - /// - /// Waits for the to complete execution within a specified number of milliseconds. After that - /// rethrows the operation exception (if any). The wait terminates if a timeout interval elapses or a cancellation token is - /// canceled before the operation completes. - /// - /// The operation to wait for. - /// The number of milliseconds to wait, or (-1) to wait indefinitely. - /// A cancellation token to observe while waiting for the operation to complete. - /// if the operation completed execution within the allotted time; otherwise, . - /// is a negative number other than -1. - /// The was canceled. - /// Thrown is the operation is disposed. - /// - public static bool Wait(this IAsyncOperation op, int millisecondsTimeout, CancellationToken cancellationToken) - { - if (WaitInternal(op, millisecondsTimeout, cancellationToken)) - { - ThrowIfNonSuccess(op); - return true; - } - - return false; - } - - /// - /// Waits for the to complete execution within a specified time interval. After that rethrows - /// the operation exception (if any). The wait terminates if a timeout interval elapses or a cancellation token is canceled - /// before the operation completes. - /// - /// The operation to wait for. - /// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - /// A cancellation token to observe while waiting for the operation to complete. - /// if the operation completed execution within the allotted time; otherwise, . - /// is a negative number other than -1 milliseconds, or is greater than . - /// The was canceled. - /// Thrown is the operation is disposed. - /// - public static bool Wait(this IAsyncOperation op, TimeSpan timeout, CancellationToken cancellationToken) - { - if (WaitInternal(op, timeout, cancellationToken)) - { - ThrowIfNonSuccess(op); - return true; - } - - return false; - } - -#endif - - #endregion - - #region Join - - /// - /// Waits for the to complete execution. After that rethrows the operation exception (if any). - /// - /// The operation to join. - /// Thrown is the operation is disposed. - /// - /// - /// - public static void Join(this IAsyncOperation op) - { - if (!op.IsCompleted) - { - op.AsyncWaitHandle.WaitOne(); - } - - ThrowIfNonSuccess(op); - } - - /// - /// Waits for the to complete execution within a specified number of milliseconds. After that rethrows the operation exception (if any). - /// - /// The operation to wait for. - /// The number of milliseconds to wait, or (-1) to wait indefinitely. - /// is a negative number other than -1. - /// Thrown if the operation did not completed within . - /// Thrown is the operation is disposed. - /// - /// - /// - public static void Join(this IAsyncOperation op, int millisecondsTimeout) - { - var result = true; - - if (!op.IsCompleted) - { - result = op.AsyncWaitHandle.WaitOne(millisecondsTimeout); - } - - if (result) - { - ThrowIfNonSuccess(op); - } - else - { - throw new TimeoutException(); - } - } - - /// - /// Waits for the to complete execution within a specified timeout. After that rethrows the operation exception (if any). - /// - /// The operation to wait for. - /// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - /// is a negative number other than -1 milliseconds, or is greater than . - /// Thrown if the operation did not completed within . - /// Thrown is the operation is disposed. - /// - /// - /// - public static void Join(this IAsyncOperation op, TimeSpan timeout) - { - var result = true; - - if (!op.IsCompleted) - { - result = op.AsyncWaitHandle.WaitOne(timeout); - } - - if (result) - { - ThrowIfNonSuccess(op); - } - else - { - throw new TimeoutException(); - } - } - - /// - /// Waits for the to complete execution. After that rethrows the operation exception (if any). - /// - /// The operation to join. - /// The operation result. - /// Thrown is the operation is disposed. - /// - /// - /// - public static TResult Join(this IAsyncOperation op) - { - if (!op.IsCompleted) - { - op.AsyncWaitHandle.WaitOne(); - } - - return op.Result; - } - - /// - /// Waits for the to complete execution within a specified number of milliseconds. After that rethrows the operation exception (if any). - /// - /// The operation to wait for. - /// The number of milliseconds to wait, or (-1) to wait indefinitely. - /// The operation result. - /// is a negative number other than -1. - /// Thrown if the operation did not completed within . - /// Thrown is the operation is disposed. - /// - /// - /// - public static TResult Join(this IAsyncOperation op, int millisecondsTimeout) - { - var result = true; - - if (!op.IsCompleted) - { - result = op.AsyncWaitHandle.WaitOne(millisecondsTimeout); - } - - if (!result) - { - throw new TimeoutException(); - } - - return op.Result; - } - - /// - /// Waits for the to complete execution within a specified timeout. After that rethrows the operation exception (if any). - /// - /// The operation to wait for. - /// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - /// The operation result. - /// is a negative number other than -1 milliseconds, or is greater than . - /// Thrown if the operation did not completed within . - /// Thrown is the operation is disposed. - /// - /// - /// - public static TResult Join(this IAsyncOperation op, TimeSpan timeout) - { - var result = true; - - if (!op.IsCompleted) - { - result = op.AsyncWaitHandle.WaitOne(timeout); - } - - if (!result) - { - throw new TimeoutException(); - } - - return op.Result; - } - -#if !NET35 - - /// - /// Waits for the to complete execution. After that rethrows the operation exception (if any). The wait terminates - /// if a cancellation token is canceled before the operation completes. - /// - /// The operation to join. - /// A cancellation token to observe while waiting for the operation to complete. - /// The was canceled. - /// Thrown is the operation is disposed. - /// - public static void Join(this IAsyncOperation op, CancellationToken cancellationToken) - { - WaitInternal(op, cancellationToken); - ThrowIfNonSuccess(op); - } - - /// - /// Waits for the to complete execution within a specified number of milliseconds. After that rethrows the operation exception (if any). - /// The wait terminates if a timeout interval elapses or a cancellation token is canceled before the operation completes. - /// - /// The operation to wait for. - /// The number of milliseconds to wait, or (-1) to wait indefinitely. - /// A cancellation token to observe while waiting for the operation to complete. - /// is a negative number other than -1. - /// Thrown if the operation did not completed within . - /// The was canceled. - /// Thrown is the operation is disposed. - /// - public static void Join(this IAsyncOperation op, int millisecondsTimeout, CancellationToken cancellationToken) - { - if (WaitInternal(op, millisecondsTimeout, cancellationToken)) - { - ThrowIfNonSuccess(op); - } - else - { - throw new TimeoutException(); - } - } - - /// - /// Waits for the to complete execution within a specified timeout. After that rethrows the operation exception (if any). - /// The wait terminates if a timeout interval elapses or a cancellation token is canceled before the operation completes. - /// - /// The operation to wait for. - /// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - /// A cancellation token to observe while waiting for the operation to complete. - /// is a negative number other than -1 milliseconds, or is greater than . - /// Thrown if the operation did not completed within . - /// The was canceled. - /// Thrown is the operation is disposed. - /// - public static void Join(this IAsyncOperation op, TimeSpan timeout, CancellationToken cancellationToken) - { - if (WaitInternal(op, timeout, cancellationToken)) - { - ThrowIfNonSuccess(op); - } - else - { - throw new TimeoutException(); - } - } - - /// - /// Waits for the to complete execution. After that rethrows the operation exception (if any). - /// The wait terminates if a cancellation token is canceled before the operation completes. - /// - /// The operation to join. - /// A cancellation token to observe while waiting for the operation to complete. - /// The operation result. - /// The was canceled. - /// Thrown is the operation is disposed. - /// - public static TResult Join(this IAsyncOperation op, CancellationToken cancellationToken) - { - WaitInternal(op, cancellationToken); - return op.Result; - } - - /// - /// Waits for the to complete execution within a specified number of milliseconds. After that rethrows the operation exception (if any). - /// The wait terminates if a timeout interval elapses or a cancellation token is canceled before the operation completes. - /// - /// The operation to wait for. - /// The number of milliseconds to wait, or (-1) to wait indefinitely. - /// A cancellation token to observe while waiting for the operation to complete. - /// The operation result. - /// is a negative number other than -1. - /// Thrown if the operation did not completed within . - /// The was canceled. - /// Thrown is the operation is disposed. - /// - public static TResult Join(this IAsyncOperation op, int millisecondsTimeout, CancellationToken cancellationToken) - { - if (!WaitInternal(op, millisecondsTimeout, cancellationToken)) - { - throw new TimeoutException(); - } - - return op.Result; - } - - /// - /// Waits for the to complete execution within a specified timeout. After that rethrows the operation exception (if any). - /// The wait terminates if a timeout interval elapses or a cancellation token is canceled before the operation completes. - /// - /// The operation to wait for. - /// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - /// A cancellation token to observe while waiting for the operation to complete. - /// The operation result. - /// is a negative number other than -1 milliseconds, or is greater than . - /// Thrown if the operation did not completed within . - /// The was canceled. - /// Thrown is the operation is disposed. - /// - public static TResult Join(this IAsyncOperation op, TimeSpan timeout, CancellationToken cancellationToken) - { - if (!WaitInternal(op, timeout, cancellationToken)) - { - throw new TimeoutException(); - } - - return op.Result; - } - -#endif - - #endregion - - #region implementation - -#if !NET35 - - private static void WaitInternal(IAsyncOperation op, CancellationToken cancellationToken) - { - if (!op.IsCompleted) - { - if (cancellationToken.CanBeCanceled) - { - cancellationToken.ThrowIfCancellationRequested(); - - var index = WaitHandle.WaitAny(new WaitHandle[] { op.AsyncWaitHandle, cancellationToken.WaitHandle }); - - if (index == 1) - { - throw new OperationCanceledException(); - } - } - else - { - op.AsyncWaitHandle.WaitOne(); - } - } - } - - private static bool WaitInternal(IAsyncOperation op, int millisecondsTimeout, CancellationToken cancellationToken) - { - var result = true; - - if (!op.IsCompleted) - { - if (cancellationToken.CanBeCanceled) - { - cancellationToken.ThrowIfCancellationRequested(); - - var index = WaitHandle.WaitAny(new WaitHandle[] { op.AsyncWaitHandle, cancellationToken.WaitHandle }, millisecondsTimeout); - - if (index == WaitHandle.WaitTimeout) - { - result = false; - } - else if (index == 1) - { - throw new OperationCanceledException(); - } - } - else - { - result = op.AsyncWaitHandle.WaitOne(millisecondsTimeout); - } - } - - return result; - } - - private static bool WaitInternal(IAsyncOperation op, TimeSpan timeout, CancellationToken cancellationToken) - { - var result = true; - - if (!op.IsCompleted) - { - if (cancellationToken.CanBeCanceled) - { - cancellationToken.ThrowIfCancellationRequested(); - - var index = WaitHandle.WaitAny(new WaitHandle[] { op.AsyncWaitHandle, cancellationToken.WaitHandle }, timeout); - - if (index == WaitHandle.WaitTimeout) - { - result = false; - } - else if (index == 1) - { - throw new OperationCanceledException(); - } - } - else - { - result = op.AsyncWaitHandle.WaitOne(timeout); - } - } - - return result; - } - -#endif - - #endregion - } -} diff --git a/src/UnityFx.Async/Implementation/Public/Extensions/AsyncExtensions.cs b/src/UnityFx.Async/Implementation/Public/Extensions/AsyncExtensions.cs deleted file mode 100644 index b23957b..0000000 --- a/src/UnityFx.Async/Implementation/Public/Extensions/AsyncExtensions.cs +++ /dev/null @@ -1,820 +0,0 @@ -// Copyright (c) Alexander Bogarsukov. -// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. - -using System; -using System.Collections; -using System.Collections.Generic; -using System.ComponentModel; -using System.Threading; - -namespace UnityFx.Async -{ - /// - /// Extension methods for . - /// - [EditorBrowsable(EditorBrowsableState.Advanced)] - public static partial class AsyncExtensions - { - #region data - - private static SendOrPostCallback _actionCallback; - -#if !NET35 - - private static Action _cancelHandler; - -#endif - - #endregion - - #region Common - - /// - /// Throws if the specified operation is faulted/canceled. - /// - public static void ThrowIfNonSuccess(this IAsyncOperation op) - { - var status = op.Status; - - if (status == AsyncOperationStatus.Faulted) - { - if (!AsyncResult.TryThrowException(op.Exception)) - { - // Should never get here. Exception should never be null in faulted state. - throw new Exception(); - } - } - else if (status == AsyncOperationStatus.Canceled) - { - if (!AsyncResult.TryThrowException(op.Exception)) - { - throw new OperationCanceledException(); - } - } - } - - /// - /// Creates an that completes when the specified operation completes. - /// - /// The operation to convert to enumerator. - /// An enumerator that represents the operation. - public static IEnumerator ToEnum(this IAsyncResult op) - { - if (op is IEnumerator e) - { - return e; - } - - return new TaskEnumerator(op); - } - - /// - /// Spins until the operation has completed. - /// - /// The operation to wait for. - public static void SpinUntilCompleted(this IAsyncResult op) - { -#if NET35 - - while (!op.IsCompleted) - { - Thread.SpinWait(1); - } - -#else - - var sw = new SpinWait(); - - while (!op.IsCompleted) - { - sw.SpinOnce(); - } - -#endif - } - - /// - /// Spins until the operation has completed within a specified timeout. - /// - /// The operation to wait for. - /// The number of milliseconds to wait, or (-1) to wait indefinitely. - /// is a negative number other than -1. - /// Returns if the operation was completed within the specified time interfval; otherwise. - public static bool SpinUntilCompleted(this IAsyncResult op, int millisecondsTimeout) - { - if (millisecondsTimeout < -1) - { - throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout), millisecondsTimeout, Messages.FormatError_InvalidTimeout()); - } - - if (millisecondsTimeout == Timeout.Infinite) - { - SpinUntilCompleted(op); - return true; - } - - return SpinUntilCompletedInternal(op, TimeSpan.FromMilliseconds(millisecondsTimeout)); - } - - /// - /// Spins until the operation has completed within a specified timeout. - /// - /// The operation to wait for. - /// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - /// is a negative number other than -1 milliseconds, or is greater than . - /// Returns if the operation was completed within the specified time interfval; otherwise. - public static bool SpinUntilCompleted(this IAsyncResult op, TimeSpan timeout) - { - var totalMilliseconds = (long)timeout.TotalMilliseconds; - - if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue) - { - throw new ArgumentOutOfRangeException(nameof(timeout), timeout, Messages.FormatError_InvalidTimeout()); - } - - if (totalMilliseconds == Timeout.Infinite) - { - SpinUntilCompleted(op); - return true; - } - - return SpinUntilCompletedInternal(op, timeout); - } - -#if !NET35 - - /// - /// Spins until the operation has completed or until canceled. - /// - /// The operation to wait for. - /// A cancellation token that can be used to cancel wait operation. - /// The was canceled. - /// - public static void SpinUntilCompleted(this IAsyncResult op, CancellationToken cancellationToken) - { - var sw = new SpinWait(); - - while (!op.IsCompleted) - { - cancellationToken.ThrowIfCancellationRequested(); - sw.SpinOnce(); - } - } - - /// - /// Spins until the operation has completed within a specified timeout or until canceled. - /// - /// The operation to wait for. - /// The number of milliseconds to wait, or (-1) to wait indefinitely. - /// A cancellation token that can be used to cancel wait operation. - /// is a negative number other than -1. - /// The was canceled. - /// Returns if the operation was completed within the specified time interfval; otherwise. - public static bool SpinUntilCompleted(this IAsyncResult op, int millisecondsTimeout, CancellationToken cancellationToken) - { - if (millisecondsTimeout < -1) - { - throw new ArgumentOutOfRangeException(nameof(millisecondsTimeout), millisecondsTimeout, Messages.FormatError_InvalidTimeout()); - } - - if (millisecondsTimeout == Timeout.Infinite) - { - SpinUntilCompleted(op, cancellationToken); - return true; - } - - return SpinUntilCompletedInternal(op, TimeSpan.FromMilliseconds(millisecondsTimeout), cancellationToken); - } - - /// - /// Spins until the operation has completed within a specified timeout or until canceled. - /// - /// The operation to wait for. - /// A that represents the number of milliseconds to wait, or a that represents -1 milliseconds to wait indefinitely. - /// A cancellation token that can be used to cancel wait operation. - /// is a negative number other than -1 milliseconds, or is greater than . - /// The was canceled. - /// Returns if the operation was completed within the specified time interfval; otherwise. - public static bool SpinUntilCompleted(this IAsyncResult op, TimeSpan timeout, CancellationToken cancellationToken) - { - var totalMilliseconds = (long)timeout.TotalMilliseconds; - - if (totalMilliseconds < -1 || totalMilliseconds > int.MaxValue) - { - throw new ArgumentOutOfRangeException(nameof(timeout), timeout, Messages.FormatError_InvalidTimeout()); - } - - if (totalMilliseconds == Timeout.Infinite) - { - SpinUntilCompleted(op, cancellationToken); - return true; - } - - return SpinUntilCompletedInternal(op, timeout, cancellationToken); - } - - /// - /// Registers a that can be used to cancel the specified operation. - /// - /// An operation to register for. - /// A cancellation token that can be used to cancel the operation. - /// Thrown if the target operation does not support cancellation. - /// Returns the target operation. - public static IAsyncOperation WithCancellation(this IAsyncOperation op, CancellationToken cancellationToken) - { - if (cancellationToken.CanBeCanceled && !op.IsCompleted) - { - if (cancellationToken.IsCancellationRequested) - { - op.Cancel(); - } - else - { - if (_cancelHandler == null) - { - _cancelHandler = args => (args as IAsyncCancellable).Cancel(); - } - - cancellationToken.Register(_cancelHandler, op, false); - } - } - - return op; - } - -#endif - - #endregion - - #region SynchronizationContext - - /// - /// Dispatches an synchronous message to a synchronization context. - /// - /// The target context. - /// The delegate to invoke. - /// Thrown if is . - public static void Send(this SynchronizationContext context, Action action) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - if (_actionCallback == null) - { - _actionCallback = ActionCallback; - } - - context.Post(_actionCallback, action); - } - - /// - /// Dispatches an asynchronous message to a synchronization context. - /// - /// The target context. - /// The delegate to invoke. - /// Thrown if is . - public static void Post(this SynchronizationContext context, Action action) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - if (_actionCallback == null) - { - _actionCallback = ActionCallback; - } - - context.Post(_actionCallback, action); - } - - /// - /// Dispatches a message to a synchronization context. - /// - /// The target context. - /// The delegate to invoke. - /// Thrown if is . - public static void Invoke(this SynchronizationContext context, Action action) - { - if (context == SynchronizationContext.Current) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - action.Invoke(); - } - else - { - context.Post(_actionCallback, action); - } - } - - /// - /// Dispatches a message to a synchronization context. - /// - /// The target context. - /// The delegate to invoke. - /// User-defined state. - /// Thrown if is . - public static void Invoke(this SynchronizationContext context, SendOrPostCallback d, object state) - { - if (context == SynchronizationContext.Current) - { - if (d == null) - { - throw new ArgumentNullException(nameof(d)); - } - - d.Invoke(state); - } - else - { - context.Post(d, state); - } - } - - #endregion - - #region IAsyncOperationEvents - - /// - /// Adds a completion callback to be executed after the operation has completed. If the operation is already completed - /// the is called synchronously. - /// - /// - /// The is invoked on a thread that registered the continuation (if it has a attached). - /// Throwing an exception from the callback might cause unspecified behaviour. - /// - /// The operation to schedule continuation for. - /// The callback to be executed when the operation has completed. - /// Thrown if is . - /// Thrown is the operation has been disposed. - public static void AddCompletionCallback(this IAsyncOperationEvents op, Action callback) - { - op.AddCompletionCallback(callback, SynchronizationContext.Current); - } - - /// - /// Adds a completion callback to be executed after the operation has completed. If the operation is already completed - /// the is invoked on a context specified via . - /// - /// - /// The is invoked on a specified. - /// Throwing an exception from the callback might cause unspecified behaviour. - /// - /// The operation to schedule continuation for. - /// The callback to be executed when the operation has completed. - /// Identifier of a to schedule callback on. - /// Thrown if is . - /// Thrown is the operation has been disposed. - public static void AddCompletionCallback(this IAsyncOperationEvents op, Action callback, AsyncContinuationContext continuationContext) - { - op.AddCompletionCallback(callback, AsyncResult.GetSynchronizationContext(continuationContext)); - } - - /// - /// Adds a completion callback to be executed after the operation has completed. If the operation is already completed - /// the is called synchronously. - /// - /// - /// The is invoked on a thread that registered the continuation (if it has a attached). - /// Throwing an exception from the callback might cause unspecified behaviour. - /// - /// The operation to schedule continuation for. - /// The callback to be executed when the operation has completed. - /// Thrown if is . - /// Thrown is the operation has been disposed. - public static void AddCompletionCallback(this IAsyncOperationEvents op, IAsyncContinuation callback) - { - op.AddCompletionCallback(callback, SynchronizationContext.Current); - } - - /// - /// Adds a completion callback to be executed after the operation has completed. If the operation is already completed - /// the is invoked on a context specified via . - /// - /// - /// The is invoked on a specified. - /// Throwing an exception from the callback might cause unspecified behaviour. - /// - /// The operation to schedule continuation for. - /// The callback to be executed when the operation has completed. - /// Identifier of a to schedule callback on. - /// Thrown if is . - /// Thrown is the operation has been disposed. - public static void AddCompletionCallback(this IAsyncOperationEvents op, IAsyncContinuation callback, AsyncContinuationContext continuationContext) - { - op.AddCompletionCallback(callback, AsyncResult.GetSynchronizationContext(continuationContext)); - } - - /// - /// Adds a callback to be executed when the operation progress has changed. If the operation is already completed - /// the is called synchronously. - /// - /// - /// The is invoked on a thread that registered the callback (if it has a attached). - /// Throwing an exception from the callback might cause unspecified behaviour. - /// - /// The operation to schedule continuation for. - /// The callback to be executed when the operation progress has changed. - /// Thrown if is . - /// Thrown is the operation has been disposed. - public static void AddProgressCallback(this IAsyncOperationEvents op, Action callback) - { - op.AddProgressCallback(callback, SynchronizationContext.Current); - } - - /// - /// Adds a callback to be executed when the operation progress has changed. If the operation is already completed - /// the is invoked on a context specified via . - /// - /// - /// The is invoked on a specified. - /// Throwing an exception from the callback might cause unspecified behaviour. - /// - /// The operation to schedule continuation for. - /// The callback to be executed when the operation progress has changed. - /// Identifier of a to schedule callback on. - /// Thrown if is . - /// Thrown is the operation has been disposed. - public static void AddProgressCallback(this IAsyncOperationEvents op, Action callback, AsyncContinuationContext continuationContext) - { - op.AddProgressCallback(callback, AsyncResult.GetSynchronizationContext(continuationContext)); - } - -#if !NET35 - - /// - /// Adds a callback to be executed when the operation progress has changed. If the operation is already completed - /// the is called synchronously. - /// - /// - /// The is invoked on a thread that registered the callback (if it has a attached). - /// Throwing an exception from the callback might cause unspecified behaviour. - /// - /// The operation to schedule continuation for. - /// The callback to be executed when the operation progress has changed. - /// Thrown if is . - /// Thrown is the operation has been disposed. - public static void AddProgressCallback(this IAsyncOperationEvents op, IProgress callback) - { - op.AddProgressCallback(callback, SynchronizationContext.Current); - } - - /// - /// Adds a callback to be executed when the operation progress has changed. If the operation is already completed - /// the is invoked on a context specified via . - /// - /// - /// The is invoked on a specified. - /// Throwing an exception from the callback might cause unspecified behaviour. - /// - /// The operation to schedule continuation for. - /// The callback to be executed when the operation progress has changed. - /// Identifier of a to schedule callback on. - /// Thrown if is . - /// Thrown is the operation has been disposed. - public static void AddProgressCallback(this IAsyncOperationEvents op, IProgress callback, AsyncContinuationContext continuationContext) - { - op.AddProgressCallback(callback, AsyncResult.GetSynchronizationContext(continuationContext)); - } - -#endif - - #endregion - - #region IAsyncCompletionSource - - /// - /// Sets the operation progress value in range [0, 1]. - /// - /// The completion source instance. - /// The operation progress in range [0, 1]. - /// Thrown if is not in range [0, 1]. - /// Thrown if the progress value cannot be set. - /// Thrown is the operation is disposed. - /// - public static void SetProgress(this IAsyncCompletionSource completionSource, float progress) - { - if (!completionSource.TrySetProgress(progress)) - { - throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); - } - } - - /// - /// Sets the operation progress value in range [0, 1]. - /// - /// The completion source instance. - /// The operation progress in range [0, 1]. - /// Thrown if is not in range [0, 1]. - /// Thrown if the progress value cannot be set. - /// Thrown is the operation is disposed. - /// - public static void SetProgress(this IAsyncCompletionSource completionSource, float progress) - { - if (!completionSource.TrySetProgress(progress)) - { - throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); - } - } - - /// - /// Transitions the underlying into the state. - /// - /// The completion source instance. - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - /// - public static void SetCanceled(this IAsyncCompletionSource completionSource) - { - if (!completionSource.TrySetCanceled()) - { - throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); - } - } - - /// - /// Transitions the underlying into the state. - /// - /// The completion source instance. - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - /// - public static void SetCanceled(this IAsyncCompletionSource completionSource) - { - if (!completionSource.TrySetCanceled()) - { - throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); - } - } - - /// - /// Attempts to transition the underlying into the state. - /// - /// The completion source instance. - /// An exception message. - /// Thrown if is . - /// Thrown is the operation is disposed. - /// Returns if the attemp was successfull; otherwise. - /// - /// - /// - public static bool TrySetException(this IAsyncCompletionSource completionSource, string message) - { - return completionSource.TrySetException(new Exception(message)); - } - - /// - /// Attempts to transition the underlying into the state. - /// - /// The completion source instance. - /// An exception message. - /// Thrown if is . - /// Thrown is the operation is disposed. - /// Returns if the attemp was successfull; otherwise. - /// - /// - /// - public static bool TrySetException(this IAsyncCompletionSource completionSource, string message) - { - return completionSource.TrySetException(new Exception(message)); - } - - /// - /// Transitions the underlying into the state. - /// - /// The completion source instance. - /// An exception message. - /// Thrown if is . - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - /// - public static void SetException(this IAsyncCompletionSource completionSource, string message) - { - if (!completionSource.TrySetException(new Exception(message))) - { - throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); - } - } - - /// - /// Transitions the underlying into the state. - /// - /// The completion source instance. - /// An exception message. - /// Thrown if is . - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - /// - public static void SetException(this IAsyncCompletionSource completionSource, string message) - { - if (!completionSource.TrySetException(new Exception(message))) - { - throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); - } - } - - /// - /// Transitions the underlying into the state. - /// - /// The completion source instance. - /// An exception that caused the operation to end prematurely. - /// Thrown if is . - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - /// - public static void SetException(this IAsyncCompletionSource completionSource, Exception exception) - { - if (!completionSource.TrySetException(exception)) - { - throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); - } - } - - /// - /// Transitions the underlying into the state. - /// - /// The completion source instance. - /// An exception that caused the operation to end prematurely. - /// Thrown if is . - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - /// - public static void SetException(this IAsyncCompletionSource completionSource, Exception exception) - { - if (!completionSource.TrySetException(exception)) - { - throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); - } - } - - /// - /// Transitions the underlying into the state. - /// - /// The completion source instance. - /// Exceptions that caused the operation to end prematurely. - /// Thrown if is . - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - /// - public static void SetExceptions(this IAsyncCompletionSource completionSource, IEnumerable exceptions) - { - if (!completionSource.TrySetExceptions(exceptions)) - { - throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); - } - } - - /// - /// Transitions the underlying into the state. - /// - /// The completion source instance. - /// Exceptions that caused the operation to end prematurely. - /// Thrown if is . - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - /// - public static void SetExceptions(this IAsyncCompletionSource completionSource, IEnumerable exceptions) - { - if (!completionSource.TrySetExceptions(exceptions)) - { - throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); - } - } - - /// - /// Transitions the underlying into the state. - /// - /// The completion source instance. - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - /// - public static void SetCompleted(this IAsyncCompletionSource completionSource) - { - if (!completionSource.TrySetCompleted()) - { - throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); - } - } - - /// - /// Transitions the underlying into the state. - /// - /// The completion source instance. - /// The operation result. - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - /// - public static void SetResult(this IAsyncCompletionSource completionSource, TResult result) - { - if (!completionSource.TrySetResult(result)) - { - throw new InvalidOperationException(Messages.FormatError_OperationStateCannotBeChanged()); - } - } - - #endregion - - #region implementation - - private class TaskEnumerator : IEnumerator - { - private readonly IAsyncResult _op; - - public TaskEnumerator(IAsyncResult task) => _op = task; - public object Current => null; - public bool MoveNext() => !_op.IsCompleted; - public void Reset() => throw new NotSupportedException(); - } - -#if !NET35 - - private static bool SpinUntilCompletedInternal(IAsyncResult op, TimeSpan timeout, CancellationToken cancellationToken) - { - var endTime = DateTime.Now + timeout; - var sw = new SpinWait(); - - while (!op.IsCompleted) - { - if (DateTime.Now > endTime) - { - return false; - } - - cancellationToken.ThrowIfCancellationRequested(); - sw.SpinOnce(); - } - - return true; - } - -#endif - - private static bool SpinUntilCompletedInternal(IAsyncResult op, TimeSpan timeout) - { - var endTime = DateTime.Now + timeout; - -#if NET35 - - while (!op.IsCompleted) - { - if (DateTime.Now > endTime) - { - return false; - } - - Thread.SpinWait(1); - } - -#else - - var sw = new SpinWait(); - - while (!op.IsCompleted) - { - if (DateTime.Now > endTime) - { - return false; - } - - sw.SpinOnce(); - } - -#endif - - return true; - } - - private static void ActionCallback(object args) - { - ((Action)args).Invoke(); - } - - #endregion - } -} diff --git a/src/UnityFx.Async/Implementation/Public/Helpers/AsyncLazy.cs b/src/UnityFx.Async/Implementation/Public/Helpers/AsyncLazy.cs deleted file mode 100644 index 91ceef2..0000000 --- a/src/UnityFx.Async/Implementation/Public/Helpers/AsyncLazy.cs +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) Alexander Bogarsukov. -// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. - -using System; - -namespace UnityFx.Async -{ - /// - /// A helper for lazy initialization. - /// - /// - /// This value-type is mutable, so DO NOT create readonly instances of it. - /// - /// - public struct AsyncLazy - { - #region data - - private Func _opFactory; - private IAsyncOperation _op; - - #endregion - - #region interface - - /// - /// Gets a value indicating whether the operation has started. - /// - public bool IsStarted => _op != null; - - /// - /// Gets a value indicating whether the operation completed successfully (i.e. with status). - /// - public bool IsCompletedSuccessfully => _op?.IsCompletedSuccessfully ?? false; - - /// - /// Gets a value indicating whether the operation has completed. - /// - public bool IsCompleted => _op?.IsCompleted ?? false; - - /// - /// Gets a value indicating whether the operation completed due to an unhandled exception (i.e. with status). - /// - public bool IsFaulted => _op?.IsFaulted ?? false; - - /// - /// Gets a value indicating whether the operation completed due to being canceled (i.e. with status). - /// - public bool IsCanceled => _op?.IsCanceled ?? false; - - /// - /// Initializes a new instance of the struct. - /// - /// The delegate that is invoked to produce the lazily initialized operation when it is needed. - /// Thrown if value is . - public AsyncLazy(Func opFactory) - { - _opFactory = opFactory ?? throw new ArgumentNullException(nameof(opFactory)); - _op = null; - } - - /// - /// Starts the operation or just updates its state. - /// - /// Thrown if operation factory is . - public IAsyncOperation StartOrUpdate() - { - if (_op != null) - { - UpdateOperation(); - } - else - { - _op = _opFactory?.Invoke() ?? throw new InvalidOperationException(); - } - - return _op; - } - - /// - /// Resets state of the instance to default. - /// - public void Reset() - { - _op = null; - } - -#if !NET35 - - /// - /// Returns the operation awaiter. This method is intended for compiler use only. - /// - public CompilerServices.AsyncAwaiter GetAwaiter() - { - var op = StartOrUpdate(); - return new CompilerServices.AsyncAwaiter(op); - } - -#endif - - #endregion - - #region implementation - - private void UpdateOperation() - { - if (_opFactory != null) - { - var status = _op.Status; - - switch (status) - { - case AsyncOperationStatus.RanToCompletion: - _op = AsyncResult.CompletedOperation; - _opFactory = null; - break; - } - } - } - - #endregion - } -} diff --git a/src/UnityFx.Async/Implementation/Public/Helpers/AsyncResultQueue{T}.cs b/src/UnityFx.Async/Implementation/Public/Helpers/AsyncResultQueue{T}.cs deleted file mode 100644 index b5d11c4..0000000 --- a/src/UnityFx.Async/Implementation/Public/Helpers/AsyncResultQueue{T}.cs +++ /dev/null @@ -1,430 +0,0 @@ -// Copyright (c) Alexander Bogarsukov. -// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. - -using System; -using System.Collections; -#if !NET35 -using System.Collections.Concurrent; -#endif -using System.Collections.Generic; -using System.Threading; - -namespace UnityFx.Async -{ - /// - /// A FIFO queue of instances that are executed sequentially. - /// - /// - /// Completed operations are removed from the queue automatically. All interaction with queued operations is done - /// through the that can be specified on queue construction. - /// - /// - /// -#if NET35 - public class AsyncResultQueue : IEnumerable, IAsyncCancellable where T : AsyncResult -#else - public class AsyncResultQueue : IReadOnlyCollection, IAsyncCancellable where T : AsyncResult -#endif - { - #region data - - private readonly SynchronizationContext _syncContext; - - private int _maxOpsSize = 0; - private bool _suspended; -#if NET35 - private List _ops = new List(); -#else - private ConcurrentQueue _ops = new ConcurrentQueue(); -#endif - private SendOrPostCallback _startCallback; - private Action _completionCallback; - - #endregion - - #region interface - - /// - /// Gets a value indicating whether the queue is empty. - /// - /// The empty flag. - /// - public bool IsEmpty - { - get - { -#if NET35 - return _ops.Count == 0; -#else - return _ops.IsEmpty; -#endif - } - } - - /// - /// Gets or sets maximum queue size. Default is 0 (no constraints). - /// - /// Maximum queue size. Zero means no constraints. - /// Thrown if the value is less than 0. - /// - public int MaxCount - { - get - { - return _maxOpsSize; - } - set - { - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(MaxCount), value, Messages.FormatError_ValueIsLessThanZero()); - } - - _maxOpsSize = value; - } - } - - /// - /// Gets or sets a value indicating whether the queue in on pause. When in suspended state queue does not change state of the operations. - /// - /// The paused flag. - public bool Suspended - { - get - { - return _suspended; - } - set - { - _suspended = value; - - if (!_suspended) - { - TryStart(null); - } - } - } - - /// - /// Gets an operation that is running currently or if the queue is empty. - /// - /// An operation that is running currently. - public T Current - { - get - { -#if NET35 - lock (_ops) - { - return _ops.Count > 0 ? _ops[0] : null; - } -#else - return _ops.TryPeek(out var result) ? result : null; -#endif - } - } - - /// - /// Initializes a new instance of the class. - /// - public AsyncResultQueue() - { - } - - /// - /// Initializes a new instance of the class. - /// - /// The synchronization context to to use for marshaling operation calls to specific thread. Can have value. - public AsyncResultQueue(SynchronizationContext syncContext) - { - _syncContext = syncContext; - } - - /// - /// Adds a new operation to the end of the queue. Operation is expected to have its status set to . - /// - /// The operation to enqueue. - /// Thrown if is . - /// Thrown if the operatinon is started. - /// - public bool Add(T op) - { - if (op == null) - { - throw new ArgumentNullException(nameof(op)); - } - - if (op.IsStarted) - { - throw new InvalidOperationException(); - } - - if (_maxOpsSize > 0 && _ops.Count >= _maxOpsSize) - { - return false; - } - - if (_completionCallback == null) - { - _completionCallback = OnCompletedCallback; - } - - op.AddCompletionCallback(_completionCallback, _syncContext); - -#if NET35 - lock (_ops) - { - _ops.Add(op); - TryStart(op); - } -#else - _ops.Enqueue(op); - TryStart(op); -#endif - - return true; - } - - /// - /// Removes all elements from the collection. - /// - public void Clear() - { -#if NET35 - lock (_ops) - { - foreach (var op in _ops) - { - op.RemoveCompletionCallback(_completionCallback); - } - - _ops.Clear(); - } -#else - while (_ops.TryDequeue(out var op)) - { - op.RemoveCompletionCallback(_completionCallback); - } -#endif - } - - /// - /// Copies the collection elements to an array. - /// - public void CopyTo(T[] array, int arrayIndex) - { -#if NET35 - lock (_ops) - { - _ops.CopyTo(array, arrayIndex); - } -#else - _ops.CopyTo(array, arrayIndex); -#endif - } - - /// - /// Returns the queue snapshot as array. - /// - /// An array containing the queue snapshot. - public T[] ToArray() - { -#if NET35 - lock (_ops) - { - return _ops.ToArray(); - } -#else - return _ops.ToArray(); -#endif - } - - #endregion - - #region IAsyncCancellable - - /// - public void Cancel() - { -#if NET35 - lock (_ops) - { - foreach (var op in _ops) - { - op.RemoveCompletionCallback(_completionCallback); - op.Cancel(); - } - - _ops.Clear(); - } -#else - while (_ops.TryDequeue(out var op)) - { - op.RemoveCompletionCallback(_completionCallback); - op.Cancel(); - } -#endif - } - - #endregion - - #region IReadOnlyCollection - - /// - public int Count => _ops.Count; - - #endregion - - #region IEnumerable - -#if NET35 - - private class Enumerator : IEnumerator - { - private List _list; - private List.Enumerator _enumerator; - - public Enumerator(List list) - { - Monitor.Enter(list); - - _list = list; - _enumerator = list.GetEnumerator(); - } - - public T Current => _enumerator.Current; - - object IEnumerator.Current => _enumerator.Current; - - public bool MoveNext() => _enumerator.MoveNext(); - - public void Reset() => throw new NotSupportedException(); - - public void Dispose() - { - if (_list != null) - { - _enumerator.Dispose(); - Monitor.Exit(_list); - _list = null; - } - } - } - - /// - public IEnumerator GetEnumerator() - { - return new Enumerator(_ops); - } - - /// - IEnumerator IEnumerable.GetEnumerator() - { - return new Enumerator(_ops); - } - -#else - - /// - public IEnumerator GetEnumerator() - { - return _ops.GetEnumerator(); - } - - /// - IEnumerator IEnumerable.GetEnumerator() - { - return _ops.GetEnumerator(); - } - -#endif - - #endregion - - #region implementation - - private void TryStart(T op) - { - if (!_suspended) - { - if (_syncContext == null || _syncContext == SynchronizationContext.Current) - { - TryStartUnsafe(op); - } - else - { - if (_startCallback == null) - { - _startCallback = OnStartCallback; - } - - _syncContext.Post(_startCallback, op); - } - } - } - - private void TryStartUnsafe(T op) - { - if (!_suspended) - { - op?.TrySetScheduled(); - -#if NET35 - while (_ops.Count > 0) - { - var firstOp = _ops[0]; - - if (firstOp.IsCompleted) - { - _ops.RemoveAt(0); - } - else - { - firstOp.TrySetRunning(); - break; - } - } -#else - while (_ops.TryPeek(out var firstOp)) - { - if (firstOp.IsCompleted) - { - _ops.TryDequeue(out firstOp); - } - else - { - firstOp.TrySetRunning(); - break; - } - } -#endif - } - } - - private void OnStartCallback(object args) - { -#if NET35 - lock (_ops) - { - TryStartUnsafe(args as T); - } -#else - TryStartUnsafe(args as T); -#endif - } - - private void OnCompletedCallback(IAsyncOperation op) - { -#if NET35 - lock (_ops) - { - TryStartUnsafe(null); - } -#else - TryStartUnsafe(null); -#endif - } - - #endregion - } -} diff --git a/src/UnityFx.Async/Implementation/Specialized/ActionResult.cs b/src/UnityFx.Async/Implementation/Specialized/ActionResult.cs new file mode 100644 index 0000000..b5e61ae --- /dev/null +++ b/src/UnityFx.Async/Implementation/Specialized/ActionResult.cs @@ -0,0 +1,62 @@ +// Copyright (c) Alexander Bogarsukov. +// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; +using System.Diagnostics; +using System.Threading; + +namespace UnityFx.Async +{ + internal sealed class ActionResult : AsyncResult + { + #region data + + private readonly object _action; + + #endregion + + #region interface + + public ActionResult(Action action) + : base(AsyncOperationStatus.Scheduled) + { + _action = action; + } + + public ActionResult(SendOrPostCallback action, object state) + : base(AsyncOperationStatus.Scheduled, state) + { + _action = action; + } + + #endregion + + #region AsyncResult + + protected override void OnStarted() + { + try + { + if (_action is Action a) + { + a(); + } + else + { + ((SendOrPostCallback)_action)(AsyncState); + } + + TrySetCompleted(); + } + catch (Exception e) + { + TrySetException(e); + } + } + + #endregion + + #region implementation + #endregion + } +} diff --git a/src/UnityFx.Async/Implementation/Specialized/ActionResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/ActionResult{T}.cs new file mode 100644 index 0000000..ccf8246 --- /dev/null +++ b/src/UnityFx.Async/Implementation/Specialized/ActionResult{T}.cs @@ -0,0 +1,42 @@ +// Copyright (c) Alexander Bogarsukov. +// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; + +namespace UnityFx.Async +{ + internal sealed class ActionResult : AsyncResult + { + #region data + + private readonly Func _action; + + #endregion + + #region interface + + public ActionResult(Func action) + : base(AsyncOperationStatus.Scheduled) + { + _action = action; + } + + #endregion + + #region AsyncResult + + protected override void OnStarted() + { + try + { + TrySetResult(_action()); + } + catch (Exception e) + { + TrySetException(e); + } + } + + #endregion + } +} diff --git a/src/UnityFx.Async/Implementation/Specialized/ApmResult{TSource,TResult}.cs b/src/UnityFx.Async/Implementation/Specialized/ApmResult{TSource,TResult}.cs new file mode 100644 index 0000000..3c2944c --- /dev/null +++ b/src/UnityFx.Async/Implementation/Specialized/ApmResult{TSource,TResult}.cs @@ -0,0 +1,18 @@ +// Copyright (c) Alexander Bogarsukov. +// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; + +namespace UnityFx.Async +{ + internal class ApmResult : AsyncResult + { + public TSource Source { get; } + + public ApmResult(TSource source) + : base(AsyncOperationStatus.Running) + { + Source = source; + } + } +} diff --git a/src/UnityFx.Async/Implementation/Private/Continuations/ContinueWithResult{T,U}.cs b/src/UnityFx.Async/Implementation/Specialized/ContinueWithResult{T,U}.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Continuations/ContinueWithResult{T,U}.cs rename to src/UnityFx.Async/Implementation/Specialized/ContinueWithResult{T,U}.cs diff --git a/src/UnityFx.Async/Implementation/Private/Observable/AsyncObservableResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/FromObservableResult{T}.cs similarity index 72% rename from src/UnityFx.Async/Implementation/Private/Observable/AsyncObservableResult{T}.cs rename to src/UnityFx.Async/Implementation/Specialized/FromObservableResult{T}.cs index 709d3c9..b1489cd 100644 --- a/src/UnityFx.Async/Implementation/Private/Observable/AsyncObservableResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/FromObservableResult{T}.cs @@ -7,7 +7,7 @@ namespace UnityFx.Async { #if !NET35 - internal sealed class AsyncObservableResult : AsyncResult, IObserver + internal sealed class FromObservableResult : AsyncResult, IObserver { #region data @@ -17,7 +17,7 @@ internal sealed class AsyncObservableResult : AsyncResult, IObserver #region interface - internal AsyncObservableResult(IObservable observable) + internal FromObservableResult(IObservable observable) : base(AsyncOperationStatus.Running) { _subscription = observable.Subscribe(this); @@ -43,18 +43,14 @@ protected override void Dispose(bool disposing) void IObserver.OnNext(T value) { - if (TrySetResult(value, false)) - { - _subscription.Dispose(); - } + _subscription.Dispose(); + TrySetResult(value, false); } void IObserver.OnError(Exception error) { - if (TrySetException(error, false)) - { - _subscription.Dispose(); - } + _subscription.Dispose(); + TrySetException(error, false); } void IObserver.OnCompleted() diff --git a/src/UnityFx.Async/Implementation/Private/Observable/AsyncObservableSubscription{T}.cs b/src/UnityFx.Async/Implementation/Specialized/ObservableSubscription{T}.cs similarity index 82% rename from src/UnityFx.Async/Implementation/Private/Observable/AsyncObservableSubscription{T}.cs rename to src/UnityFx.Async/Implementation/Specialized/ObservableSubscription{T}.cs index 1e5c171..93d2436 100644 --- a/src/UnityFx.Async/Implementation/Private/Observable/AsyncObservableSubscription{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/ObservableSubscription{T}.cs @@ -7,7 +7,7 @@ namespace UnityFx.Async { #if !NET35 - internal class AsyncObservableSubscription : IAsyncContinuation, IDisposable + internal class ObservableSubscription : IAsyncContinuation, IDisposable { #region data @@ -18,7 +18,7 @@ internal class AsyncObservableSubscription : IAsyncContinuation, IDisposable #region interface - public AsyncObservableSubscription(IAsyncOperation op, IObserver observer) + public ObservableSubscription(IAsyncOperation op, IObserver observer) { _op = op; _observer = observer; diff --git a/src/UnityFx.Async/Implementation/Private/Specialized/RetryResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Specialized/RetryResult{T}.cs rename to src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs diff --git a/src/UnityFx.Async/Implementation/Private/Specialized/TimerDelayResult.cs b/src/UnityFx.Async/Implementation/Specialized/TimerDelayResult.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Specialized/TimerDelayResult.cs rename to src/UnityFx.Async/Implementation/Specialized/TimerDelayResult.cs diff --git a/src/UnityFx.Async/Implementation/Private/Specialized/TimerRetryResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/TimerRetryResult{T}.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Specialized/TimerRetryResult{T}.cs rename to src/UnityFx.Async/Implementation/Specialized/TimerRetryResult{T}.cs diff --git a/src/UnityFx.Async/Implementation/Private/Continuations/UnwrapResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/UnwrapResult{T}.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Continuations/UnwrapResult{T}.cs rename to src/UnityFx.Async/Implementation/Specialized/UnwrapResult{T}.cs diff --git a/src/UnityFx.Async/Implementation/Private/Specialized/UpdatableDelayResult.cs b/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Specialized/UpdatableDelayResult.cs rename to src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs diff --git a/src/UnityFx.Async/Implementation/Private/Specialized/UpdatableRetryResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/UpdatableRetryResult{T}.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Specialized/UpdatableRetryResult{T}.cs rename to src/UnityFx.Async/Implementation/Specialized/UpdatableRetryResult{T}.cs diff --git a/src/UnityFx.Async/Implementation/Private/Specialized/WhenAllResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Specialized/WhenAllResult{T}.cs rename to src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs diff --git a/src/UnityFx.Async/Implementation/Private/Specialized/WhenAnyResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Private/Specialized/WhenAnyResult{T}.cs rename to src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs diff --git a/unity/Sandbox/Assets/Plugins.meta b/unity/Sandbox/Assets/Plugins.meta deleted file mode 100644 index cc089cd..0000000 --- a/unity/Sandbox/Assets/Plugins.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: 23e3c25ef357701448d443d61ea39944 -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: