From 20b0bc1b1206cc8fd8371f56acc5b8519556c4ff Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 28 Mar 2018 23:16:54 +0300 Subject: [PATCH 001/128] Added AsyncContinuationOptions support --- CHANGELOG.md | 3 + README.md | 2 +- .../Tests/AsyncResultTests.cs | 12 +-- src/UnityFx.Async/Api/Core/AsyncExtensions.cs | 63 +++++++-------- src/UnityFx.Async/Api/Core/AsyncResult.cs | 64 ++++++++++----- .../Api/Core/AsyncResultQueue{T}.cs | 2 +- src/UnityFx.Async/Api/Core/AsyncResult{T}.cs | 8 +- .../Api/Interfaces/IAsyncOperationEvents.cs | 79 ++++++++++++++++++- .../Implementation/AsyncContinuation.cs | 44 ++++++++--- .../Implementation/AsyncObservable{T}.cs | 2 +- .../Implementation/RetryResult{T}.cs | 2 +- .../Implementation/WhenAllResult{T}.cs | 2 +- .../Implementation/WhenAnyResult{T}.cs | 2 +- 13 files changed, 204 insertions(+), 81 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e1423d..6a3c1f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/); this proj ----------------------- ## [Unreleased] +### Added +- Added `AsyncContinuationOptions` support. + ----------------------- ## [0.8.2] - 2018-03-28 diff --git a/README.md b/README.md index 0c10d18..fb54fee 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ TPL | UnityFx.Async | Notes [Task<TResult>](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1) | [AsyncResult<TResult>](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncResult-1.html), [IAsyncOperation<TResult>](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.IAsyncOperation-1.html) | Represents an asynchronous operation that can return a value. [TaskStatus](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskstatus) | [AsyncOperationStatus](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncOperationStatus.html) | Represents the current stage in the lifecycle of an asynchronous operation. [TaskCreationOptions](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcreationoptions) | - | Specifies flags that control optional behavior for the creation and execution of asynchronous operations. -[TaskContinuationOptions](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcontinuationoptions) | TODO | Specifies the behavior for an asynchronous operation that is created by using continuation methods (`ContinueWith`). +[TaskContinuationOptions](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcontinuationoptions) | [AsyncContinuationOptions](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncContinuationOptions.html) | Specifies the behavior for an asynchronous operation that is created by using continuation methods (`ContinueWith`). [TaskCanceledException](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcanceledexception) | - | Represents an exception used to communicate an asynchronous operation cancellation. [TaskCompletionSource<TResult>](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcompletionsource-1) | [AsyncCompletionSource](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncCompletionSource.html), [IAsyncCompletionSource](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.IAsyncCompletionSource.html), [AsyncCompletionSource<TResult>](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncCompletionSource-1.html), [IAsyncCompletionSource<TResult>](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.IAsyncCompletionSource-1.html) | Represents the producer side of an asyncronous operation unbound to a delegate. [TaskScheduler](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskscheduler) | - | Represents an object that handles the low-level work of queuing asynchronous operations onto threads. diff --git a/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs b/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs index f916604..37fbc62 100644 --- a/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs +++ b/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs @@ -465,7 +465,7 @@ public void TrySetCanceled_RaisesCompletionCallbacks() var asyncCallbackCalled1 = false; var asyncCallbackCalled2 = false; var op = new AsyncCompletionSource(asyncResult => asyncCallbackCalled1 = true, null); - op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, false); + op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, AsyncContinuationOptions.None); // Act op.TrySetCanceled(); @@ -575,7 +575,7 @@ public void TrySetException_RaisesCompletionCallbacks() var asyncCallbackCalled1 = false; var asyncCallbackCalled2 = false; var op = new AsyncCompletionSource(asyncResult => asyncCallbackCalled1 = true, null); - op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, false); + op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, AsyncContinuationOptions.None); // Act op.TrySetException(e); @@ -696,7 +696,7 @@ public void TrySetCompleted_RaisesCompletionCallbacks() var asyncCallbackCalled1 = false; var asyncCallbackCalled2 = false; var op = new AsyncCompletionSource(asyncResult => asyncCallbackCalled1 = true, null); - op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, false); + op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, AsyncContinuationOptions.None); // Act op.TrySetCompleted(); @@ -804,7 +804,7 @@ public void TrySetResult_RaisesCompletionCallbacks() var asyncCallbackCalled1 = false; var asyncCallbackCalled2 = false; var op = new AsyncCompletionSource(asyncResult => asyncCallbackCalled1 = true, null); - op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, false); + op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, AsyncContinuationOptions.None); // Act op.TrySetResult(10); @@ -884,7 +884,7 @@ public void TryAddCompletionCallback_FailsIfOperationIsCompleted() op.SetCanceled(); // Act - var result = op.TryAddCompletionCallback(_ => { }, null); + var result = op.TryAddCompletionCallback(_ => { }, AsyncContinuationOptions.None, null); // Assert Assert.False(result); @@ -897,7 +897,7 @@ public void TryAddCompletionCallback_FailsIfOperationIsCompletedSynchronously() var op = AsyncResult.CompletedOperation; // Act - var result = op.TryAddCompletionCallback(_ => { }, null); + var result = op.TryAddCompletionCallback(_ => { }, AsyncContinuationOptions.None, null); // Assert Assert.False(result); diff --git a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs index 9fd9f5c..ecf6859 100644 --- a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs +++ b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs @@ -343,10 +343,10 @@ public static T Join(this IAsyncOperation op, TimeSpan timeout) /// Thrown if the is . /// Thrown is the operation has been disposed. /// - /// + /// public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action) { - if (!op.TryAddCompletionCallback(action, SynchronizationContext.Current)) + if (!op.TryAddCompletionCallback(action, AsyncContinuationOptions.CaptureSynchronizationContext, null)) { action(op); } @@ -357,20 +357,21 @@ public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperation /// /// The target operation. /// The callback to be executed when the operation has completed. - /// If method attempts to marshal the continuation back to the current synchronization context. - /// Otherwise the callback is invoked on a thread that initiated the operation completion. - /// + /// Options for when the callback is executed. /// Thrown if the is . /// Thrown is the operation has been disposed. /// - /// - public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action, bool continueOnCapturedContext) + /// + public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action, AsyncContinuationOptions options) { - var context = continueOnCapturedContext ? SynchronizationContext.Current : null; + var syncContext = ((options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0) ? SynchronizationContext.Current : null; - if (!op.TryAddCompletionCallback(action, context)) + if (!op.TryAddCompletionCallback(action, options, syncContext)) { - action(op); + if (AsyncContinuation.CanInvoke(op, options)) + { + action(op); + } } } @@ -380,24 +381,28 @@ public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperation /// /// The target operation. /// The callback to be executed when the operation has completed. + /// Options for when the callback is executed. /// If not method attempts to marshal the continuation to the synchronization context. /// Otherwise the callback is invoked on a thread that initiated the operation completion. /// /// Thrown if the is . /// Thrown is the operation has been disposed. /// - /// - public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action, SynchronizationContext synchronizationContext) + /// + public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action, AsyncContinuationOptions options, SynchronizationContext synchronizationContext) { - if (!op.TryAddCompletionCallback(action, synchronizationContext)) + if (!op.TryAddCompletionCallback(action, options, synchronizationContext)) { - if (synchronizationContext == null || synchronizationContext.GetType() == typeof(SynchronizationContext) || synchronizationContext == SynchronizationContext.Current) - { - action(op); - } - else + if (AsyncContinuation.CanInvoke(op, options)) { - synchronizationContext.Post(args => action(op), op); + if (synchronizationContext == null || synchronizationContext.GetType() == typeof(SynchronizationContext) || synchronizationContext == SynchronizationContext.Current) + { + action(op); + } + else + { + synchronizationContext.Post(args => action(op), op); + } } } } @@ -486,7 +491,7 @@ public static IAsyncOperation ContinueWith(this T op, Func(this T op, Func ContinueWith(this T op, Func, false); }, - false); + AsyncContinuationOptions.None); } catch (Exception e) { @@ -756,7 +761,7 @@ public static IAsyncOperation ContinueWith(this T op, Func, false); }, - false); + AsyncContinuationOptions.None); } catch (Exception e) { @@ -803,7 +808,7 @@ public static IAsyncOperation TransformWith(this T op, Func resul result.TrySetException(e, false); } }, - false); + AsyncContinuationOptions.None); return result; } @@ -1009,7 +1014,7 @@ public static Task ToTask(this IAsyncOperation op) { var result = new TaskCompletionSource(); - if (!op.TryAddCompletionCallback(asyncOp => AsyncContinuation.InvokeTaskContinuation(asyncOp, result), null)) + if (!op.TryAddCompletionCallback(asyncOp => AsyncContinuation.InvokeTaskContinuation(asyncOp, result), AsyncContinuationOptions.None, null)) { AsyncContinuation.InvokeTaskContinuation(op, result); } @@ -1043,7 +1048,7 @@ public static Task ToTask(this IAsyncOperation op) { var result = new TaskCompletionSource(); - if (!op.TryAddCompletionCallback(asyncOp => AsyncContinuation.InvokeTaskContinuation(asyncOp as IAsyncOperation, result), null)) + if (!op.TryAddCompletionCallback(asyncOp => AsyncContinuation.InvokeTaskContinuation(asyncOp as IAsyncOperation, result), AsyncContinuationOptions.None, null)) { AsyncContinuation.InvokeTaskContinuation(op, result); } @@ -1056,11 +1061,7 @@ private static void SetAwaitedCompletionCallback(IAsyncOperation op, Action cont { var syncContext = continueOnCapturedContext ? SynchronizationContext.Current : null; - if (op is AsyncResult ar) - { - ar.SetContinuationForAwait(continuation, syncContext); - } - else if (!op.TryAddCompletionCallback(o => continuation(), syncContext)) + if (!op.TryAddCompletionCallback(continuation, AsyncContinuationOptions.None, syncContext)) { continuation(); } diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index eb79635..53c08d0 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -19,7 +19,7 @@ namespace UnityFx.Async /// /// This class is the core entity of the library. In many aspects it mimics Task /// interface and behaviour. For example, any instance can have any - /// number of continuations (added either explicitly via + /// number of continuations (added either explicitly via TryAddCompletionCallback /// call or implicitly using async/await keywords). These continuations can be /// invoked on a captured . The class inherits /// (just like Task) and can be used to implement Asynchronous Programming Model (APM). @@ -1280,19 +1280,6 @@ internal void SetCompleted(int status, bool completedSynchronously) OnCompleted(); } - /// - /// Special continuation for the awaiter. - /// - internal void SetContinuationForAwait(Action action, SynchronizationContext syncContext) - { - ThrowIfDisposed(); - - if (!TryAddContinuation(action, syncContext)) - { - action(); - } - } - /// /// Rethrows the specified . /// @@ -1352,7 +1339,11 @@ public void GetResult() public void OnCompleted(Action continuation) { var syncContext = _continueOnCapturedContext ? SynchronizationContext.Current : null; - _op.SetContinuationForAwait(continuation, syncContext); + + if (!_op.TryAddCompletionCallback(continuation, AsyncContinuationOptions.None, syncContext)) + { + continuation(); + } } } @@ -1435,7 +1426,7 @@ public event AsyncOperationCallback Completed throw new ArgumentNullException(nameof(value)); } - if (!TryAddContinuation(value, SynchronizationContext.Current)) + if (!TryAddContinuation(value, AsyncContinuationOptions.None, SynchronizationContext.Current)) { value(this); } @@ -1452,7 +1443,7 @@ public event AsyncOperationCallback Completed } /// - public bool TryAddCompletionCallback(AsyncOperationCallback action, SynchronizationContext syncContext) + public bool TryAddCompletionCallback(AsyncOperationCallback action, AsyncContinuationOptions options, SynchronizationContext syncContext) { ThrowIfDisposed(); @@ -1461,7 +1452,7 @@ public bool TryAddCompletionCallback(AsyncOperationCallback action, Synchronizat throw new ArgumentNullException(nameof(action)); } - return TryAddContinuation(action, syncContext); + return TryAddContinuation(action, options, syncContext); } /// @@ -1477,6 +1468,32 @@ public bool RemoveCompletionCallback(AsyncOperationCallback action) return false; } + /// + public bool TryAddCompletionCallback(Action action, AsyncContinuationOptions options, SynchronizationContext syncContext) + { + ThrowIfDisposed(); + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return TryAddContinuation(action, options, syncContext); + } + + /// + public bool RemoveCompletionCallback(Action action) + { + ThrowIfDisposed(); + + if (action != null) + { + return TryRemoveContinuation(action); + } + + return false; + } + #endregion #region IAsyncResult @@ -1635,13 +1652,18 @@ private AsyncResult(int flags) /// Attempts to register a continuation object. For internal use only. /// /// The continuation object to add. + /// Continuation options. /// A instance to execute continuation on. /// Returns if the continuation was added; otherwise. - private bool TryAddContinuation(object continuation, SynchronizationContext syncContext) + private bool TryAddContinuation(object continuation, AsyncContinuationOptions options, SynchronizationContext syncContext) { - if (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) + if ((options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0) + { + continuation = new AsyncContinuation(this, options, SynchronizationContext.Current, continuation); + } + else if (options != AsyncContinuationOptions.None || (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext))) { - continuation = new AsyncContinuation(this, syncContext, continuation); + continuation = new AsyncContinuation(this, options, syncContext, continuation); } return TryAddContinuation(continuation); diff --git a/src/UnityFx.Async/Api/Core/AsyncResultQueue{T}.cs b/src/UnityFx.Async/Api/Core/AsyncResultQueue{T}.cs index da70aaa..60c84eb 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResultQueue{T}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResultQueue{T}.cs @@ -153,7 +153,7 @@ public bool TryAdd(T op) _completionCallback = OnCompletedCallback; } - if (op.TryAddCompletionCallback(_completionCallback, _syncContext)) + if (op.TryAddCompletionCallback(_completionCallback, AsyncContinuationOptions.None, _syncContext)) { lock (_ops) { diff --git a/src/UnityFx.Async/Api/Core/AsyncResult{T}.cs b/src/UnityFx.Async/Api/Core/AsyncResult{T}.cs index 4f41cca..4671389 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult{T}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult{T}.cs @@ -173,7 +173,11 @@ public T GetResult() public void OnCompleted(Action continuation) { var syncContext = _continueOnCapturedContext ? SynchronizationContext.Current : null; - _op.SetContinuationForAwait(continuation, syncContext); + + if (!_op.TryAddCompletionCallback(continuation, AsyncContinuationOptions.None, syncContext)) + { + continuation(); + } } } @@ -273,7 +277,7 @@ public IDisposable Subscribe(IObserver observer) } }; - if (TryAddCompletionCallback(completionCallback, null)) + if (TryAddCompletionCallback(completionCallback, AsyncContinuationOptions.None, null)) { return new AsyncObservableSubscription(this, completionCallback); } diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs index 41144eb..ffaa437 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs @@ -14,6 +14,54 @@ namespace UnityFx.Async /// public delegate void AsyncOperationCallback(IAsyncOperation op); + /// + /// Specifies the behavior of an asynchronous opration continuation. + /// + [Flags] + public enum AsyncContinuationOptions + { + /// + /// When no continuation options are specified, specifies that default behavior should be used when executing a continuation. + /// I.e. continuation is scheduled independently of the operation completion status. + /// + None = 0, + + /// + /// Specifies that the continuation should not be scheduled if its antecedent ran to completion. + /// + NotOnRanToCompletion = 1, + + /// + /// Specifies that the continuation should not be scheduled if its antecedent threw an unhandled exception. + /// + NotOnFaulted = 2, + + /// + /// Specifies that the continuation should not be scheduled if its antecedent was canceled. + /// + NotOnCanceled = 4, + + /// + /// Specifies that the continuation should be scheduled only if its antecedent ran to completion. + /// + OnlyOnRanToCompletion = NotOnFaulted | NotOnCanceled, + + /// + /// Specifies that the continuation should be scheduled only if its antecedent threw an unhandled exception. + /// + OnlyOnFaulted = NotOnRanToCompletion | NotOnCanceled, + + /// + /// Specifies that the continuation should be scheduled only if its antecedent was canceled. + /// + OnlyOnCanceled = NotOnRanToCompletion | NotOnFaulted, + + /// + /// Specifies whether a should be captured when a continuation is registered. + /// + CaptureSynchronizationContext = 8 + } + /// /// A controller for completion callbacks. /// @@ -29,7 +77,7 @@ public interface IAsyncOperationEvents /// /// Thrown if the delegate being registered is . /// Thrown is the operation has been disposed. - /// + /// /// event AsyncOperationCallback Completed; @@ -38,6 +86,7 @@ public interface IAsyncOperationEvents /// the method does nothing and just returns . /// /// The callback to be executed when the operation has completed. + /// Options for when the callback is executed. /// If not method attempts to marshal the continuation to the synchronization context. /// Otherwise the callback is invoked on a thread that initiated the operation completion. /// @@ -45,7 +94,7 @@ public interface IAsyncOperationEvents /// Thrown if the is . /// Thrown is the operation has been disposed. /// - bool TryAddCompletionCallback(AsyncOperationCallback action, SynchronizationContext syncContext); + bool TryAddCompletionCallback(AsyncOperationCallback action, AsyncContinuationOptions options, SynchronizationContext syncContext); /// /// Removes an existing completion callback. @@ -53,7 +102,31 @@ public interface IAsyncOperationEvents /// The callback to remove. Can be . /// Returns if the was removed; otherwise. /// Thrown is the operation has been disposed. - /// + /// bool RemoveCompletionCallback(AsyncOperationCallback action); + + /// + /// Attempts to add a completion callback to be executed after the operation has finished. If the operation is already completed + /// the method does nothing and just returns . + /// + /// The callback to be executed when the operation has completed. + /// Options for when the callback is executed. + /// If not method attempts to marshal the continuation to the synchronization context. + /// Otherwise the callback is invoked on a thread that initiated the operation completion. + /// + /// Returns if the callback was added; otherwise (the operation is completed). + /// Thrown if the is . + /// Thrown is the operation has been disposed. + /// + bool TryAddCompletionCallback(Action action, AsyncContinuationOptions options, SynchronizationContext syncContext); + + /// + /// Removes an existing completion callback. + /// + /// The callback to remove. Can be . + /// Returns if the was removed; otherwise. + /// Thrown is the operation has been disposed. + /// + bool RemoveCompletionCallback(Action action); } } diff --git a/src/UnityFx.Async/Implementation/AsyncContinuation.cs b/src/UnityFx.Async/Implementation/AsyncContinuation.cs index 6374e5c..803dd86 100644 --- a/src/UnityFx.Async/Implementation/AsyncContinuation.cs +++ b/src/UnityFx.Async/Implementation/AsyncContinuation.cs @@ -16,6 +16,7 @@ internal class AsyncContinuation private static SendOrPostCallback _postCallback; private readonly AsyncResult _op; + private readonly AsyncContinuationOptions _options; private readonly SynchronizationContext _syncContext; private readonly object _continuation; @@ -23,32 +24,51 @@ internal class AsyncContinuation #region interface - internal AsyncContinuation(AsyncResult op, SynchronizationContext syncContext, object continuation) + internal AsyncContinuation(AsyncResult op, AsyncContinuationOptions options, SynchronizationContext syncContext, object continuation) { _op = op; + _options = options; _syncContext = syncContext; _continuation = continuation; } internal void Invoke() { - if (_syncContext == null || _syncContext == SynchronizationContext.Current) + if (CanInvoke(_op, _options)) { - InvokeInternal(_op, _continuation); - } - else - { - if (_postCallback == null) + if (_syncContext == null || _syncContext == SynchronizationContext.Current) { - _postCallback = args => + InvokeInternal(_op, _continuation); + } + else + { + if (_postCallback == null) { - var c = args as AsyncContinuation; - InvokeInternal(c._op, c._continuation); - }; + _postCallback = args => + { + var c = args as AsyncContinuation; + InvokeInternal(c._op, c._continuation); + }; + } + + _syncContext.Post(_postCallback, this); } + } + } - _syncContext.Post(_postCallback, this); + internal static bool CanInvoke(IAsyncOperation op, AsyncContinuationOptions options) + { + if (op.IsCompletedSuccessfully) + { + return (options & AsyncContinuationOptions.NotOnRanToCompletion) == 0; + } + + if (op.IsFaulted) + { + return (options & AsyncContinuationOptions.NotOnFaulted) == 0; } + + return (options & AsyncContinuationOptions.NotOnCanceled) == 0; } internal static void Invoke(IAsyncOperation op, object continuation) diff --git a/src/UnityFx.Async/Implementation/AsyncObservable{T}.cs b/src/UnityFx.Async/Implementation/AsyncObservable{T}.cs index c2dd41b..84c06eb 100644 --- a/src/UnityFx.Async/Implementation/AsyncObservable{T}.cs +++ b/src/UnityFx.Async/Implementation/AsyncObservable{T}.cs @@ -51,7 +51,7 @@ public IDisposable Subscribe(IObserver observer) } }; - if (_op.TryAddCompletionCallback(d, null)) + if (_op.TryAddCompletionCallback(d, AsyncContinuationOptions.None, null)) { return new AsyncObservableSubscription(_op, d); } diff --git a/src/UnityFx.Async/Implementation/RetryResult{T}.cs b/src/UnityFx.Async/Implementation/RetryResult{T}.cs index 5ae58f5..78bd975 100644 --- a/src/UnityFx.Async/Implementation/RetryResult{T}.cs +++ b/src/UnityFx.Async/Implementation/RetryResult{T}.cs @@ -73,7 +73,7 @@ private void StartOperation(bool calledFromConstructor) throw new InvalidOperationException("Invalid delegate type."); } - if (!_op.TryAddCompletionCallback(_opCompletionCallback, null)) + if (!_op.TryAddCompletionCallback(_opCompletionCallback, AsyncContinuationOptions.None, null)) { if (_op.IsCompletedSuccessfully) { diff --git a/src/UnityFx.Async/Implementation/WhenAllResult{T}.cs b/src/UnityFx.Async/Implementation/WhenAllResult{T}.cs index 4a54ff9..94a80c8 100644 --- a/src/UnityFx.Async/Implementation/WhenAllResult{T}.cs +++ b/src/UnityFx.Async/Implementation/WhenAllResult{T}.cs @@ -31,7 +31,7 @@ public WhenAllResult(IAsyncOperation[] ops) foreach (var op in ops) { - if (!op.TryAddCompletionCallback(_completionAction, null)) + if (!op.TryAddCompletionCallback(_completionAction, AsyncContinuationOptions.None, null)) { OnOperationCompleted(op); } diff --git a/src/UnityFx.Async/Implementation/WhenAnyResult{T}.cs b/src/UnityFx.Async/Implementation/WhenAnyResult{T}.cs index 99524cb..575d60f 100644 --- a/src/UnityFx.Async/Implementation/WhenAnyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/WhenAnyResult{T}.cs @@ -24,7 +24,7 @@ public WhenAnyResult(T[] ops) foreach (var op in ops) { - if (!op.TryAddCompletionCallback(_completionAction, null)) + if (!op.TryAddCompletionCallback(_completionAction, AsyncContinuationOptions.None, null)) { TrySetResult(op, true); break; From df86df66db8e0d62d6e6c33cf10b8a9132dd0b03 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 29 Mar 2018 12:51:16 +0300 Subject: [PATCH 002/128] Removed GetAwaiter/ConfigureAwait methods from AsyncResult (extension methods should be used instead) --- src/UnityFx.Async/Api/Core/AsyncExtensions.cs | 79 ++++++++-------- src/UnityFx.Async/Api/Core/AsyncResult.cs | 93 ------------------ src/UnityFx.Async/Api/Core/AsyncResult{T}.cs | 94 ------------------- 3 files changed, 37 insertions(+), 229 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs index ecf6859..19e61f5 100644 --- a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs +++ b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs @@ -49,7 +49,7 @@ public static void SpinUntilCompleted(this IAsyncOperation op) /// /// Throws exception if the operation has failed or canceled. /// - internal static void ThrowIfFaultedOrCanceled(IAsyncOperation op, bool throwAggregate) + internal static void ThrowIfNonSuccess(IAsyncOperation op, bool throwAggregate) { if (op is AsyncResult ar) { @@ -104,7 +104,7 @@ public static void Wait(this IAsyncOperation op) op.AsyncWaitHandle.WaitOne(); } - ThrowIfFaultedOrCanceled(op, true); + ThrowIfNonSuccess(op, true); } /// @@ -129,7 +129,7 @@ public static bool Wait(this IAsyncOperation op, int millisecondsTimeout) if (result) { - ThrowIfFaultedOrCanceled(op, true); + ThrowIfNonSuccess(op, true); } return result; @@ -157,7 +157,7 @@ public static bool Wait(this IAsyncOperation op, TimeSpan timeout) if (result) { - ThrowIfFaultedOrCanceled(op, true); + ThrowIfNonSuccess(op, true); } return result; @@ -182,7 +182,7 @@ public static void Join(this IAsyncOperation op) op.AsyncWaitHandle.WaitOne(); } - ThrowIfFaultedOrCanceled(op, false); + ThrowIfNonSuccess(op, false); } /// @@ -207,7 +207,7 @@ public static void Join(this IAsyncOperation op, int millisecondsTimeout) if (result) { - ThrowIfFaultedOrCanceled(op, false); + ThrowIfNonSuccess(op, false); } else { @@ -237,7 +237,7 @@ public static void Join(this IAsyncOperation op, TimeSpan timeout) if (result) { - ThrowIfFaultedOrCanceled(op, false); + ThrowIfNonSuccess(op, false); } else { @@ -261,7 +261,7 @@ public static T Join(this IAsyncOperation op) op.AsyncWaitHandle.WaitOne(); } - ThrowIfFaultedOrCanceled(op, false); + ThrowIfNonSuccess(op, false); return op.Result; } @@ -288,7 +288,7 @@ public static T Join(this IAsyncOperation op, int millisecondsTimeout) if (result) { - ThrowIfFaultedOrCanceled(op, false); + ThrowIfNonSuccess(op, false); } else { @@ -321,7 +321,7 @@ public static T Join(this IAsyncOperation op, TimeSpan timeout) if (result) { - ThrowIfFaultedOrCanceled(op, false); + ThrowIfNonSuccess(op, false); } else { @@ -826,15 +826,15 @@ public static IAsyncOperation TransformWith(this T op, Func resul public struct AsyncAwaiter : INotifyCompletion { private readonly IAsyncOperation _op; - private readonly bool _continueOnCapturedContext; + private readonly AsyncContinuationOptions _options; /// /// Initializes a new instance of the struct. /// - public AsyncAwaiter(IAsyncOperation op, bool continueOnCapturedContext) + public AsyncAwaiter(IAsyncOperation op, AsyncContinuationOptions options) { _op = op; - _continueOnCapturedContext = continueOnCapturedContext; + _options = options; } /// @@ -848,13 +848,19 @@ public AsyncAwaiter(IAsyncOperation op, bool continueOnCapturedContext) /// public void GetResult() { - GetAwaiterResult(_op); + if (!_op.IsCompletedSuccessfully) + { + ThrowIfNonSuccess(_op, false); + } } /// public void OnCompleted(Action continuation) { - SetAwaitedCompletionCallback(_op, continuation, _continueOnCapturedContext); + if (!_op.TryAddCompletionCallback(continuation, _options, null)) + { + continuation(); + } } } @@ -865,15 +871,15 @@ public void OnCompleted(Action continuation) public struct AsyncAwaiter : INotifyCompletion { private readonly IAsyncOperation _op; - private readonly bool _continueOnCapturedContext; + private readonly AsyncContinuationOptions _options; /// /// Initializes a new instance of the struct. /// - public AsyncAwaiter(IAsyncOperation op, bool continueOnCapturedContext) + public AsyncAwaiter(IAsyncOperation op, AsyncContinuationOptions options) { _op = op; - _continueOnCapturedContext = continueOnCapturedContext; + _options = options; } /// @@ -888,14 +894,21 @@ public AsyncAwaiter(IAsyncOperation op, bool continueOnCapturedContext) /// Returns the underlying operation result. public T GetResult() { - GetAwaiterResult(_op); + if (!_op.IsCompletedSuccessfully) + { + ThrowIfNonSuccess(_op, false); + } + return _op.Result; } /// public void OnCompleted(Action continuation) { - SetAwaitedCompletionCallback(_op, continuation, _continueOnCapturedContext); + if (!_op.TryAddCompletionCallback(continuation, _options, null)) + { + continuation(); + } } } @@ -912,7 +925,7 @@ public struct ConfiguredAsyncAwaitable /// public ConfiguredAsyncAwaitable(IAsyncOperation op, bool continueOnCapturedContext) { - _awaiter = new AsyncAwaiter(op, continueOnCapturedContext); + _awaiter = new AsyncAwaiter(op, continueOnCapturedContext ? AsyncContinuationOptions.CaptureSynchronizationContext : AsyncContinuationOptions.None); } /// @@ -934,7 +947,7 @@ public struct ConfiguredAsyncAwaitable /// public ConfiguredAsyncAwaitable(IAsyncOperation op, bool continueOnCapturedContext) { - _awaiter = new AsyncAwaiter(op, continueOnCapturedContext); + _awaiter = new AsyncAwaiter(op, continueOnCapturedContext ? AsyncContinuationOptions.CaptureSynchronizationContext : AsyncContinuationOptions.None); } /// @@ -950,7 +963,7 @@ public ConfiguredAsyncAwaitable(IAsyncOperation op, bool continueOnCapturedCo /// public static AsyncAwaiter GetAwaiter(this IAsyncOperation op) { - return new AsyncAwaiter(op, true); + return new AsyncAwaiter(op, AsyncContinuationOptions.CaptureSynchronizationContext); } /// @@ -960,7 +973,7 @@ public static AsyncAwaiter GetAwaiter(this IAsyncOperation op) /// public static AsyncAwaiter GetAwaiter(this IAsyncOperation op) { - return new AsyncAwaiter(op, true); + return new AsyncAwaiter(op, AsyncContinuationOptions.CaptureSynchronizationContext); } /// @@ -1057,24 +1070,6 @@ public static Task ToTask(this IAsyncOperation op) } } - private static void SetAwaitedCompletionCallback(IAsyncOperation op, Action continuation, bool continueOnCapturedContext) - { - var syncContext = continueOnCapturedContext ? SynchronizationContext.Current : null; - - if (!op.TryAddCompletionCallback(continuation, AsyncContinuationOptions.None, syncContext)) - { - continuation(); - } - } - - private static void GetAwaiterResult(IAsyncOperation op) - { - if (!op.IsCompletedSuccessfully) - { - ThrowIfFaultedOrCanceled(op, false); - } - } - #endregion #endif diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 53c08d0..17df93e 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -1300,99 +1300,6 @@ internal static bool TryThrowException(AggregateException e) #endregion - #region async/await - -#if UNITYFX_SUPPORT_TAP - - /// - /// Provides an object that waits for the completion of . This type and its members are intended for compiler use only. - /// - public struct AsyncAwaiter : INotifyCompletion - { - private readonly AsyncResult _op; - private readonly bool _continueOnCapturedContext; - - /// - /// Initializes a new instance of the struct. - /// - public AsyncAwaiter(AsyncResult op, bool continueOnCapturedContext) - { - _op = op; - _continueOnCapturedContext = continueOnCapturedContext; - } - - /// - /// Gets a value indicating whether the underlying operation is completed. - /// - /// The operation completion flag. - public bool IsCompleted => _op.IsCompleted; - - /// - /// Returns the source result value. - /// - public void GetResult() - { - _op.ThrowIfNonSuccess(false); - } - - /// - public void OnCompleted(Action continuation) - { - var syncContext = _continueOnCapturedContext ? SynchronizationContext.Current : null; - - if (!_op.TryAddCompletionCallback(continuation, AsyncContinuationOptions.None, syncContext)) - { - continuation(); - } - } - } - - /// - /// Provides an awaitable object that allows for configured awaits on . This type is intended for compiler use only. - /// - public struct ConfiguredAsyncAwaitable - { - private readonly AsyncAwaiter _awaiter; - - /// - /// Initializes a new instance of the struct. - /// - public ConfiguredAsyncAwaitable(AsyncResult op, bool continueOnCapturedContext) - { - _awaiter = new AsyncAwaiter(op, continueOnCapturedContext); - } - - /// - /// Returns the awaiter. - /// - public AsyncAwaiter GetAwaiter() - { - return _awaiter; - } - } - - /// - /// Returns the operation awaiter. This method is intended for compiler rather than use directly in code. - /// - public AsyncAwaiter GetAwaiter() - { - return new AsyncAwaiter(this, true); - } - - /// - /// Configures an awaiter used to await this operation. - /// - /// If attempts to marshal the continuation back to the original context captured. - /// An object used to await the operation. - public ConfiguredAsyncAwaitable ConfigureAwait(bool continueOnCapturedContext) - { - return new ConfiguredAsyncAwaitable(this, continueOnCapturedContext); - } - -#endif - - #endregion - #region IAsyncOperation /// diff --git a/src/UnityFx.Async/Api/Core/AsyncResult{T}.cs b/src/UnityFx.Async/Api/Core/AsyncResult{T}.cs index 4671389..151a559 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult{T}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult{T}.cs @@ -133,100 +133,6 @@ protected internal bool TrySetResult(T result, bool completedSynchronously) #endregion - #region async/await - -#if UNITYFX_SUPPORT_TAP - - /// - /// Provides an object that waits for the completion of . This type and its members are intended for compiler use only. - /// - public new struct AsyncAwaiter : INotifyCompletion - { - private readonly AsyncResult _op; - private readonly bool _continueOnCapturedContext; - - /// - /// Initializes a new instance of the struct. - /// - public AsyncAwaiter(AsyncResult op, bool continueOnCapturedContext) - { - _op = op; - _continueOnCapturedContext = continueOnCapturedContext; - } - - /// - /// Gets a value indicating whether the underlying operation is completed. - /// - /// The operation completion flag. - public bool IsCompleted => _op.IsCompleted; - - /// - /// Returns the source result value. - /// - public T GetResult() - { - _op.ThrowIfNonSuccess(false); - return _op.Result; - } - - /// - public void OnCompleted(Action continuation) - { - var syncContext = _continueOnCapturedContext ? SynchronizationContext.Current : null; - - if (!_op.TryAddCompletionCallback(continuation, AsyncContinuationOptions.None, syncContext)) - { - continuation(); - } - } - } - - /// - /// Provides an awaitable object that allows for configured awaits on . This type is intended for compiler use only. - /// - public new struct ConfiguredAsyncAwaitable - { - private readonly AsyncAwaiter _awaiter; - - /// - /// Initializes a new instance of the struct. - /// - public ConfiguredAsyncAwaitable(AsyncResult op, bool continueOnCapturedContext) - { - _awaiter = new AsyncAwaiter(op, continueOnCapturedContext); - } - - /// - /// Returns the awaiter. - /// - public AsyncAwaiter GetAwaiter() - { - return _awaiter; - } - } - - /// - /// Returns the operation awaiter. This method is intended for compiler rather than use directly in code. - /// - public new AsyncAwaiter GetAwaiter() - { - return new AsyncAwaiter(this, true); - } - - /// - /// Configures an awaiter used to await this operation. - /// - /// If attempts to marshal the continuation back to the original context captured. - /// An object used to await the operation. - public new ConfiguredAsyncAwaitable ConfigureAwait(bool continueOnCapturedContext) - { - return new ConfiguredAsyncAwaitable(this, continueOnCapturedContext); - } - -#endif - - #endregion - #region IAsyncOperation /// From ead33d6588538da4605af9fdf1be68b539b4ecbe Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 29 Mar 2018 15:12:15 +0300 Subject: [PATCH 003/128] Added pilot Then implementation --- src/UnityFx.Async/Api/Core/AsyncExtensions.cs | 111 ++++++++++++++++-- .../Api/Interfaces/IAsyncOperationEvents.cs | 6 +- 2 files changed, 106 insertions(+), 11 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs index 19e61f5..7b8a4ca 100644 --- a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs +++ b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs @@ -333,6 +333,101 @@ public static T Join(this IAsyncOperation op, TimeSpan timeout) #endregion + #region Then + + /// + /// Adds a completion callback to be executed after the operation has succeeded. + /// + /// The target operation. + /// The callback to be executed when the operation has completed. + public static IAsyncOperation Then(this IAsyncOperation op, Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback( + asyncOp => + { + try + { + if (asyncOp.IsCompletedSuccessfully) + { + action(); + result.TrySetCompleted(); + } + else if (asyncOp.IsFaulted) + { + result.TrySetException(asyncOp.Exception); + } + else + { + result.TrySetCanceled(); + } + } + catch (Exception e) + { + result.TrySetException(e); + } + }, + AsyncContinuationOptions.CaptureSynchronizationContext); + + return result; + } + + /// + /// Adds a completion callback to be executed after the operation has succeeded. + /// + /// The target operation. + /// The callback to be executed when the operation has completed. + public static IAsyncOperation Then(this IAsyncOperation op, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback( + asyncOp => + { + try + { + if (asyncOp.IsCompletedSuccessfully) + { + action().AddCompletionCallback(asyncOp2 => result.CopyCompletionState(asyncOp2, false), AsyncContinuationOptions.None); + } + else if (asyncOp.IsFaulted) + { + result.TrySetException(asyncOp.Exception); + } + else + { + result.TrySetCanceled(); + } + } + catch (Exception e) + { + result.TrySetException(e); + } + }, + AsyncContinuationOptions.CaptureSynchronizationContext); + + return result; + } + + #endregion + + #region Catch + #endregion + + #region Finally + #endregion + #region AddCompletionCallback /// @@ -364,9 +459,7 @@ public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperation /// public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action, AsyncContinuationOptions options) { - var syncContext = ((options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0) ? SynchronizationContext.Current : null; - - if (!op.TryAddCompletionCallback(action, options, syncContext)) + if (!op.TryAddCompletionCallback(action, options, null)) { if (AsyncContinuation.CanInvoke(op, options)) { @@ -377,31 +470,31 @@ public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperation /// /// Adds a completion callback to be executed after the operation has finished. If the operation is completed the is invoked - /// on the specified. + /// on the specified. /// /// The target operation. /// The callback to be executed when the operation has completed. /// Options for when the callback is executed. - /// If not method attempts to marshal the continuation to the synchronization context. + /// If not method attempts to marshal the continuation to the synchronization context. /// Otherwise the callback is invoked on a thread that initiated the operation completion. /// /// Thrown if the is . /// Thrown is the operation has been disposed. /// /// - public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action, AsyncContinuationOptions options, SynchronizationContext synchronizationContext) + public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action, AsyncContinuationOptions options, SynchronizationContext syncContext) { - if (!op.TryAddCompletionCallback(action, options, synchronizationContext)) + if (!op.TryAddCompletionCallback(action, options, syncContext)) { if (AsyncContinuation.CanInvoke(op, options)) { - if (synchronizationContext == null || synchronizationContext.GetType() == typeof(SynchronizationContext) || synchronizationContext == SynchronizationContext.Current) + if (syncContext == null || syncContext.GetType() == typeof(SynchronizationContext) || syncContext == SynchronizationContext.Current) { action(op); } else { - synchronizationContext.Post(args => action(op), op); + syncContext.Post(args => action(op), op); } } } diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs index ffaa437..76cf309 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs @@ -88,7 +88,8 @@ public interface IAsyncOperationEvents /// The callback to be executed when the operation has completed. /// Options for when the callback is executed. /// If not method attempts to marshal the continuation to the synchronization context. - /// Otherwise the callback is invoked on a thread that initiated the operation completion. + /// Otherwise the callback is invoked on a thread that initiated the operation completion. The argument value is ignored if + /// is set to . /// /// Returns if the callback was added; otherwise (the operation is completed). /// Thrown if the is . @@ -112,7 +113,8 @@ public interface IAsyncOperationEvents /// The callback to be executed when the operation has completed. /// Options for when the callback is executed. /// If not method attempts to marshal the continuation to the synchronization context. - /// Otherwise the callback is invoked on a thread that initiated the operation completion. + /// Otherwise the callback is invoked on a thread that initiated the operation completion. The argument value is ignored if + /// is set to . /// /// Returns if the callback was added; otherwise (the operation is completed). /// Thrown if the is . From 147aa8c767e0384c896a39299ffd833f29693f0a Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 29 Mar 2018 15:33:26 +0300 Subject: [PATCH 004/128] Added Catch/Finally stubs --- src/UnityFx.Async/Api/Core/AsyncExtensions.cs | 131 ++++++++++++++++-- 1 file changed, 121 insertions(+), 10 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs index 7b8a4ca..b30c51b 100644 --- a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs +++ b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs @@ -339,12 +339,13 @@ public static T Join(this IAsyncOperation op, TimeSpan timeout) /// Adds a completion callback to be executed after the operation has succeeded. /// /// The target operation. - /// The callback to be executed when the operation has completed. - public static IAsyncOperation Then(this IAsyncOperation op, Action action) + /// The callback to be executed when the operation has completed. + /// + public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback) { - if (action == null) + if (successCallback == null) { - throw new ArgumentNullException(nameof(action)); + throw new ArgumentNullException(nameof(successCallback)); } var result = new AsyncCompletionSource(AsyncOperationStatus.Running); @@ -356,7 +357,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action action) { if (asyncOp.IsCompletedSuccessfully) { - action(); + successCallback(); result.TrySetCompleted(); } else if (asyncOp.IsFaulted) @@ -382,12 +383,13 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action action) /// Adds a completion callback to be executed after the operation has succeeded. /// /// The target operation. - /// The callback to be executed when the operation has completed. - public static IAsyncOperation Then(this IAsyncOperation op, Func action) + /// The callback to be executed when the operation has completed. + /// + public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback) { - if (action == null) + if (successCallback == null) { - throw new ArgumentNullException(nameof(action)); + throw new ArgumentNullException(nameof(successCallback)); } var result = new AsyncCompletionSource(AsyncOperationStatus.Running); @@ -399,7 +401,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func result.CopyCompletionState(asyncOp2, false), AsyncContinuationOptions.None); + successCallback().AddCompletionCallback(asyncOp2 => result.CopyCompletionState(asyncOp2, false), AsyncContinuationOptions.None); } else if (asyncOp.IsFaulted) { @@ -420,12 +422,121 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func + /// Adds a completion callback to be executed after the operation has succeeded. + /// + /// The target operation. + /// The callback to be executed when the operation has succeeded. + /// The callback to be executed when the operation has faulted/was canceled. + /// + public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback, Action errorCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + if (errorCallback == null) + { + throw new ArgumentNullException(nameof(errorCallback)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback( + asyncOp => + { + try + { + if (asyncOp.IsCompletedSuccessfully) + { + successCallback(); + result.TrySetCompleted(); + } + else if (asyncOp.IsFaulted) + { + errorCallback(asyncOp.Exception.InnerException); + result.TrySetException(asyncOp.Exception); + } + else + { + errorCallback(new OperationCanceledException()); + result.TrySetCanceled(); + } + } + catch (Exception e) + { + errorCallback(e); + result.TrySetException(e); + } + }, + AsyncContinuationOptions.CaptureSynchronizationContext); + + return result; + } + + /// + /// Adds a completion callback to be executed after the operation has succeeded. + /// + /// The target operation. + /// The callback to be executed when the operation has succeeded. + /// The callback to be executed when the operation has faulted/was canceled. + /// + public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback, Action errorCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + if (errorCallback == null) + { + throw new ArgumentNullException(nameof(errorCallback)); + } + + throw new NotImplementedException(); + } + #endregion #region Catch + + /// + /// Adds a completion callback to be executed after the operation has faulted or was canceled. + /// + /// The target operation. + /// The callback to be executed when the operation has faulted/was canceled. + /// + public static IAsyncOperation Catch(this IAsyncOperation op, Action errorCallback) + { + if (errorCallback == null) + { + throw new ArgumentNullException(nameof(errorCallback)); + } + + throw new NotImplementedException(); + } + #endregion #region Finally + + /// + /// Adds a completion callback to be executed after the operation has completed. + /// + /// The target operation. + /// The callback to be executed when the operation has completed. + /// + public static IAsyncOperation Finally(this IAsyncOperation op, Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + throw new NotImplementedException(); + } + #endregion #region AddCompletionCallback From f6cc5686b42d8ce7375595724fcaab60a0651e37 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 29 Mar 2018 18:26:57 +0300 Subject: [PATCH 005/128] Minor continuation processing changes --- src/UnityFx.Async/Api/Core/AsyncExtensions.cs | 4 ++-- src/UnityFx.Async/Api/Core/AsyncResult.cs | 10 ++++++---- .../Api/Interfaces/IAsyncOperationEvents.cs | 8 ++------ 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs index b30c51b..60f475e 100644 --- a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs +++ b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs @@ -1061,7 +1061,7 @@ public void GetResult() /// public void OnCompleted(Action continuation) { - if (!_op.TryAddCompletionCallback(continuation, _options, null)) + if (!_op.TryAddCompletionCallback(continuation, _options)) { continuation(); } @@ -1109,7 +1109,7 @@ public T GetResult() /// public void OnCompleted(Action continuation) { - if (!_op.TryAddCompletionCallback(continuation, _options, null)) + if (!_op.TryAddCompletionCallback(continuation, _options)) { continuation(); } diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 17df93e..fec6d33 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -1376,7 +1376,7 @@ public bool RemoveCompletionCallback(AsyncOperationCallback action) } /// - public bool TryAddCompletionCallback(Action action, AsyncContinuationOptions options, SynchronizationContext syncContext) + public bool TryAddCompletionCallback(Action action, AsyncContinuationOptions options) { ThrowIfDisposed(); @@ -1385,7 +1385,7 @@ public bool TryAddCompletionCallback(Action action, AsyncContinuationOptions opt throw new ArgumentNullException(nameof(action)); } - return TryAddContinuation(action, options, syncContext); + return TryAddContinuation(action, options, null); } /// @@ -1566,9 +1566,11 @@ private bool TryAddContinuation(object continuation, AsyncContinuationOptions op { if ((options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0) { - continuation = new AsyncContinuation(this, options, SynchronizationContext.Current, continuation); + syncContext = SynchronizationContext.Current; + options &= ~AsyncContinuationOptions.CaptureSynchronizationContext; } - else if (options != AsyncContinuationOptions.None || (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext))) + + if (options != AsyncContinuationOptions.None || (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext))) { continuation = new AsyncContinuation(this, options, syncContext, continuation); } diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs index 76cf309..fbdbb50 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs @@ -112,15 +112,11 @@ public interface IAsyncOperationEvents /// /// The callback to be executed when the operation has completed. /// Options for when the callback is executed. - /// If not method attempts to marshal the continuation to the synchronization context. - /// Otherwise the callback is invoked on a thread that initiated the operation completion. The argument value is ignored if - /// is set to . - /// /// Returns if the callback was added; otherwise (the operation is completed). /// Thrown if the is . /// Thrown is the operation has been disposed. /// - bool TryAddCompletionCallback(Action action, AsyncContinuationOptions options, SynchronizationContext syncContext); + bool TryAddCompletionCallback(Action action, AsyncContinuationOptions options); /// /// Removes an existing completion callback. @@ -128,7 +124,7 @@ public interface IAsyncOperationEvents /// The callback to remove. Can be . /// Returns if the was removed; otherwise. /// Thrown is the operation has been disposed. - /// + /// bool RemoveCompletionCallback(Action action); } } From 61d95b177a6c1113d5f83c4b0e5e225919d1f709 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 29 Mar 2018 19:09:08 +0300 Subject: [PATCH 006/128] Catch/Finally implemented --- src/UnityFx.Async/Api/Core/AsyncExtensions.cs | 216 +++++++++++++++++- 1 file changed, 209 insertions(+), 7 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs index 60f475e..9e0b3cd 100644 --- a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs +++ b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs @@ -336,10 +336,11 @@ public static T Join(this IAsyncOperation op, TimeSpan timeout) #region Then /// - /// Adds a completion callback to be executed after the operation has succeeded. + /// Schedules a callback to be executed after the operation has succeeded. /// /// The target operation. /// The callback to be executed when the operation has completed. + /// TODO /// public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback) { @@ -380,7 +381,52 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba } /// - /// Adds a completion callback to be executed after the operation has succeeded. + /// Schedules a callback to be executed after the operation has succeeded. + /// + /// The target operation. + /// The callback to be executed when the operation has completed. + /// TODO + /// + public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback( + asyncOp => + { + try + { + if (asyncOp.IsCompletedSuccessfully) + { + successCallback((asyncOp as IAsyncOperation).Result); + result.TrySetCompleted(); + } + else if (asyncOp.IsFaulted) + { + result.TrySetException(asyncOp.Exception); + } + else + { + result.TrySetCanceled(); + } + } + catch (Exception e) + { + result.TrySetException(e); + } + }, + AsyncContinuationOptions.CaptureSynchronizationContext); + + return result; + } + + /// + /// Schedules a callback to be executed after the operation has succeeded. /// /// The target operation. /// The callback to be executed when the operation has completed. @@ -401,7 +447,9 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func result.CopyCompletionState(asyncOp2, false), AsyncContinuationOptions.None); + successCallback().AddCompletionCallback( + asyncOp2 => result.CopyCompletionState(asyncOp2, false), + AsyncContinuationOptions.None); } else if (asyncOp.IsFaulted) { @@ -423,7 +471,52 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func - /// Adds a completion callback to be executed after the operation has succeeded. + /// Schedules a callback to be executed after the operation has succeeded. + /// + /// The target operation. + /// The callback to be executed when the operation has completed. + /// + public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback( + asyncOp => + { + try + { + if (asyncOp.IsCompletedSuccessfully) + { + successCallback((asyncOp as IAsyncOperation).Result).AddCompletionCallback( + asyncOp2 => result.CopyCompletionState(asyncOp2, false), + AsyncContinuationOptions.None); + } + else if (asyncOp.IsFaulted) + { + result.TrySetException(asyncOp.Exception); + } + else + { + result.TrySetCanceled(); + } + } + catch (Exception e) + { + result.TrySetException(e); + } + }, + AsyncContinuationOptions.CaptureSynchronizationContext); + + return result; + } + + /// + /// Schedules a callbacks to be executed after the operation has completed. /// /// The target operation. /// The callback to be executed when the operation has succeeded. @@ -466,7 +559,58 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba } catch (Exception e) { - errorCallback(e); + result.TrySetException(e); + } + }, + AsyncContinuationOptions.CaptureSynchronizationContext); + + return result; + } + + /// + /// Schedules a callbacks to be executed after the operation has completed. + /// + /// The target operation. + /// The callback to be executed when the operation has succeeded. + /// The callback to be executed when the operation has faulted/was canceled. + /// + public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback, Action errorCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + if (errorCallback == null) + { + throw new ArgumentNullException(nameof(errorCallback)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback( + asyncOp => + { + try + { + if (asyncOp.IsCompletedSuccessfully) + { + successCallback((asyncOp as IAsyncOperation).Result); + result.TrySetCompleted(); + } + else if (asyncOp.IsFaulted) + { + errorCallback(asyncOp.Exception.InnerException); + result.TrySetException(asyncOp.Exception); + } + else + { + errorCallback(new OperationCanceledException()); + result.TrySetCanceled(); + } + } + catch (Exception e) + { result.TrySetException(e); } }, @@ -514,7 +658,36 @@ public static IAsyncOperation Catch(this IAsyncOperation op, Action e throw new ArgumentNullException(nameof(errorCallback)); } - throw new NotImplementedException(); + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback( + asyncOp => + { + try + { + if (asyncOp.IsCompletedSuccessfully) + { + result.TrySetCompleted(); + } + else if (asyncOp.IsFaulted) + { + errorCallback(asyncOp.Exception.InnerException); + result.TrySetCompleted(); + } + else + { + errorCallback(new OperationCanceledException()); + result.TrySetCompleted(); + } + } + catch (Exception e) + { + result.TrySetException(e); + } + }, + AsyncContinuationOptions.CaptureSynchronizationContext); + + return result; } #endregion @@ -534,7 +707,36 @@ public static IAsyncOperation Finally(this IAsyncOperation op, Action action) throw new ArgumentNullException(nameof(action)); } - throw new NotImplementedException(); + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback( + asyncOp => + { + try + { + action(); + + if (asyncOp.IsCompletedSuccessfully) + { + result.TrySetCompleted(); + } + else if (asyncOp.IsFaulted) + { + result.TrySetCompleted(); + } + else + { + result.TrySetCompleted(); + } + } + catch (Exception e) + { + result.TrySetException(e); + } + }, + AsyncContinuationOptions.CaptureSynchronizationContext); + + return result; } #endregion From 75ffb11d9b1d181764b0f90bd6f3e24e0da22ef5 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 29 Mar 2018 19:23:15 +0300 Subject: [PATCH 007/128] More Then overloads --- src/UnityFx.Async/Api/Core/AsyncExtensions.cs | 86 ++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs index 9e0b3cd..d949056 100644 --- a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs +++ b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs @@ -638,7 +638,91 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func + { + try + { + if (asyncOp.IsCompletedSuccessfully) + { + successCallback().AddCompletionCallback( + asyncOp2 => result.CopyCompletionState(asyncOp2, false), + AsyncContinuationOptions.None); + } + else if (asyncOp.IsFaulted) + { + errorCallback(asyncOp.Exception.InnerException); + result.TrySetException(asyncOp.Exception); + } + else + { + errorCallback(new OperationCanceledException()); + result.TrySetCanceled(); + } + } + catch (Exception e) + { + result.TrySetException(e); + } + }, + AsyncContinuationOptions.CaptureSynchronizationContext); + + return result; + } + + /// + /// Adds a completion callback to be executed after the operation has succeeded. + /// + /// The target operation. + /// The callback to be executed when the operation has succeeded. + /// The callback to be executed when the operation has faulted/was canceled. + /// + public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback, Action errorCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + if (errorCallback == null) + { + throw new ArgumentNullException(nameof(errorCallback)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback( + asyncOp => + { + try + { + if (asyncOp.IsCompletedSuccessfully) + { + successCallback((asyncOp as IAsyncOperation).Result).AddCompletionCallback( + asyncOp2 => result.CopyCompletionState(asyncOp2, false), + AsyncContinuationOptions.None); + } + else if (asyncOp.IsFaulted) + { + errorCallback(asyncOp.Exception.InnerException); + result.TrySetException(asyncOp.Exception); + } + else + { + errorCallback(new OperationCanceledException()); + result.TrySetCanceled(); + } + } + catch (Exception e) + { + result.TrySetException(e); + } + }, + AsyncContinuationOptions.CaptureSynchronizationContext); + + return result; } #endregion From 8750fc7d9b827deae863a26ccdbbca39ca9f3431 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 29 Mar 2018 19:42:58 +0300 Subject: [PATCH 008/128] Exception handling fixes --- .../Tests/AsyncResultTests.cs | 26 ++++++++++-- src/UnityFx.Async/Api/Core/AsyncExtensions.cs | 4 +- src/UnityFx.Async/Api/Core/AsyncResult.cs | 41 +++++++++++++++++-- 3 files changed, 61 insertions(+), 10 deletions(-) diff --git a/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs b/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs index 37fbc62..72b8f8b 100644 --- a/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs +++ b/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs @@ -141,16 +141,16 @@ public void FromException_ReturnsFailedOperation() } [Fact] - public void FromException_ReturnsFailedOperation_Generic() + public void FromException_ReturnsCanceledOperation() { // Arrange - var e = new InvalidCastException(); + var e = new OperationCanceledException(); // Act - var op = AsyncResult.FromException(e); + var op = AsyncResult.FromException(e); // Assert - AssertFaulted(op, e); + AssertCanceled(op); Assert.True(op.CompletedSynchronously); } @@ -567,6 +567,24 @@ public void TrySetException_SetsStatusToFaulted(AsyncOperationStatus status) Assert.True(result); } + [Theory] + [InlineData(AsyncOperationStatus.Created)] + [InlineData(AsyncOperationStatus.Scheduled)] + [InlineData(AsyncOperationStatus.Running)] + public void TrySetException_SetsStatusToCanceled(AsyncOperationStatus status) + { + // Arrange + var e = new OperationCanceledException(); + var op = new AsyncCompletionSource(status); + + // Act + var result = op.TrySetException(e); + + // Assert + AssertCanceled(op); + Assert.True(result); + } + [Fact] public void TrySetException_RaisesCompletionCallbacks() { diff --git a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs index d949056..40ffdab 100644 --- a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs +++ b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs @@ -68,7 +68,7 @@ internal static void ThrowIfNonSuccess(IAsyncOperation op, bool throwAggregate) else if (!AsyncResult.TryThrowException(op.Exception)) { // Should never get here. If faulted state excpetion should not be null. - throw new Exception(op.ToString()); + throw new Exception(); } } else if (status == AsyncOperationStatus.Canceled) @@ -838,7 +838,7 @@ public static IAsyncOperation Finally(this IAsyncOperation op, Action action) /// public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action) { - if (!op.TryAddCompletionCallback(action, AsyncContinuationOptions.CaptureSynchronizationContext, null)) + if (!op.TryAddCompletionCallback(action, AsyncContinuationOptions.None, SynchronizationContext.Current)) { action(op); } diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index fec6d33..43c5ea1 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -152,8 +152,16 @@ internal AsyncResult(Exception exception, object asyncState) _exception = new AggregateException(exception); } + if (_exception.InnerException is OperationCanceledException) + { + _flags = StatusCanceled | _flagCompletedSynchronously; + } + else + { + _flags = StatusFaulted | _flagCompletedSynchronously; + } + _continuation = _continuationCompletionSentinel; - _flags = StatusFaulted | _flagCompletedSynchronously; _asyncState = asyncState; } @@ -171,8 +179,17 @@ internal AsyncResult(IEnumerable exceptions, object asyncState) } _exception = new AggregateException(exceptions); + + if (_exception.InnerException is OperationCanceledException) + { + _flags = StatusCanceled | _flagCompletedSynchronously; + } + else + { + _flags = StatusFaulted | _flagCompletedSynchronously; + } + _continuation = _continuationCompletionSentinel; - _flags = StatusFaulted | _flagCompletedSynchronously; _asyncState = asyncState; } @@ -299,7 +316,14 @@ protected internal bool TrySetException(Exception exception, bool completedSynch _exception = new AggregateException(exception); } - SetCompleted(StatusFaulted, completedSynchronously); + if (_exception.InnerException is OperationCanceledException) + { + SetCompleted(StatusCanceled, completedSynchronously); + } + else + { + SetCompleted(StatusFaulted, completedSynchronously); + } } return true; @@ -350,7 +374,16 @@ protected internal bool TrySetExceptions(IEnumerable exceptions, bool if (TryReserveCompletion()) { _exception = new AggregateException(list); - SetCompleted(StatusFaulted, completedSynchronously); + + if (_exception.InnerException is OperationCanceledException) + { + SetCompleted(StatusCanceled, completedSynchronously); + } + else + { + SetCompleted(StatusFaulted, completedSynchronously); + } + return true; } else if (!IsCompleted) From 1b7e838f6421d90dcec55d2c157851e59a625cc8 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 29 Mar 2018 19:46:56 +0300 Subject: [PATCH 009/128] Changed AsyncResult.Exception to always return exception when the operation is completed --- src/UnityFx.Async/Api/Core/AsyncResult.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 43c5ea1..197ee11 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -1339,7 +1339,7 @@ internal static bool TryThrowException(AggregateException e) public AsyncOperationStatus Status => (AsyncOperationStatus)(_flags & _statusMask); /// - public AggregateException Exception => (_flags & _statusMask) == StatusFaulted ? _exception : null; + public AggregateException Exception => (_flags & _flagCompleted) != 0 ? _exception : null; /// public bool IsCompletedSuccessfully => (_flags & _statusMask) == StatusRanToCompletion; From 73bb5a75aba3005b9006244669f50c4433442800 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 29 Mar 2018 19:54:31 +0300 Subject: [PATCH 010/128] Minor optimizations --- .../Tests/AsyncResultTests.cs | 13 +++- src/UnityFx.Async/Api/Core/AsyncExtensions.cs | 77 +++---------------- 2 files changed, 23 insertions(+), 67 deletions(-) diff --git a/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs b/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs index 72b8f8b..fd5d007 100644 --- a/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs +++ b/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs @@ -150,7 +150,7 @@ public void FromException_ReturnsCanceledOperation() var op = AsyncResult.FromException(e); // Assert - AssertCanceled(op); + AssertCanceled(op, e); Assert.True(op.CompletedSynchronously); } @@ -1011,6 +1011,16 @@ private void AssertCompletedWithResult(IAsyncOperation op, T result) Assert.Null(op.Exception); } + private void AssertCanceled(IAsyncOperation op, OperationCanceledException e) + { + Assert.Equal(AsyncOperationStatus.Canceled, op.Status); + Assert.Equal(e, op.Exception.InnerException); + Assert.True(op.IsCompleted); + Assert.True(op.IsCanceled); + Assert.False(op.IsCompletedSuccessfully); + Assert.False(op.IsFaulted); + } + private void AssertCanceled(IAsyncOperation op) { Assert.Equal(AsyncOperationStatus.Canceled, op.Status); @@ -1018,7 +1028,6 @@ private void AssertCanceled(IAsyncOperation op) Assert.True(op.IsCanceled); Assert.False(op.IsCompletedSuccessfully); Assert.False(op.IsFaulted); - Assert.Null(op.Exception); } private void AssertFaulted(IAsyncOperation op, Exception e) diff --git a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs index 40ffdab..8bdd20b 100644 --- a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs +++ b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs @@ -75,9 +75,9 @@ internal static void ThrowIfNonSuccess(IAsyncOperation op, bool throwAggregate) { if (throwAggregate) { - throw new AggregateException(new OperationCanceledException()); + throw op.Exception; } - else + else if (!AsyncResult.TryThrowException(op.Exception)) { throw new OperationCanceledException(); } @@ -361,13 +361,9 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba successCallback(); result.TrySetCompleted(); } - else if (asyncOp.IsFaulted) - { - result.TrySetException(asyncOp.Exception); - } else { - result.TrySetCanceled(); + result.TrySetException(asyncOp.Exception); } } catch (Exception e) @@ -406,13 +402,9 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action succ successCallback((asyncOp as IAsyncOperation).Result); result.TrySetCompleted(); } - else if (asyncOp.IsFaulted) - { - result.TrySetException(asyncOp.Exception); - } else { - result.TrySetCanceled(); + result.TrySetException(asyncOp.Exception); } } catch (Exception e) @@ -451,13 +443,9 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func result.CopyCompletionState(asyncOp2, false), AsyncContinuationOptions.None); } - else if (asyncOp.IsFaulted) - { - result.TrySetException(asyncOp.Exception); - } else { - result.TrySetCanceled(); + result.TrySetException(asyncOp.Exception); } } catch (Exception e) @@ -496,13 +484,9 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func result.CopyCompletionState(asyncOp2, false), AsyncContinuationOptions.None); } - else if (asyncOp.IsFaulted) - { - result.TrySetException(asyncOp.Exception); - } else { - result.TrySetCanceled(); + result.TrySetException(asyncOp.Exception); } } catch (Exception e) @@ -546,16 +530,11 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba successCallback(); result.TrySetCompleted(); } - else if (asyncOp.IsFaulted) + else { errorCallback(asyncOp.Exception.InnerException); result.TrySetException(asyncOp.Exception); } - else - { - errorCallback(new OperationCanceledException()); - result.TrySetCanceled(); - } } catch (Exception e) { @@ -598,16 +577,11 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action succ successCallback((asyncOp as IAsyncOperation).Result); result.TrySetCompleted(); } - else if (asyncOp.IsFaulted) + else { errorCallback(asyncOp.Exception.InnerException); result.TrySetException(asyncOp.Exception); } - else - { - errorCallback(new OperationCanceledException()); - result.TrySetCanceled(); - } } catch (Exception e) { @@ -651,16 +625,11 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func result.CopyCompletionState(asyncOp2, false), AsyncContinuationOptions.None); } - else if (asyncOp.IsFaulted) + else { errorCallback(asyncOp.Exception.InnerException); result.TrySetException(asyncOp.Exception); } - else - { - errorCallback(new OperationCanceledException()); - result.TrySetCanceled(); - } } catch (Exception e) { @@ -704,16 +673,11 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func result.CopyCompletionState(asyncOp2, false), AsyncContinuationOptions.None); } - else if (asyncOp.IsFaulted) + else { errorCallback(asyncOp.Exception.InnerException); result.TrySetException(asyncOp.Exception); } - else - { - errorCallback(new OperationCanceledException()); - result.TrySetCanceled(); - } } catch (Exception e) { @@ -753,14 +717,9 @@ public static IAsyncOperation Catch(this IAsyncOperation op, Action e { result.TrySetCompleted(); } - else if (asyncOp.IsFaulted) - { - errorCallback(asyncOp.Exception.InnerException); - result.TrySetCompleted(); - } else { - errorCallback(new OperationCanceledException()); + errorCallback(asyncOp.Exception.InnerException); result.TrySetCompleted(); } } @@ -799,19 +758,7 @@ public static IAsyncOperation Finally(this IAsyncOperation op, Action action) try { action(); - - if (asyncOp.IsCompletedSuccessfully) - { - result.TrySetCompleted(); - } - else if (asyncOp.IsFaulted) - { - result.TrySetCompleted(); - } - else - { - result.TrySetCompleted(); - } + result.TrySetCompleted(); } catch (Exception e) { From 5a9e634d5682a88a311c799a86ffe07c2556e0a7 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 29 Mar 2018 19:59:59 +0300 Subject: [PATCH 011/128] Added AddCompletionCallback extensions corresponding to the Action versions of TryAddCompletionCallback --- src/UnityFx.Async/Api/Core/AsyncExtensions.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs index 8bdd20b..70250e2 100644 --- a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs +++ b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs @@ -782,6 +782,7 @@ public static IAsyncOperation Finally(this IAsyncOperation op, Action action) /// Thrown if the is . /// Thrown is the operation has been disposed. /// + /// /// public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action) { @@ -791,6 +792,23 @@ public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperation } } + /// + /// Adds a completion callback to be executed after the operation has finished. If the operation is completed the is invoked synchronously. + /// + /// The target operation. + /// The callback to be executed when the operation has completed. + /// Thrown if the is . + /// Thrown is the operation has been disposed. + /// + /// + public static void AddCompletionCallback(this IAsyncOperation op, Action action) + { + if (!op.TryAddCompletionCallback(action, AsyncContinuationOptions.CaptureSynchronizationContext)) + { + action(); + } + } + /// /// Adds a completion callback to be executed after the operation has finished. If the operation is completed the is invoked synchronously. /// @@ -800,6 +818,7 @@ public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperation /// Thrown if the is . /// Thrown is the operation has been disposed. /// + /// /// public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action, AsyncContinuationOptions options) { @@ -812,6 +831,27 @@ public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperation } } + /// + /// Adds a completion callback to be executed after the operation has finished. If the operation is completed the is invoked synchronously. + /// + /// The target operation. + /// The callback to be executed when the operation has completed. + /// Options for when the callback is executed. + /// Thrown if the is . + /// Thrown is the operation has been disposed. + /// + /// + public static void AddCompletionCallback(this IAsyncOperation op, Action action, AsyncContinuationOptions options) + { + if (!op.TryAddCompletionCallback(action, options)) + { + if (AsyncContinuation.CanInvoke(op, options)) + { + action(); + } + } + } + /// /// Adds a completion callback to be executed after the operation has finished. If the operation is completed the is invoked /// on the specified. @@ -825,6 +865,7 @@ public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperation /// Thrown if the is . /// Thrown is the operation has been disposed. /// + /// /// public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action, AsyncContinuationOptions options, SynchronizationContext syncContext) { From 743ee76c020b8d10cddc32c84d89f2d065e33bb4 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 30 Mar 2018 12:26:22 +0300 Subject: [PATCH 012/128] Made IAsyncOperationEvents interface non-ambihous --- .../Tests/AsyncResultTests.cs | 4 +- .../Api/Core/AsyncCompletionSource.cs | 6 +-- .../Api/Core/AsyncCompletionSource{T}.cs | 6 +-- src/UnityFx.Async/Api/Core/AsyncExtensions.cs | 42 +++++++++---------- src/UnityFx.Async/Api/Core/AsyncResult.cs | 17 +++++++- .../Api/Core/AsyncResultQueue{T}.cs | 2 +- src/UnityFx.Async/Api/Core/AsyncResult{T}.cs | 2 +- .../Api/Interfaces/IAsyncOperationEvents.cs | 24 ++++++++--- .../Implementation/AsyncObservable{T}.cs | 2 +- .../Implementation/RetryResult{T}.cs | 2 +- .../Implementation/WhenAllResult{T}.cs | 2 +- .../Implementation/WhenAnyResult{T}.cs | 2 +- 12 files changed, 63 insertions(+), 48 deletions(-) diff --git a/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs b/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs index fd5d007..8468c23 100644 --- a/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs +++ b/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs @@ -902,7 +902,7 @@ public void TryAddCompletionCallback_FailsIfOperationIsCompleted() op.SetCanceled(); // Act - var result = op.TryAddCompletionCallback(_ => { }, AsyncContinuationOptions.None, null); + var result = op.TryAddCompletionCallback(_ => { }, null); // Assert Assert.False(result); @@ -915,7 +915,7 @@ public void TryAddCompletionCallback_FailsIfOperationIsCompletedSynchronously() var op = AsyncResult.CompletedOperation; // Act - var result = op.TryAddCompletionCallback(_ => { }, AsyncContinuationOptions.None, null); + var result = op.TryAddCompletionCallback(_ => { }, null); // Assert Assert.False(result); diff --git a/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs b/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs index 3c472a2..88dda80 100644 --- a/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs +++ b/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs @@ -308,14 +308,10 @@ internal bool TryCopyCompletionState(IAsyncOperation patternOp, bool completedSy { return base.TrySetCompleted(completedSynchronously); } - else if (patternOp.IsFaulted) + else if (patternOp.IsFaulted || patternOp.IsCanceled) { return base.TrySetException(patternOp.Exception, completedSynchronously); } - else if (patternOp.IsCanceled) - { - return base.TrySetCanceled(completedSynchronously); - } return false; } diff --git a/src/UnityFx.Async/Api/Core/AsyncCompletionSource{T}.cs b/src/UnityFx.Async/Api/Core/AsyncCompletionSource{T}.cs index 1d24967..4edcbdb 100644 --- a/src/UnityFx.Async/Api/Core/AsyncCompletionSource{T}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncCompletionSource{T}.cs @@ -311,14 +311,10 @@ internal bool TryCopyCompletionState(IAsyncOperation patternOp, bool complete { return base.TrySetResult(patternOp.Result, completedSynchronously); } - else if (patternOp.IsFaulted) + else if (patternOp.IsFaulted || patternOp.IsCanceled) { return base.TrySetException(patternOp.Exception, completedSynchronously); } - else if (patternOp.IsCanceled) - { - return base.TrySetCanceled(completedSynchronously); - } return false; } diff --git a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs index 70250e2..8529fb8 100644 --- a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs +++ b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs @@ -775,7 +775,7 @@ public static IAsyncOperation Finally(this IAsyncOperation op, Action action) #region AddCompletionCallback /// - /// Adds a completion callback to be executed after the operation has finished. If the operation is completed the is invoked synchronously. + /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked synchronously. /// /// The target operation. /// The callback to be executed when the operation has completed. @@ -783,17 +783,17 @@ public static IAsyncOperation Finally(this IAsyncOperation op, Action action) /// Thrown is the operation has been disposed. /// /// - /// + /// public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action) { - if (!op.TryAddCompletionCallback(action, AsyncContinuationOptions.None, SynchronizationContext.Current)) + if (!op.TryAddCompletionCallback(action, SynchronizationContext.Current)) { action(op); } } /// - /// Adds a completion callback to be executed after the operation has finished. If the operation is completed the is invoked synchronously. + /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked synchronously. /// /// The target operation. /// The callback to be executed when the operation has completed. @@ -810,7 +810,7 @@ public static void AddCompletionCallback(this IAsyncOperation op, Action action) } /// - /// Adds a completion callback to be executed after the operation has finished. If the operation is completed the is invoked synchronously. + /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked synchronously. /// /// The target operation. /// The callback to be executed when the operation has completed. @@ -819,10 +819,10 @@ public static void AddCompletionCallback(this IAsyncOperation op, Action action) /// Thrown is the operation has been disposed. /// /// - /// + /// public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action, AsyncContinuationOptions options) { - if (!op.TryAddCompletionCallback(action, options, null)) + if (!op.TryAddCompletionCallback(action, options)) { if (AsyncContinuation.CanInvoke(op, options)) { @@ -832,7 +832,7 @@ public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperation } /// - /// Adds a completion callback to be executed after the operation has finished. If the operation is completed the is invoked synchronously. + /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked synchronously. /// /// The target operation. /// The callback to be executed when the operation has completed. @@ -853,12 +853,11 @@ public static void AddCompletionCallback(this IAsyncOperation op, Action action, } /// - /// Adds a completion callback to be executed after the operation has finished. If the operation is completed the is invoked + /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked /// on the specified. /// /// The target operation. /// The callback to be executed when the operation has completed. - /// Options for when the callback is executed. /// If not method attempts to marshal the continuation to the synchronization context. /// Otherwise the callback is invoked on a thread that initiated the operation completion. /// @@ -867,20 +866,17 @@ public static void AddCompletionCallback(this IAsyncOperation op, Action action, /// /// /// - public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action, AsyncContinuationOptions options, SynchronizationContext syncContext) + public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action, SynchronizationContext syncContext) { - if (!op.TryAddCompletionCallback(action, options, syncContext)) + if (!op.TryAddCompletionCallback(action, syncContext)) { - if (AsyncContinuation.CanInvoke(op, options)) + if (syncContext == null || syncContext.GetType() == typeof(SynchronizationContext) || syncContext == SynchronizationContext.Current) { - if (syncContext == null || syncContext.GetType() == typeof(SynchronizationContext) || syncContext == SynchronizationContext.Current) - { - action(op); - } - else - { - syncContext.Post(args => action(op), op); - } + action(op); + } + else + { + syncContext.Post(args => action(args as IAsyncOperation), op); } } } @@ -1505,7 +1501,7 @@ public static Task ToTask(this IAsyncOperation op) { var result = new TaskCompletionSource(); - if (!op.TryAddCompletionCallback(asyncOp => AsyncContinuation.InvokeTaskContinuation(asyncOp, result), AsyncContinuationOptions.None, null)) + if (!op.TryAddCompletionCallback(asyncOp => AsyncContinuation.InvokeTaskContinuation(asyncOp, result), null)) { AsyncContinuation.InvokeTaskContinuation(op, result); } @@ -1539,7 +1535,7 @@ public static Task ToTask(this IAsyncOperation op) { var result = new TaskCompletionSource(); - if (!op.TryAddCompletionCallback(asyncOp => AsyncContinuation.InvokeTaskContinuation(asyncOp as IAsyncOperation, result), AsyncContinuationOptions.None, null)) + if (!op.TryAddCompletionCallback(asyncOp => AsyncContinuation.InvokeTaskContinuation(asyncOp as IAsyncOperation, result), null)) { AsyncContinuation.InvokeTaskContinuation(op, result); } diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 197ee11..63f6bb0 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -1383,7 +1383,7 @@ public event AsyncOperationCallback Completed } /// - public bool TryAddCompletionCallback(AsyncOperationCallback action, AsyncContinuationOptions options, SynchronizationContext syncContext) + public bool TryAddCompletionCallback(AsyncOperationCallback action, AsyncContinuationOptions options) { ThrowIfDisposed(); @@ -1392,7 +1392,20 @@ public bool TryAddCompletionCallback(AsyncOperationCallback action, AsyncContinu throw new ArgumentNullException(nameof(action)); } - return TryAddContinuation(action, options, syncContext); + return TryAddContinuation(action, options, null); + } + + /// + public bool TryAddCompletionCallback(AsyncOperationCallback action, SynchronizationContext syncContext) + { + ThrowIfDisposed(); + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return TryAddContinuation(action, AsyncContinuationOptions.None, syncContext); } /// diff --git a/src/UnityFx.Async/Api/Core/AsyncResultQueue{T}.cs b/src/UnityFx.Async/Api/Core/AsyncResultQueue{T}.cs index 60c84eb..da70aaa 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResultQueue{T}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResultQueue{T}.cs @@ -153,7 +153,7 @@ public bool TryAdd(T op) _completionCallback = OnCompletedCallback; } - if (op.TryAddCompletionCallback(_completionCallback, AsyncContinuationOptions.None, _syncContext)) + if (op.TryAddCompletionCallback(_completionCallback, _syncContext)) { lock (_ops) { diff --git a/src/UnityFx.Async/Api/Core/AsyncResult{T}.cs b/src/UnityFx.Async/Api/Core/AsyncResult{T}.cs index 151a559..26b2b55 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult{T}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult{T}.cs @@ -183,7 +183,7 @@ public IDisposable Subscribe(IObserver observer) } }; - if (TryAddCompletionCallback(completionCallback, AsyncContinuationOptions.None, null)) + if (TryAddCompletionCallback(completionCallback, null)) { return new AsyncObservableSubscription(this, completionCallback); } diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs index fbdbb50..81c87d9 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs @@ -77,7 +77,8 @@ public interface IAsyncOperationEvents /// /// Thrown if the delegate being registered is . /// Thrown is the operation has been disposed. - /// + /// + /// /// event AsyncOperationCallback Completed; @@ -87,15 +88,27 @@ public interface IAsyncOperationEvents /// /// The callback to be executed when the operation has completed. /// Options for when the callback is executed. + /// Returns if the callback was added; otherwise (the operation is completed). + /// Thrown if the is . + /// Thrown is the operation has been disposed. + /// + /// + bool TryAddCompletionCallback(AsyncOperationCallback action, AsyncContinuationOptions options); + + /// + /// Attempts to add a completion callback to be executed after the operation has finished. If the operation is already completed + /// the method does nothing and just returns . + /// + /// The callback to be executed when the operation has completed. /// If not method attempts to marshal the continuation to the synchronization context. - /// Otherwise the callback is invoked on a thread that initiated the operation completion. The argument value is ignored if - /// is set to . + /// Otherwise the callback is invoked on a thread that initiated the operation completion. /// /// Returns if the callback was added; otherwise (the operation is completed). /// Thrown if the is . /// Thrown is the operation has been disposed. + /// /// - bool TryAddCompletionCallback(AsyncOperationCallback action, AsyncContinuationOptions options, SynchronizationContext syncContext); + bool TryAddCompletionCallback(AsyncOperationCallback action, SynchronizationContext syncContext); /// /// Removes an existing completion callback. @@ -103,7 +116,8 @@ public interface IAsyncOperationEvents /// The callback to remove. Can be . /// Returns if the was removed; otherwise. /// Thrown is the operation has been disposed. - /// + /// + /// bool RemoveCompletionCallback(AsyncOperationCallback action); /// diff --git a/src/UnityFx.Async/Implementation/AsyncObservable{T}.cs b/src/UnityFx.Async/Implementation/AsyncObservable{T}.cs index 84c06eb..c2dd41b 100644 --- a/src/UnityFx.Async/Implementation/AsyncObservable{T}.cs +++ b/src/UnityFx.Async/Implementation/AsyncObservable{T}.cs @@ -51,7 +51,7 @@ public IDisposable Subscribe(IObserver observer) } }; - if (_op.TryAddCompletionCallback(d, AsyncContinuationOptions.None, null)) + if (_op.TryAddCompletionCallback(d, null)) { return new AsyncObservableSubscription(_op, d); } diff --git a/src/UnityFx.Async/Implementation/RetryResult{T}.cs b/src/UnityFx.Async/Implementation/RetryResult{T}.cs index 78bd975..5ae58f5 100644 --- a/src/UnityFx.Async/Implementation/RetryResult{T}.cs +++ b/src/UnityFx.Async/Implementation/RetryResult{T}.cs @@ -73,7 +73,7 @@ private void StartOperation(bool calledFromConstructor) throw new InvalidOperationException("Invalid delegate type."); } - if (!_op.TryAddCompletionCallback(_opCompletionCallback, AsyncContinuationOptions.None, null)) + if (!_op.TryAddCompletionCallback(_opCompletionCallback, null)) { if (_op.IsCompletedSuccessfully) { diff --git a/src/UnityFx.Async/Implementation/WhenAllResult{T}.cs b/src/UnityFx.Async/Implementation/WhenAllResult{T}.cs index 94a80c8..4a54ff9 100644 --- a/src/UnityFx.Async/Implementation/WhenAllResult{T}.cs +++ b/src/UnityFx.Async/Implementation/WhenAllResult{T}.cs @@ -31,7 +31,7 @@ public WhenAllResult(IAsyncOperation[] ops) foreach (var op in ops) { - if (!op.TryAddCompletionCallback(_completionAction, AsyncContinuationOptions.None, null)) + if (!op.TryAddCompletionCallback(_completionAction, null)) { OnOperationCompleted(op); } diff --git a/src/UnityFx.Async/Implementation/WhenAnyResult{T}.cs b/src/UnityFx.Async/Implementation/WhenAnyResult{T}.cs index 575d60f..99524cb 100644 --- a/src/UnityFx.Async/Implementation/WhenAnyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/WhenAnyResult{T}.cs @@ -24,7 +24,7 @@ public WhenAnyResult(T[] ops) foreach (var op in ops) { - if (!op.TryAddCompletionCallback(_completionAction, AsyncContinuationOptions.None, null)) + if (!op.TryAddCompletionCallback(_completionAction, null)) { TrySetResult(op, true); break; From ab1c4b337fea38ffce30229e4f748f3857130411 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 30 Mar 2018 12:54:13 +0300 Subject: [PATCH 013/128] Fixed FromCanceled not settings exception correctly --- src/UnityFx.Async/Api/Core/AsyncExtensions.cs | 7 ++----- src/UnityFx.Async/Api/Core/AsyncResult.cs | 4 ++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs index 8529fb8..30d7fc7 100644 --- a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs +++ b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs @@ -961,11 +961,8 @@ public static IAsyncOperation ContinueWith(this T op, Func - { - result.CopyCompletionState(asyncOp2, false); - }, - AsyncContinuationOptions.None); + asyncOp2 => result.CopyCompletionState(asyncOp2, false), + null); } catch (Exception e) { diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 63f6bb0..dd27371 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -1589,6 +1589,10 @@ private AsyncResult(int flags) { _exception = new AggregateException(); } + else if (flags == StatusCanceled) + { + _exception = new AggregateException(new OperationCanceledException()); + } if (flags > StatusRunning) { From b0b013432392dd6da3e17a0b3ce92806a1914903 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 30 Mar 2018 15:05:07 +0300 Subject: [PATCH 014/128] Then/ContinueWith fixes --- .../Tests/AsyncExtensionsTests.cs | 26 +- src/UnityFx.Async/Api/Core/AsyncExtensions.cs | 797 +++++++++++------- 2 files changed, 494 insertions(+), 329 deletions(-) diff --git a/src/UnityFx.Async.Tests/Tests/AsyncExtensionsTests.cs b/src/UnityFx.Async.Tests/Tests/AsyncExtensionsTests.cs index 80bd7cc..ab3ca7f 100644 --- a/src/UnityFx.Async.Tests/Tests/AsyncExtensionsTests.cs +++ b/src/UnityFx.Async.Tests/Tests/AsyncExtensionsTests.cs @@ -94,27 +94,7 @@ public void ToObservable_OnErrorIsCalled() #region ContinueWith - [Fact] - public async Task ContinueWith_CompletesWhenBothOperationsComplete() - { - // Arrange - var op = AsyncResult.Delay(10); - var op2 = op.ContinueWith((asyncResult, cs) => - { - Task.Run(() => - { - Thread.Sleep(10); - cs.SetCompleted(); - }); - }); - - // Act - await op2; - - // Assert - Assert.True(op.IsCompleted); - Assert.True(op2.IsCompleted); - } + // TODO #endregion @@ -138,7 +118,7 @@ public async Task ToTask_CompletesWhenSourceCompletes() public async Task ToTask_FailsWhenSourceFails() { // Arrange - var op = AsyncResult.Delay(1).ContinueWith(result => AsyncResult.FromException(new Exception())); + var op = AsyncResult.Delay(1).Then(() => AsyncResult.FromException(new Exception())); var task = op.ToTask(); // Act/Assert @@ -149,7 +129,7 @@ public async Task ToTask_FailsWhenSourceFails() public async Task ToTask_FailsWhenSourceIsCanceled() { // Arrange - var op = AsyncResult.Delay(1).ContinueWith(result => AsyncResult.FromCanceled()); + var op = AsyncResult.Delay(1).Then(() => AsyncResult.FromCanceled()); var task = op.ToTask(); // Act/Assert diff --git a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs index 30d7fc7..f79164f 100644 --- a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs +++ b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs @@ -351,27 +351,25 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - op.AddCompletionCallback( - asyncOp => + op.AddCompletionCallback(asyncOp => + { + try { - try + if (asyncOp.IsCompletedSuccessfully) { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback(); - result.TrySetCompleted(); - } - else - { - result.TrySetException(asyncOp.Exception); - } + successCallback(); + result.TrySetCompleted(); } - catch (Exception e) + else { - result.TrySetException(e); + result.TrySetException(asyncOp.Exception); } - }, - AsyncContinuationOptions.CaptureSynchronizationContext); + } + catch (Exception e) + { + result.TrySetException(e); + } + }); return result; } @@ -392,27 +390,25 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action succ var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - op.AddCompletionCallback( - asyncOp => + op.AddCompletionCallback(asyncOp => + { + try { - try + if (asyncOp.IsCompletedSuccessfully) { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback((asyncOp as IAsyncOperation).Result); - result.TrySetCompleted(); - } - else - { - result.TrySetException(asyncOp.Exception); - } + successCallback((asyncOp as IAsyncOperation).Result); + result.TrySetCompleted(); } - catch (Exception e) + else { - result.TrySetException(e); + result.TrySetException(asyncOp.Exception); } - }, - AsyncContinuationOptions.CaptureSynchronizationContext); + } + catch (Exception e) + { + result.TrySetException(e); + } + }); return result; } @@ -432,28 +428,24 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func + op.AddCompletionCallback(asyncOp => + { + try { - try + if (asyncOp.IsCompletedSuccessfully) { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback().AddCompletionCallback( - asyncOp2 => result.CopyCompletionState(asyncOp2, false), - AsyncContinuationOptions.None); - } - else - { - result.TrySetException(asyncOp.Exception); - } + successCallback().AddCompletionCallback(asyncOp2 => result.CopyCompletionState(asyncOp2, false), null); } - catch (Exception e) + else { - result.TrySetException(e); + result.TrySetException(asyncOp.Exception); } - }, - AsyncContinuationOptions.CaptureSynchronizationContext); + } + catch (Exception e) + { + result.TrySetException(e); + } + }); return result; } @@ -473,28 +465,24 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func + op.AddCompletionCallback(asyncOp => + { + try { - try + if (asyncOp.IsCompletedSuccessfully) { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback((asyncOp as IAsyncOperation).Result).AddCompletionCallback( - asyncOp2 => result.CopyCompletionState(asyncOp2, false), - AsyncContinuationOptions.None); - } - else - { - result.TrySetException(asyncOp.Exception); - } + successCallback((asyncOp as IAsyncOperation).Result).AddCompletionCallback(asyncOp2 => result.CopyCompletionState(asyncOp2, false), null); } - catch (Exception e) + else { - result.TrySetException(e); + result.TrySetException(asyncOp.Exception); } - }, - AsyncContinuationOptions.CaptureSynchronizationContext); + } + catch (Exception e) + { + result.TrySetException(e); + } + }); return result; } @@ -520,28 +508,26 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - op.AddCompletionCallback( - asyncOp => + op.AddCompletionCallback(asyncOp => + { + try { - try + if (asyncOp.IsCompletedSuccessfully) { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback(); - result.TrySetCompleted(); - } - else - { - errorCallback(asyncOp.Exception.InnerException); - result.TrySetException(asyncOp.Exception); - } + successCallback(); + result.TrySetCompleted(); } - catch (Exception e) + else { - result.TrySetException(e); + errorCallback(asyncOp.Exception.InnerException); + result.TrySetException(asyncOp.Exception); } - }, - AsyncContinuationOptions.CaptureSynchronizationContext); + } + catch (Exception e) + { + result.TrySetException(e); + } + }); return result; } @@ -567,28 +553,26 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action succ var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - op.AddCompletionCallback( - asyncOp => + op.AddCompletionCallback(asyncOp => + { + try { - try + if (asyncOp.IsCompletedSuccessfully) { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback((asyncOp as IAsyncOperation).Result); - result.TrySetCompleted(); - } - else - { - errorCallback(asyncOp.Exception.InnerException); - result.TrySetException(asyncOp.Exception); - } + successCallback((asyncOp as IAsyncOperation).Result); + result.TrySetCompleted(); } - catch (Exception e) + else { - result.TrySetException(e); + errorCallback(asyncOp.Exception.InnerException); + result.TrySetException(asyncOp.Exception); } - }, - AsyncContinuationOptions.CaptureSynchronizationContext); + } + catch (Exception e) + { + result.TrySetException(e); + } + }); return result; } @@ -614,29 +598,25 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func + op.AddCompletionCallback(asyncOp => + { + try { - try + if (asyncOp.IsCompletedSuccessfully) { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback().AddCompletionCallback( - asyncOp2 => result.CopyCompletionState(asyncOp2, false), - AsyncContinuationOptions.None); - } - else - { - errorCallback(asyncOp.Exception.InnerException); - result.TrySetException(asyncOp.Exception); - } + successCallback().AddCompletionCallback(asyncOp2 => result.CopyCompletionState(asyncOp2, false), null); } - catch (Exception e) + else { - result.TrySetException(e); + errorCallback(asyncOp.Exception.InnerException); + result.TrySetException(asyncOp.Exception); } - }, - AsyncContinuationOptions.CaptureSynchronizationContext); + } + catch (Exception e) + { + result.TrySetException(e); + } + }); return result; } @@ -662,29 +642,25 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func + op.AddCompletionCallback(asyncOp => + { + try { - try + if (asyncOp.IsCompletedSuccessfully) { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback((asyncOp as IAsyncOperation).Result).AddCompletionCallback( - asyncOp2 => result.CopyCompletionState(asyncOp2, false), - AsyncContinuationOptions.None); - } - else - { - errorCallback(asyncOp.Exception.InnerException); - result.TrySetException(asyncOp.Exception); - } + successCallback((asyncOp as IAsyncOperation).Result).AddCompletionCallback(asyncOp2 => result.CopyCompletionState(asyncOp2, false), null); } - catch (Exception e) + else { - result.TrySetException(e); + errorCallback(asyncOp.Exception.InnerException); + result.TrySetException(asyncOp.Exception); } - }, - AsyncContinuationOptions.CaptureSynchronizationContext); + } + catch (Exception e) + { + result.TrySetException(e); + } + }); return result; } @@ -888,42 +864,31 @@ public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperation /// /// Creates a continuation that executes when the target completes. /// - /// - /// The is expected to start another asynchronous operation. When that operation is completed it - /// should use the second argument to complete the continuation. If the - /// is already completed the is being called synchronously. - /// Continuation behaviour is very close to TPL: if is set the continuation posted onto it. - /// Otherwise it is executed on a thread that initiated the operation completion. - /// - /// Type of the operation to continue. /// 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 T op, Action action) where T : IAsyncOperation + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action action) { if (action == null) { throw new ArgumentNullException(nameof(action)); } - var result = new AsyncCompletionSource(AsyncOperationStatus.Scheduled); + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - op.Completed += asyncOp => + op.AddCompletionCallback(asyncOp => { try { - result.SetRunning(); - action(op, result); + action(asyncOp); + result.TrySetCompleted(); } catch (Exception e) { result.TrySetException(e, false); } - }; + }); return result; } @@ -931,44 +896,119 @@ public static IAsyncOperation ContinueWith(this T op, Action /// Creates a continuation that executes when the target completes. /// - /// - /// The is expected to start another asynchronous operation. If the - /// is already completed the is being called synchronously. - /// Continuation behaviour is very close to TPL: if is set the continuation posted onto it. - /// Otherwise it is executed on a thread that initiated the operation completion. - /// - /// Type of the operation to continue. /// 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 T op, Func action) where T : IAsyncOperation + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action action, AsyncContinuationOptions options) { if (action == null) { throw new ArgumentNullException(nameof(action)); } - var result = new AsyncCompletionSource(AsyncOperationStatus.Scheduled); + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; + + op.AddCompletionCallback( + asyncOp => + { + try + { + if (AsyncContinuation.CanInvoke(asyncOp, options)) + { + action(asyncOp); + result.TrySetCompleted(); + } + else + { + result.TrySetCanceled(); + } + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }, + syncContext); + + return result; + } - op.Completed += asyncOp => + /// + /// 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) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback(asyncOp => { try { - result.SetRunning(); - - action(op).AddCompletionCallback( - asyncOp2 => result.CopyCompletionState(asyncOp2, false), - null); + action(asyncOp, userState); + result.TrySetCompleted(); } catch (Exception e) { result.TrySetException(e, false); } - }; + }); + + return result; + } + + /// + /// 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)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; + + op.AddCompletionCallback( + asyncOp => + { + try + { + if (AsyncContinuation.CanInvoke(asyncOp, options)) + { + action(asyncOp, userState); + result.TrySetCompleted(); + } + else + { + result.TrySetCanceled(); + } + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }, + syncContext); return result; } @@ -976,39 +1016,74 @@ public static IAsyncOperation ContinueWith(this T op, Func /// Creates a continuation that executes when the target completes. /// - /// - /// See ContinueWith remarks for details. - /// - /// Type of the operation to continue. /// The operation to continue. /// An action to run when the completes. - /// User-defined state that is passed as last argument of . /// Thrown if the is . /// An operation that is executed after completes. - /// - /// - /// - public static IAsyncOperation ContinueWith(this T op, Action action, object state) where T : IAsyncOperation + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func action) { if (action == null) { throw new ArgumentNullException(nameof(action)); } - var result = new AsyncCompletionSource(AsyncOperationStatus.Scheduled); + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - op.Completed += asyncOp => + op.AddCompletionCallback(asyncOp => { try { - result.SetRunning(); - action(op, result, state); + var resultValue = action(asyncOp); + result.TrySetResult(resultValue); } catch (Exception e) { result.TrySetException(e, false); } - }; + }); + + return result; + } + + /// + /// 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)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; + + op.AddCompletionCallback( + asyncOp => + { + try + { + if (AsyncContinuation.CanInvoke(asyncOp, options)) + { + var resultValue = action(asyncOp); + result.TrySetResult(resultValue); + } + else + { + result.TrySetCanceled(); + } + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }, + syncContext); return result; } @@ -1016,48 +1091,32 @@ public static IAsyncOperation ContinueWith(this T op, Action /// Creates a continuation that executes when the target completes. /// - /// - /// The is expected to start another asynchronous operation. If the - /// is already completed the is being called synchronously. - /// Continuation behaviour is very close to TPL: if is set the continuation posted onto it. - /// Otherwise it is executed on a thread that initiated the operation completion. - /// - /// Type of the operation to continue. /// The operation to continue. /// An action to run when the completes. - /// User-defined state that is passed as last argument of . + /// 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 T op, Func action, object state) where T : IAsyncOperation + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func action, object userState) { if (action == null) { throw new ArgumentNullException(nameof(action)); } - var result = new AsyncCompletionSource(AsyncOperationStatus.Scheduled); + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - op.Completed += asyncOp => + op.AddCompletionCallback(asyncOp => { try { - result.SetRunning(); - - action(op, state).AddCompletionCallback( - asyncOp2 => - { - result.CopyCompletionState(asyncOp2, false); - }, - AsyncContinuationOptions.None); + var resultValue = action(asyncOp, userState); + result.TrySetResult(resultValue); } catch (Exception e) { result.TrySetException(e, false); } - }; + }); return result; } @@ -1065,88 +1124,195 @@ public static IAsyncOperation ContinueWith(this T op, Func /// Creates a continuation that executes when the target completes. /// - /// - /// See ContinueWith remarks for details. - /// - /// Type of the operation to continue. - /// Result type of the continuation operation. + /// 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)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; + + op.AddCompletionCallback( + asyncOp => + { + try + { + if (AsyncContinuation.CanInvoke(asyncOp, options)) + { + var resultValue = action(asyncOp, userState); + result.TrySetResult(resultValue); + } + else + { + result.TrySetCanceled(); + } + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }, + syncContext); + + return result; + } + + /// + /// 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 T op, Action> action) where T : IAsyncOperation + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action> action) { if (action == null) { throw new ArgumentNullException(nameof(action)); } - var result = new AsyncCompletionSource(AsyncOperationStatus.Scheduled); + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - op.Completed += asyncOp => + op.AddCompletionCallback(asyncOp => { try { - result.SetRunning(); - action(op, result); + action(asyncOp as IAsyncOperation); + result.TrySetCompleted(); } catch (Exception e) { result.TrySetException(e, false); } - }; + }); return result; } /// - /// Creates a continuation that executes when the target completes. + /// Creates a continuation that executes when the target completes. /// - /// - /// The is expected to start another asynchronous operation. If the - /// is already completed the is being called synchronously. - /// Continuation behaviour is very close to TPL: if is set the continuation posted onto it. - /// Otherwise it is executed on a thread that initiated the operation completion. - /// - /// Type of the operation to continue. - /// Result type of the continuation operation. /// 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 T op, Func> action) where T : IAsyncOperation + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action> action, AsyncContinuationOptions options) { if (action == null) { throw new ArgumentNullException(nameof(action)); } - var result = new AsyncCompletionSource(AsyncOperationStatus.Scheduled); + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; - op.Completed += asyncOp => + op.AddCompletionCallback( + asyncOp => + { + try + { + if (AsyncContinuation.CanInvoke(asyncOp, options)) + { + action(asyncOp as IAsyncOperation); + result.TrySetCompleted(); + } + else + { + result.TrySetCanceled(); + } + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }, + syncContext); + + return result; + } + + /// + /// 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) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback(asyncOp => { try { - result.SetRunning(); - - action(op).AddCompletionCallback( - asyncOp2 => - { - result.CopyCompletionState(asyncOp2 as IAsyncOperation, false); - }, - AsyncContinuationOptions.None); + action(asyncOp as IAsyncOperation, userState); + result.TrySetCompleted(); } catch (Exception e) { result.TrySetException(e, false); } - }; + }); + + return result; + } + + /// + /// 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)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; + + op.AddCompletionCallback( + asyncOp => + { + try + { + if (AsyncContinuation.CanInvoke(asyncOp, options)) + { + action(asyncOp as IAsyncOperation, userState); + result.TrySetCompleted(); + } + else + { + result.TrySetCanceled(); + } + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }, + syncContext); return result; } @@ -1154,41 +1320,31 @@ public static IAsyncOperation ContinueWith(this T op, Func /// Creates a continuation that executes when the target completes. /// - /// - /// See ContinueWith remarks for details. - /// - /// Type of the operation to continue. - /// Result type of the continuation operation. /// The operation to continue. /// An action to run when the completes. - /// User-defined state that is passed as last argument of . /// Thrown if the is . - /// Thrown if the target does not implement . /// An operation that is executed after completes. - /// - /// - /// - public static IAsyncOperation ContinueWith(this T op, Action, object> action, object state) where T : IAsyncOperation + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func, U> action) { if (action == null) { throw new ArgumentNullException(nameof(action)); } - var result = new AsyncCompletionSource(AsyncOperationStatus.Scheduled); + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - op.Completed += asyncOp => + op.AddCompletionCallback(asyncOp => { try { - result.SetRunning(); - action(op, result, state); + var resultValue = action(asyncOp as IAsyncOperation); + result.TrySetResult(resultValue); } catch (Exception e) { result.TrySetException(e, false); } - }; + }); return result; } @@ -1196,90 +1352,119 @@ public static IAsyncOperation ContinueWith(this T op, Action /// Creates a continuation that executes when the target completes. /// - /// - /// The is expected to start another asynchronous operation. If the - /// is already completed the is being called synchronously. - /// Continuation behaviour is very close to TPL: if is set the continuation posted onto it. - /// Otherwise it is executed on a thread that initiated the operation completion. - /// - /// Type of the operation to continue. - /// Result type of the continuation operation. /// The operation to continue. /// An action to run when the completes. - /// User-defined state that is passed as last argument of . + /// Options for when the is executed. /// Thrown if the is . /// An operation that is executed after completes. - /// - /// - /// - public static IAsyncOperation ContinueWith(this T op, Func> action, object state) where T : IAsyncOperation + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func, U> action, AsyncContinuationOptions options) { if (action == null) { throw new ArgumentNullException(nameof(action)); } - var result = new AsyncCompletionSource(AsyncOperationStatus.Scheduled); + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; + + op.AddCompletionCallback( + asyncOp => + { + try + { + if (AsyncContinuation.CanInvoke(asyncOp, options)) + { + var resultValue = action(asyncOp as IAsyncOperation); + result.TrySetResult(resultValue); + } + else + { + result.TrySetCanceled(); + } + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }, + syncContext); + + return result; + } - op.Completed += asyncOp => + /// + /// 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, U> action, object userState) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback(asyncOp => { try { - result.SetRunning(); - - action(op, state).AddCompletionCallback( - asyncOp2 => - { - result.CopyCompletionState(asyncOp2 as IAsyncOperation, false); - }, - AsyncContinuationOptions.None); + var resultValue = action(asyncOp as IAsyncOperation, userState); + result.TrySetResult(resultValue); } catch (Exception e) { result.TrySetException(e, false); } - }; + }); return result; } - #endregion - - #region TransformWith - /// - /// Creates a continuation that transforms the target result. + /// Creates a continuation that executes when the target completes. /// - /// Type of the operation to continue. - /// Result type of the continuation operation. - /// The operation which result is to be transformed. - /// A function used for the result transformation. - /// Thrown if the is . - /// An operation with the transformed result vlaue. - /// - /// - public static IAsyncOperation TransformWith(this T op, Func resultTransformer) where T : class, IAsyncOperation + /// 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, U> action, object userState, AsyncContinuationOptions options) { - if (resultTransformer == null) + if (action == null) { - throw new ArgumentNullException(nameof(resultTransformer)); + throw new ArgumentNullException(nameof(action)); } - var result = new AsyncCompletionSource(AsyncOperationStatus.Scheduled); + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; op.AddCompletionCallback( asyncOp => { try { - result.SetResult(resultTransformer(op)); + if (AsyncContinuation.CanInvoke(asyncOp, options)) + { + var resultValue = action(asyncOp as IAsyncOperation, userState); + result.TrySetResult(resultValue); + } + else + { + result.TrySetCanceled(); + } } catch (Exception e) { result.TrySetException(e, false); } }, - AsyncContinuationOptions.None); + syncContext); return result; } From f346579eddd7c57032fff27e8d510eb4822308fb Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 30 Mar 2018 15:10:06 +0300 Subject: [PATCH 015/128] Catch/Finally fixes --- .../Tests/AsyncResultTests.cs | 8 +-- src/UnityFx.Async/Api/Core/AsyncExtensions.cs | 54 +++++++++---------- 2 files changed, 29 insertions(+), 33 deletions(-) diff --git a/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs b/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs index 8468c23..b34baf1 100644 --- a/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs +++ b/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs @@ -465,7 +465,7 @@ public void TrySetCanceled_RaisesCompletionCallbacks() var asyncCallbackCalled1 = false; var asyncCallbackCalled2 = false; var op = new AsyncCompletionSource(asyncResult => asyncCallbackCalled1 = true, null); - op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, AsyncContinuationOptions.None); + op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, null); // Act op.TrySetCanceled(); @@ -593,7 +593,7 @@ public void TrySetException_RaisesCompletionCallbacks() var asyncCallbackCalled1 = false; var asyncCallbackCalled2 = false; var op = new AsyncCompletionSource(asyncResult => asyncCallbackCalled1 = true, null); - op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, AsyncContinuationOptions.None); + op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, null); // Act op.TrySetException(e); @@ -714,7 +714,7 @@ public void TrySetCompleted_RaisesCompletionCallbacks() var asyncCallbackCalled1 = false; var asyncCallbackCalled2 = false; var op = new AsyncCompletionSource(asyncResult => asyncCallbackCalled1 = true, null); - op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, AsyncContinuationOptions.None); + op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, null); // Act op.TrySetCompleted(); @@ -822,7 +822,7 @@ public void TrySetResult_RaisesCompletionCallbacks() var asyncCallbackCalled1 = false; var asyncCallbackCalled2 = false; var op = new AsyncCompletionSource(asyncResult => asyncCallbackCalled1 = true, null); - op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, AsyncContinuationOptions.None); + op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, null); // Act op.TrySetResult(10); diff --git a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs index f79164f..68bb22a 100644 --- a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs +++ b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs @@ -684,27 +684,25 @@ public static IAsyncOperation Catch(this IAsyncOperation op, Action e var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - op.AddCompletionCallback( - asyncOp => + op.AddCompletionCallback(asyncOp => + { + try { - try + if (asyncOp.IsCompletedSuccessfully) { - if (asyncOp.IsCompletedSuccessfully) - { - result.TrySetCompleted(); - } - else - { - errorCallback(asyncOp.Exception.InnerException); - result.TrySetCompleted(); - } + result.TrySetCompleted(); } - catch (Exception e) + else { - result.TrySetException(e); + errorCallback(asyncOp.Exception.InnerException); + result.TrySetCompleted(); } - }, - AsyncContinuationOptions.CaptureSynchronizationContext); + } + catch (Exception e) + { + result.TrySetException(e); + } + }); return result; } @@ -728,20 +726,18 @@ public static IAsyncOperation Finally(this IAsyncOperation op, Action action) var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - op.AddCompletionCallback( - asyncOp => + op.AddCompletionCallback(asyncOp => + { + try { - try - { - action(); - result.TrySetCompleted(); - } - catch (Exception e) - { - result.TrySetException(e); - } - }, - AsyncContinuationOptions.CaptureSynchronizationContext); + action(); + result.TrySetCompleted(); + } + catch (Exception e) + { + result.TrySetException(e); + } + }); return result; } From 034ca8f840a0a57ec388883eaca0f8ec4c85571e Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 30 Mar 2018 17:00:05 +0300 Subject: [PATCH 016/128] AsyncExtensions is now partial class --- src/UnityFx.Async/Api/Core/AsyncExtensions.cs | 1972 ----------------- .../AsyncExtensions.Continuations.cs | 734 ++++++ .../Extensions/AsyncExtensions.Promises.cs | 421 ++++ .../Api/Extensions/AsyncExtensions.Tasks.cs | 338 +++ .../Api/Extensions/AsyncExtensions.Wait.cs | 257 +++ .../Api/Extensions/AsyncExtensions.cs | 253 +++ 6 files changed, 2003 insertions(+), 1972 deletions(-) delete mode 100644 src/UnityFx.Async/Api/Core/AsyncExtensions.cs create mode 100644 src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs create mode 100644 src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs create mode 100644 src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs create mode 100644 src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs create mode 100644 src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs diff --git a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs b/src/UnityFx.Async/Api/Core/AsyncExtensions.cs deleted file mode 100644 index 68bb22a..0000000 --- a/src/UnityFx.Async/Api/Core/AsyncExtensions.cs +++ /dev/null @@ -1,1972 +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.Generic; -using System.ComponentModel; -using System.Runtime.CompilerServices; -using System.Threading; -#if UNITYFX_SUPPORT_TAP -using System.Threading.Tasks; -#endif - -namespace UnityFx.Async -{ - /// - /// Extension methods for related classes. - /// - [EditorBrowsable(EditorBrowsableState.Advanced)] - public static class AsyncExtensions - { - #region IAsyncOperation - - #region common - - /// - /// Spins until the operation has completed. - /// - public static void SpinUntilCompleted(this IAsyncOperation op) - { -#if NET35 - - while (!op.IsCompleted) - { - Thread.SpinWait(1); - } - -#else - - var sw = new SpinWait(); - - while (!op.IsCompleted) - { - sw.SpinOnce(); - } - -#endif - } - - /// - /// Throws exception if the operation has failed or canceled. - /// - internal static void ThrowIfNonSuccess(IAsyncOperation op, bool throwAggregate) - { - if (op is AsyncResult ar) - { - ar.ThrowIfNonSuccess(throwAggregate); - } - else - { - var status = op.Status; - - if (status == AsyncOperationStatus.Faulted) - { - if (throwAggregate) - { - throw op.Exception; - } - else if (!AsyncResult.TryThrowException(op.Exception)) - { - // Should never get here. If faulted state excpetion should not be null. - throw new Exception(); - } - } - else if (status == AsyncOperationStatus.Canceled) - { - if (throwAggregate) - { - throw op.Exception; - } - else if (!AsyncResult.TryThrowException(op.Exception)) - { - throw new OperationCanceledException(); - } - } - } - } - - #endregion - - #region Wait - - /// - /// Waits for the to complete execution. - /// - /// The operation to wait for. - /// Thrown if the operation was canceled or faulted. - /// Thrown is the operation is disposed. - /// - /// - public static void Wait(this IAsyncOperation op) - { - if (!op.IsCompleted) - { - op.AsyncWaitHandle.WaitOne(); - } - - ThrowIfNonSuccess(op, true); - } - - /// - /// Waits for the to complete execution within a specified number of milliseconds. - /// - /// 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 if the operation was canceled or faulted. - /// 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, true); - } - - return result; - } - - /// - /// Waits for the to complete execution within a specified time interval. - /// - /// 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 if the operation was canceled or faulted. - /// 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, true); - } - - return result; - } - - #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, false); - } - - /// - /// 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, false); - } - 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, false); - } - 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 T Join(this IAsyncOperation op) - { - if (!op.IsCompleted) - { - op.AsyncWaitHandle.WaitOne(); - } - - ThrowIfNonSuccess(op, false); - 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 T Join(this IAsyncOperation op, int millisecondsTimeout) - { - var result = true; - - if (!op.IsCompleted) - { - result = op.AsyncWaitHandle.WaitOne(millisecondsTimeout); - } - - if (result) - { - ThrowIfNonSuccess(op, false); - } - else - { - 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 T Join(this IAsyncOperation op, TimeSpan timeout) - { - var result = true; - - if (!op.IsCompleted) - { - result = op.AsyncWaitHandle.WaitOne(timeout); - } - - if (result) - { - ThrowIfNonSuccess(op, false); - } - else - { - throw new TimeoutException(); - } - - return op.Result; - } - - #endregion - - #region Then - - /// - /// Schedules a callback to be executed after the operation has succeeded. - /// - /// The target operation. - /// The callback to be executed when the operation has completed. - /// TODO - /// - public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback) - { - if (successCallback == null) - { - throw new ArgumentNullException(nameof(successCallback)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback(); - result.TrySetCompleted(); - } - else - { - result.TrySetException(asyncOp.Exception); - } - } - catch (Exception e) - { - result.TrySetException(e); - } - }); - - return result; - } - - /// - /// Schedules a callback to be executed after the operation has succeeded. - /// - /// The target operation. - /// The callback to be executed when the operation has completed. - /// TODO - /// - public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback) - { - if (successCallback == null) - { - throw new ArgumentNullException(nameof(successCallback)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback((asyncOp as IAsyncOperation).Result); - result.TrySetCompleted(); - } - else - { - result.TrySetException(asyncOp.Exception); - } - } - catch (Exception e) - { - result.TrySetException(e); - } - }); - - return result; - } - - /// - /// Schedules a callback to be executed after the operation has succeeded. - /// - /// The target operation. - /// The callback to be executed when the operation has completed. - /// - public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback) - { - if (successCallback == null) - { - throw new ArgumentNullException(nameof(successCallback)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback().AddCompletionCallback(asyncOp2 => result.CopyCompletionState(asyncOp2, false), null); - } - else - { - result.TrySetException(asyncOp.Exception); - } - } - catch (Exception e) - { - result.TrySetException(e); - } - }); - - return result; - } - - /// - /// Schedules a callback to be executed after the operation has succeeded. - /// - /// The target operation. - /// The callback to be executed when the operation has completed. - /// - public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback) - { - if (successCallback == null) - { - throw new ArgumentNullException(nameof(successCallback)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback((asyncOp as IAsyncOperation).Result).AddCompletionCallback(asyncOp2 => result.CopyCompletionState(asyncOp2, false), null); - } - else - { - result.TrySetException(asyncOp.Exception); - } - } - catch (Exception e) - { - result.TrySetException(e); - } - }); - - return result; - } - - /// - /// Schedules a callbacks to be executed after the operation has completed. - /// - /// The target operation. - /// The callback to be executed when the operation has succeeded. - /// The callback to be executed when the operation has faulted/was canceled. - /// - public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback, Action errorCallback) - { - if (successCallback == null) - { - throw new ArgumentNullException(nameof(successCallback)); - } - - if (errorCallback == null) - { - throw new ArgumentNullException(nameof(errorCallback)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback(); - result.TrySetCompleted(); - } - else - { - errorCallback(asyncOp.Exception.InnerException); - result.TrySetException(asyncOp.Exception); - } - } - catch (Exception e) - { - result.TrySetException(e); - } - }); - - return result; - } - - /// - /// Schedules a callbacks to be executed after the operation has completed. - /// - /// The target operation. - /// The callback to be executed when the operation has succeeded. - /// The callback to be executed when the operation has faulted/was canceled. - /// - public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback, Action errorCallback) - { - if (successCallback == null) - { - throw new ArgumentNullException(nameof(successCallback)); - } - - if (errorCallback == null) - { - throw new ArgumentNullException(nameof(errorCallback)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback((asyncOp as IAsyncOperation).Result); - result.TrySetCompleted(); - } - else - { - errorCallback(asyncOp.Exception.InnerException); - result.TrySetException(asyncOp.Exception); - } - } - catch (Exception e) - { - result.TrySetException(e); - } - }); - - return result; - } - - /// - /// Adds a completion callback to be executed after the operation has succeeded. - /// - /// The target operation. - /// The callback to be executed when the operation has succeeded. - /// The callback to be executed when the operation has faulted/was canceled. - /// - public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback, Action errorCallback) - { - if (successCallback == null) - { - throw new ArgumentNullException(nameof(successCallback)); - } - - if (errorCallback == null) - { - throw new ArgumentNullException(nameof(errorCallback)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback().AddCompletionCallback(asyncOp2 => result.CopyCompletionState(asyncOp2, false), null); - } - else - { - errorCallback(asyncOp.Exception.InnerException); - result.TrySetException(asyncOp.Exception); - } - } - catch (Exception e) - { - result.TrySetException(e); - } - }); - - return result; - } - - /// - /// Adds a completion callback to be executed after the operation has succeeded. - /// - /// The target operation. - /// The callback to be executed when the operation has succeeded. - /// The callback to be executed when the operation has faulted/was canceled. - /// - public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback, Action errorCallback) - { - if (successCallback == null) - { - throw new ArgumentNullException(nameof(successCallback)); - } - - if (errorCallback == null) - { - throw new ArgumentNullException(nameof(errorCallback)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback((asyncOp as IAsyncOperation).Result).AddCompletionCallback(asyncOp2 => result.CopyCompletionState(asyncOp2, false), null); - } - else - { - errorCallback(asyncOp.Exception.InnerException); - result.TrySetException(asyncOp.Exception); - } - } - catch (Exception e) - { - result.TrySetException(e); - } - }); - - return result; - } - - #endregion - - #region Catch - - /// - /// Adds a completion callback to be executed after the operation has faulted or was canceled. - /// - /// The target operation. - /// The callback to be executed when the operation has faulted/was canceled. - /// - public static IAsyncOperation Catch(this IAsyncOperation op, Action errorCallback) - { - if (errorCallback == null) - { - throw new ArgumentNullException(nameof(errorCallback)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - if (asyncOp.IsCompletedSuccessfully) - { - result.TrySetCompleted(); - } - else - { - errorCallback(asyncOp.Exception.InnerException); - result.TrySetCompleted(); - } - } - catch (Exception e) - { - result.TrySetException(e); - } - }); - - return result; - } - - #endregion - - #region Finally - - /// - /// Adds a completion callback to be executed after the operation has completed. - /// - /// The target operation. - /// The callback to be executed when the operation has completed. - /// - public static IAsyncOperation Finally(this IAsyncOperation op, Action action) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - action(); - result.TrySetCompleted(); - } - catch (Exception e) - { - result.TrySetException(e); - } - }); - - return result; - } - - #endregion - - #region AddCompletionCallback - - /// - /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked synchronously. - /// - /// The target operation. - /// The callback to be executed when the operation has completed. - /// Thrown if the is . - /// Thrown is the operation has been disposed. - /// - /// - /// - public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action) - { - if (!op.TryAddCompletionCallback(action, SynchronizationContext.Current)) - { - action(op); - } - } - - /// - /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked synchronously. - /// - /// The target operation. - /// The callback to be executed when the operation has completed. - /// Thrown if the is . - /// Thrown is the operation has been disposed. - /// - /// - public static void AddCompletionCallback(this IAsyncOperation op, Action action) - { - if (!op.TryAddCompletionCallback(action, AsyncContinuationOptions.CaptureSynchronizationContext)) - { - action(); - } - } - - /// - /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked synchronously. - /// - /// The target operation. - /// The callback to be executed when the operation has completed. - /// Options for when the callback is executed. - /// Thrown if the is . - /// Thrown is the operation has been disposed. - /// - /// - /// - public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action, AsyncContinuationOptions options) - { - if (!op.TryAddCompletionCallback(action, options)) - { - if (AsyncContinuation.CanInvoke(op, options)) - { - action(op); - } - } - } - - /// - /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked synchronously. - /// - /// The target operation. - /// The callback to be executed when the operation has completed. - /// Options for when the callback is executed. - /// Thrown if the is . - /// Thrown is the operation has been disposed. - /// - /// - public static void AddCompletionCallback(this IAsyncOperation op, Action action, AsyncContinuationOptions options) - { - if (!op.TryAddCompletionCallback(action, options)) - { - if (AsyncContinuation.CanInvoke(op, options)) - { - action(); - } - } - } - - /// - /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked - /// on the specified. - /// - /// The target operation. - /// The callback to be executed when the operation has completed. - /// If not method attempts to marshal the continuation to the synchronization context. - /// Otherwise the callback is invoked on a thread that initiated the operation completion. - /// - /// Thrown if the is . - /// Thrown is the operation has been disposed. - /// - /// - /// - public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action, SynchronizationContext syncContext) - { - if (!op.TryAddCompletionCallback(action, syncContext)) - { - if (syncContext == null || syncContext.GetType() == typeof(SynchronizationContext) || syncContext == SynchronizationContext.Current) - { - action(op); - } - else - { - syncContext.Post(args => action(args as IAsyncOperation), op); - } - } - } - - #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) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - action(asyncOp); - result.TrySetCompleted(); - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }); - - return result; - } - - /// - /// 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)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; - - op.AddCompletionCallback( - asyncOp => - { - try - { - if (AsyncContinuation.CanInvoke(asyncOp, options)) - { - action(asyncOp); - result.TrySetCompleted(); - } - else - { - result.TrySetCanceled(); - } - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }, - syncContext); - - return result; - } - - /// - /// 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) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - action(asyncOp, userState); - result.TrySetCompleted(); - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }); - - return result; - } - - /// - /// 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)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; - - op.AddCompletionCallback( - asyncOp => - { - try - { - if (AsyncContinuation.CanInvoke(asyncOp, options)) - { - action(asyncOp, userState); - result.TrySetCompleted(); - } - else - { - result.TrySetCanceled(); - } - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }, - syncContext); - - return result; - } - - /// - /// 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) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - var resultValue = action(asyncOp); - result.TrySetResult(resultValue); - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }); - - return result; - } - - /// - /// 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)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; - - op.AddCompletionCallback( - asyncOp => - { - try - { - if (AsyncContinuation.CanInvoke(asyncOp, options)) - { - var resultValue = action(asyncOp); - result.TrySetResult(resultValue); - } - else - { - result.TrySetCanceled(); - } - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }, - syncContext); - - return result; - } - - /// - /// 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) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - var resultValue = action(asyncOp, userState); - result.TrySetResult(resultValue); - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }); - - return result; - } - - /// - /// 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)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; - - op.AddCompletionCallback( - asyncOp => - { - try - { - if (AsyncContinuation.CanInvoke(asyncOp, options)) - { - var resultValue = action(asyncOp, userState); - result.TrySetResult(resultValue); - } - else - { - result.TrySetCanceled(); - } - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }, - syncContext); - - return result; - } - - /// - /// 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) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - action(asyncOp as IAsyncOperation); - result.TrySetCompleted(); - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }); - - return result; - } - - /// - /// 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)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; - - op.AddCompletionCallback( - asyncOp => - { - try - { - if (AsyncContinuation.CanInvoke(asyncOp, options)) - { - action(asyncOp as IAsyncOperation); - result.TrySetCompleted(); - } - else - { - result.TrySetCanceled(); - } - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }, - syncContext); - - return result; - } - - /// - /// 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) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - action(asyncOp as IAsyncOperation, userState); - result.TrySetCompleted(); - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }); - - return result; - } - - /// - /// 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)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; - - op.AddCompletionCallback( - asyncOp => - { - try - { - if (AsyncContinuation.CanInvoke(asyncOp, options)) - { - action(asyncOp as IAsyncOperation, userState); - result.TrySetCompleted(); - } - else - { - result.TrySetCanceled(); - } - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }, - syncContext); - - return result; - } - - /// - /// 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, U> action) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - var resultValue = action(asyncOp as IAsyncOperation); - result.TrySetResult(resultValue); - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }); - - return result; - } - - /// - /// 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, U> action, AsyncContinuationOptions options) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; - - op.AddCompletionCallback( - asyncOp => - { - try - { - if (AsyncContinuation.CanInvoke(asyncOp, options)) - { - var resultValue = action(asyncOp as IAsyncOperation); - result.TrySetResult(resultValue); - } - else - { - result.TrySetCanceled(); - } - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }, - syncContext); - - return result; - } - - /// - /// 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, U> action, object userState) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - var resultValue = action(asyncOp as IAsyncOperation, userState); - result.TrySetResult(resultValue); - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }); - - return result; - } - - /// - /// 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, U> action, object userState, AsyncContinuationOptions options) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; - - op.AddCompletionCallback( - asyncOp => - { - try - { - if (AsyncContinuation.CanInvoke(asyncOp, options)) - { - var resultValue = action(asyncOp as IAsyncOperation, userState); - result.TrySetResult(resultValue); - } - else - { - result.TrySetCanceled(); - } - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }, - syncContext); - - return result; - } - - #endregion - -#if UNITYFX_SUPPORT_TAP - - #region GetAwaiter/ConfigureAwait - - /// - /// Provides an object that waits for the completion of an asynchronous operation. This type and its members are intended for compiler use only. - /// - /// - public struct AsyncAwaiter : INotifyCompletion - { - private readonly IAsyncOperation _op; - private readonly AsyncContinuationOptions _options; - - /// - /// Initializes a new instance of the struct. - /// - public AsyncAwaiter(IAsyncOperation op, AsyncContinuationOptions options) - { - _op = op; - _options = options; - } - - /// - /// Gets a value indicating whether the underlying operation is completed. - /// - /// The operation completion flag. - public bool IsCompleted => _op.IsCompleted; - - /// - /// Returns the source result value. - /// - public void GetResult() - { - if (!_op.IsCompletedSuccessfully) - { - ThrowIfNonSuccess(_op, false); - } - } - - /// - public void OnCompleted(Action continuation) - { - if (!_op.TryAddCompletionCallback(continuation, _options)) - { - continuation(); - } - } - } - - /// - /// Provides an object that waits for the completion of an asynchronous operation. This type and its members are intended for compiler use only. - /// - /// - public struct AsyncAwaiter : INotifyCompletion - { - private readonly IAsyncOperation _op; - private readonly AsyncContinuationOptions _options; - - /// - /// Initializes a new instance of the struct. - /// - public AsyncAwaiter(IAsyncOperation op, AsyncContinuationOptions options) - { - _op = op; - _options = options; - } - - /// - /// Gets a value indicating whether the underlying operation is completed. - /// - /// The operation completion flag. - public bool IsCompleted => _op.IsCompleted; - - /// - /// Returns the source result value. - /// - /// Returns the underlying operation result. - public T GetResult() - { - if (!_op.IsCompletedSuccessfully) - { - ThrowIfNonSuccess(_op, false); - } - - return _op.Result; - } - - /// - public void OnCompleted(Action continuation) - { - if (!_op.TryAddCompletionCallback(continuation, _options)) - { - continuation(); - } - } - } - - /// - /// Provides an awaitable object that allows for configured awaits on . This type is intended for compiler use only. - /// - /// - public struct ConfiguredAsyncAwaitable - { - private readonly AsyncAwaiter _awaiter; - - /// - /// Initializes a new instance of the struct. - /// - public ConfiguredAsyncAwaitable(IAsyncOperation op, bool continueOnCapturedContext) - { - _awaiter = new AsyncAwaiter(op, continueOnCapturedContext ? AsyncContinuationOptions.CaptureSynchronizationContext : AsyncContinuationOptions.None); - } - - /// - /// Returns the awaiter. - /// - public AsyncAwaiter GetAwaiter() => _awaiter; - } - - /// - /// Provides an awaitable object that allows for configured awaits on . This type is intended for compiler use only. - /// - /// - public struct ConfiguredAsyncAwaitable - { - private readonly AsyncAwaiter _awaiter; - - /// - /// Initializes a new instance of the struct. - /// - public ConfiguredAsyncAwaitable(IAsyncOperation op, bool continueOnCapturedContext) - { - _awaiter = new AsyncAwaiter(op, continueOnCapturedContext ? AsyncContinuationOptions.CaptureSynchronizationContext : AsyncContinuationOptions.None); - } - - /// - /// Returns the awaiter. - /// - public AsyncAwaiter GetAwaiter() => _awaiter; - } - - /// - /// Returns the operation awaiter. This method is intended for compiler rather than use directly in code. - /// - /// The operation to await. - /// - public static AsyncAwaiter GetAwaiter(this IAsyncOperation op) - { - return new AsyncAwaiter(op, AsyncContinuationOptions.CaptureSynchronizationContext); - } - - /// - /// Returns the operation awaiter. This method is intended for compiler rather than use directly in code. - /// - /// The operation to await. - /// - public static AsyncAwaiter GetAwaiter(this IAsyncOperation op) - { - return new AsyncAwaiter(op, AsyncContinuationOptions.CaptureSynchronizationContext); - } - - /// - /// 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 used to await the operation. - public static ConfiguredAsyncAwaitable ConfigureAwait(this IAsyncOperation op, bool continueOnCapturedContext) - { - return new ConfiguredAsyncAwaitable(op, continueOnCapturedContext); - } - - /// - /// 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 used to await the operation. - public static ConfiguredAsyncAwaitable ConfigureAwait(this IAsyncOperation op, bool continueOnCapturedContext) - { - return new ConfiguredAsyncAwaitable(op, continueOnCapturedContext); - } - - #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.InnerException); - } - else if (status == AsyncOperationStatus.Canceled) - { - return Task.FromCanceled(CancellationToken.None); - } - else - { - var result = new TaskCompletionSource(); - - if (!op.TryAddCompletionCallback(asyncOp => AsyncContinuation.InvokeTaskContinuation(asyncOp, result), null)) - { - AsyncContinuation.InvokeTaskContinuation(op, result); - } - - return result.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.InnerException); - } - else if (status == AsyncOperationStatus.Canceled) - { - return Task.FromCanceled(CancellationToken.None); - } - else - { - var result = new TaskCompletionSource(); - - if (!op.TryAddCompletionCallback(asyncOp => AsyncContinuation.InvokeTaskContinuation(asyncOp as IAsyncOperation, result), null)) - { - AsyncContinuation.InvokeTaskContinuation(op, result); - } - - return result.Task; - } - } - - #endregion - -#endif - -#if !NET35 - - #region ToObservable - - /// - /// Creates a instance that can be used to track the source operation progress. - /// - /// Type of the operation result. - /// The operation to track. - /// Returns an instance that can be used to track the operation. - public static IObservable ToObservable(this IAsyncOperation op) - { - if (op is AsyncResult ar) - { - return ar; - } - - return new AsyncObservable(op); - } - - #endregion - -#endif - - #endregion - - #region IAsyncCompletionSource - - /// - /// 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(); - } - } - - /// - /// 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(); - } - } - - /// - /// 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(); - } - } - - /// - /// 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(); - } - } - - /// - /// 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(); - } - } - - /// - /// 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(); - } - } - - /// - /// 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(); - } - } - - /// - /// 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, T result) - { - if (!completionSource.TrySetResult(result)) - { - throw new InvalidOperationException(); - } - } - - #endregion - - #region Task - -#if UNITYFX_SUPPORT_TAP - - /// - /// 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) - { - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - task.ContinueWith( - t => - { - if (t.IsFaulted) - { - result.SetException(t.Exception); - } - else if (t.IsCanceled) - { - result.SetCanceled(); - } - else - { - result.SetCompleted(); - } - }, - TaskContinuationOptions.ExecuteSynchronously); - - return result; - } - - /// - /// 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) - { - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - task.ContinueWith( - t => - { - if (t.IsFaulted) - { - result.SetException(t.Exception); - } - else if (t.IsCanceled) - { - result.SetCanceled(); - } - else - { - result.SetResult(t.Result); - } - }, - TaskContinuationOptions.ExecuteSynchronously); - - return result; - } - -#endif - - #endregion - } -} diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs new file mode 100644 index 0000000..5e16868 --- /dev/null +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs @@ -0,0 +1,734 @@ +// 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 AddCompletionCallback + + /// + /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked synchronously. + /// + /// The target operation. + /// The callback to be executed when the operation has completed. + /// Thrown if the is . + /// Thrown is the operation has been disposed. + /// + /// + /// + public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action) + { + if (!op.TryAddCompletionCallback(action, SynchronizationContext.Current)) + { + action(op); + } + } + + /// + /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked synchronously. + /// + /// The target operation. + /// The callback to be executed when the operation has completed. + /// Thrown if the is . + /// Thrown is the operation has been disposed. + /// + /// + public static void AddCompletionCallback(this IAsyncOperation op, Action action) + { + if (!op.TryAddCompletionCallback(action, AsyncContinuationOptions.CaptureSynchronizationContext)) + { + action(); + } + } + + /// + /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked synchronously. + /// + /// The target operation. + /// The callback to be executed when the operation has completed. + /// Options for when the callback is executed. + /// Thrown if the is . + /// Thrown is the operation has been disposed. + /// + /// + /// + public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action, AsyncContinuationOptions options) + { + if (!op.TryAddCompletionCallback(action, options)) + { + if (AsyncContinuation.CanInvoke(op, options)) + { + action(op); + } + } + } + + /// + /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked synchronously. + /// + /// The target operation. + /// The callback to be executed when the operation has completed. + /// Options for when the callback is executed. + /// Thrown if the is . + /// Thrown is the operation has been disposed. + /// + /// + public static void AddCompletionCallback(this IAsyncOperation op, Action action, AsyncContinuationOptions options) + { + if (!op.TryAddCompletionCallback(action, options)) + { + if (AsyncContinuation.CanInvoke(op, options)) + { + action(); + } + } + } + + /// + /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked + /// on the specified. + /// + /// The target operation. + /// The callback to be executed when the operation has completed. + /// If not method attempts to marshal the continuation to the synchronization context. + /// Otherwise the callback is invoked on a thread that initiated the operation completion. + /// + /// Thrown if the is . + /// Thrown is the operation has been disposed. + /// + /// + /// + public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action, SynchronizationContext syncContext) + { + if (!op.TryAddCompletionCallback(action, syncContext)) + { + if (syncContext == null || syncContext.GetType() == typeof(SynchronizationContext) || syncContext == SynchronizationContext.Current) + { + action(op); + } + else + { + syncContext.Post(args => action(args as IAsyncOperation), op); + } + } + } + + #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) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback(asyncOp => + { + try + { + action(asyncOp); + result.TrySetCompleted(); + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }); + + return result; + } + + /// + /// 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)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; + + op.AddCompletionCallback( + asyncOp => + { + try + { + if (AsyncContinuation.CanInvoke(asyncOp, options)) + { + action(asyncOp); + result.TrySetCompleted(); + } + else + { + result.TrySetCanceled(); + } + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }, + syncContext); + + return result; + } + + /// + /// 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) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback(asyncOp => + { + try + { + action(asyncOp, userState); + result.TrySetCompleted(); + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }); + + return result; + } + + /// + /// 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)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; + + op.AddCompletionCallback( + asyncOp => + { + try + { + if (AsyncContinuation.CanInvoke(asyncOp, options)) + { + action(asyncOp, userState); + result.TrySetCompleted(); + } + else + { + result.TrySetCanceled(); + } + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }, + syncContext); + + return result; + } + + /// + /// 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) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback(asyncOp => + { + try + { + var resultValue = action(asyncOp); + result.TrySetResult(resultValue); + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }); + + return result; + } + + /// + /// 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)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; + + op.AddCompletionCallback( + asyncOp => + { + try + { + if (AsyncContinuation.CanInvoke(asyncOp, options)) + { + var resultValue = action(asyncOp); + result.TrySetResult(resultValue); + } + else + { + result.TrySetCanceled(); + } + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }, + syncContext); + + return result; + } + + /// + /// 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) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback(asyncOp => + { + try + { + var resultValue = action(asyncOp, userState); + result.TrySetResult(resultValue); + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }); + + return result; + } + + /// + /// 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)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; + + op.AddCompletionCallback( + asyncOp => + { + try + { + if (AsyncContinuation.CanInvoke(asyncOp, options)) + { + var resultValue = action(asyncOp, userState); + result.TrySetResult(resultValue); + } + else + { + result.TrySetCanceled(); + } + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }, + syncContext); + + return result; + } + + /// + /// 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) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback(asyncOp => + { + try + { + action(asyncOp as IAsyncOperation); + result.TrySetCompleted(); + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }); + + return result; + } + + /// + /// 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)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; + + op.AddCompletionCallback( + asyncOp => + { + try + { + if (AsyncContinuation.CanInvoke(asyncOp, options)) + { + action(asyncOp as IAsyncOperation); + result.TrySetCompleted(); + } + else + { + result.TrySetCanceled(); + } + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }, + syncContext); + + return result; + } + + /// + /// 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) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback(asyncOp => + { + try + { + action(asyncOp as IAsyncOperation, userState); + result.TrySetCompleted(); + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }); + + return result; + } + + /// + /// 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)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; + + op.AddCompletionCallback( + asyncOp => + { + try + { + if (AsyncContinuation.CanInvoke(asyncOp, options)) + { + action(asyncOp as IAsyncOperation, userState); + result.TrySetCompleted(); + } + else + { + result.TrySetCanceled(); + } + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }, + syncContext); + + return result; + } + + /// + /// 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, U> action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback(asyncOp => + { + try + { + var resultValue = action(asyncOp as IAsyncOperation); + result.TrySetResult(resultValue); + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }); + + return result; + } + + /// + /// 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, U> action, AsyncContinuationOptions options) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; + + op.AddCompletionCallback( + asyncOp => + { + try + { + if (AsyncContinuation.CanInvoke(asyncOp, options)) + { + var resultValue = action(asyncOp as IAsyncOperation); + result.TrySetResult(resultValue); + } + else + { + result.TrySetCanceled(); + } + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }, + syncContext); + + return result; + } + + /// + /// 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, U> action, object userState) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback(asyncOp => + { + try + { + var resultValue = action(asyncOp as IAsyncOperation, userState); + result.TrySetResult(resultValue); + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }); + + return result; + } + + /// + /// 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, U> action, object userState, AsyncContinuationOptions options) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; + + op.AddCompletionCallback( + asyncOp => + { + try + { + if (AsyncContinuation.CanInvoke(asyncOp, options)) + { + var resultValue = action(asyncOp as IAsyncOperation, userState); + result.TrySetResult(resultValue); + } + else + { + result.TrySetCanceled(); + } + } + catch (Exception e) + { + result.TrySetException(e, false); + } + }, + syncContext); + + return result; + } + + #endregion + } +} diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs new file mode 100644 index 0000000..a614e76 --- /dev/null +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs @@ -0,0 +1,421 @@ +// 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 +{ + partial class AsyncExtensions + { + #region Then + + /// + /// Schedules a callback to be executed after the operation has succeeded. + /// + /// The target operation. + /// The callback to be executed when the operation has completed. + /// TODO + /// + public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback(asyncOp => + { + try + { + if (asyncOp.IsCompletedSuccessfully) + { + successCallback(); + result.TrySetCompleted(); + } + else + { + result.TrySetException(asyncOp.Exception); + } + } + catch (Exception e) + { + result.TrySetException(e); + } + }); + + return result; + } + + /// + /// Schedules a callback to be executed after the operation has succeeded. + /// + /// The target operation. + /// The callback to be executed when the operation has completed. + /// TODO + /// + public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback(asyncOp => + { + try + { + if (asyncOp.IsCompletedSuccessfully) + { + successCallback((asyncOp as IAsyncOperation).Result); + result.TrySetCompleted(); + } + else + { + result.TrySetException(asyncOp.Exception); + } + } + catch (Exception e) + { + result.TrySetException(e); + } + }); + + return result; + } + + /// + /// Schedules a callback to be executed after the operation has succeeded. + /// + /// The target operation. + /// The callback to be executed when the operation has completed. + /// + public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback(asyncOp => + { + try + { + if (asyncOp.IsCompletedSuccessfully) + { + successCallback().AddCompletionCallback(asyncOp2 => result.CopyCompletionState(asyncOp2, false), null); + } + else + { + result.TrySetException(asyncOp.Exception); + } + } + catch (Exception e) + { + result.TrySetException(e); + } + }); + + return result; + } + + /// + /// Schedules a callback to be executed after the operation has succeeded. + /// + /// The target operation. + /// The callback to be executed when the operation has completed. + /// + public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback(asyncOp => + { + try + { + if (asyncOp.IsCompletedSuccessfully) + { + successCallback((asyncOp as IAsyncOperation).Result).AddCompletionCallback(asyncOp2 => result.CopyCompletionState(asyncOp2, false), null); + } + else + { + result.TrySetException(asyncOp.Exception); + } + } + catch (Exception e) + { + result.TrySetException(e); + } + }); + + return result; + } + + /// + /// Schedules a callbacks to be executed after the operation has completed. + /// + /// The target operation. + /// The callback to be executed when the operation has succeeded. + /// The callback to be executed when the operation has faulted/was canceled. + /// + public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback, Action errorCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + if (errorCallback == null) + { + throw new ArgumentNullException(nameof(errorCallback)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback(asyncOp => + { + try + { + if (asyncOp.IsCompletedSuccessfully) + { + successCallback(); + result.TrySetCompleted(); + } + else + { + errorCallback(asyncOp.Exception.InnerException); + result.TrySetException(asyncOp.Exception); + } + } + catch (Exception e) + { + result.TrySetException(e); + } + }); + + return result; + } + + /// + /// Schedules a callbacks to be executed after the operation has completed. + /// + /// The target operation. + /// The callback to be executed when the operation has succeeded. + /// The callback to be executed when the operation has faulted/was canceled. + /// + public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback, Action errorCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + if (errorCallback == null) + { + throw new ArgumentNullException(nameof(errorCallback)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback(asyncOp => + { + try + { + if (asyncOp.IsCompletedSuccessfully) + { + successCallback((asyncOp as IAsyncOperation).Result); + result.TrySetCompleted(); + } + else + { + errorCallback(asyncOp.Exception.InnerException); + result.TrySetException(asyncOp.Exception); + } + } + catch (Exception e) + { + result.TrySetException(e); + } + }); + + return result; + } + + /// + /// Adds a completion callback to be executed after the operation has succeeded. + /// + /// The target operation. + /// The callback to be executed when the operation has succeeded. + /// The callback to be executed when the operation has faulted/was canceled. + /// + public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback, Action errorCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + if (errorCallback == null) + { + throw new ArgumentNullException(nameof(errorCallback)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback(asyncOp => + { + try + { + if (asyncOp.IsCompletedSuccessfully) + { + successCallback().AddCompletionCallback(asyncOp2 => result.CopyCompletionState(asyncOp2, false), null); + } + else + { + errorCallback(asyncOp.Exception.InnerException); + result.TrySetException(asyncOp.Exception); + } + } + catch (Exception e) + { + result.TrySetException(e); + } + }); + + return result; + } + + /// + /// Adds a completion callback to be executed after the operation has succeeded. + /// + /// The target operation. + /// The callback to be executed when the operation has succeeded. + /// The callback to be executed when the operation has faulted/was canceled. + /// + public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback, Action errorCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + if (errorCallback == null) + { + throw new ArgumentNullException(nameof(errorCallback)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback(asyncOp => + { + try + { + if (asyncOp.IsCompletedSuccessfully) + { + successCallback((asyncOp as IAsyncOperation).Result).AddCompletionCallback(asyncOp2 => result.CopyCompletionState(asyncOp2, false), null); + } + else + { + errorCallback(asyncOp.Exception.InnerException); + result.TrySetException(asyncOp.Exception); + } + } + catch (Exception e) + { + result.TrySetException(e); + } + }); + + return result; + } + + #endregion + + #region Catch + + /// + /// Adds a completion callback to be executed after the operation has faulted or was canceled. + /// + /// The target operation. + /// The callback to be executed when the operation has faulted/was canceled. + /// + public static IAsyncOperation Catch(this IAsyncOperation op, Action errorCallback) + { + if (errorCallback == null) + { + throw new ArgumentNullException(nameof(errorCallback)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback(asyncOp => + { + try + { + if (asyncOp.IsCompletedSuccessfully) + { + result.TrySetCompleted(); + } + else + { + errorCallback(asyncOp.Exception.InnerException); + result.TrySetCompleted(); + } + } + catch (Exception e) + { + result.TrySetException(e); + } + }); + + return result; + } + + #endregion + + #region Finally + + /// + /// Adds a completion callback to be executed after the operation has completed. + /// + /// The target operation. + /// The callback to be executed when the operation has completed. + /// + public static IAsyncOperation Finally(this IAsyncOperation op, Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + op.AddCompletionCallback(asyncOp => + { + try + { + action(); + result.TrySetCompleted(); + } + catch (Exception e) + { + result.TrySetException(e); + } + }); + + return result; + } + + #endregion + } +} diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs new file mode 100644 index 0000000..1d20627 --- /dev/null +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs @@ -0,0 +1,338 @@ +// 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 UNITYFX_SUPPORT_TAP +using System.Threading.Tasks; +#endif + +namespace UnityFx.Async +{ +#if UNITYFX_SUPPORT_TAP + + partial class AsyncExtensions + { + #region GetAwaiter/ConfigureAwait + + /// + /// Provides an object that waits for the completion of an asynchronous operation. This type and its members are intended for compiler use only. + /// + /// + public struct AsyncAwaiter : INotifyCompletion + { + private readonly IAsyncOperation _op; + private readonly AsyncContinuationOptions _options; + + /// + /// Initializes a new instance of the struct. + /// + public AsyncAwaiter(IAsyncOperation op, AsyncContinuationOptions options) + { + _op = op; + _options = options; + } + + /// + /// Gets a value indicating whether the underlying operation is completed. + /// + /// The operation completion flag. + public bool IsCompleted => _op.IsCompleted; + + /// + /// Returns the source result value. + /// + public void GetResult() + { + if (!_op.IsCompletedSuccessfully) + { + ThrowIfNonSuccess(_op, false); + } + } + + /// + public void OnCompleted(Action continuation) + { + if (!_op.TryAddCompletionCallback(continuation, _options)) + { + continuation(); + } + } + } + + /// + /// Provides an object that waits for the completion of an asynchronous operation. This type and its members are intended for compiler use only. + /// + /// + public struct AsyncAwaiter : INotifyCompletion + { + private readonly IAsyncOperation _op; + private readonly AsyncContinuationOptions _options; + + /// + /// Initializes a new instance of the struct. + /// + public AsyncAwaiter(IAsyncOperation op, AsyncContinuationOptions options) + { + _op = op; + _options = options; + } + + /// + /// Gets a value indicating whether the underlying operation is completed. + /// + /// The operation completion flag. + public bool IsCompleted => _op.IsCompleted; + + /// + /// Returns the source result value. + /// + /// Returns the underlying operation result. + public T GetResult() + { + if (!_op.IsCompletedSuccessfully) + { + ThrowIfNonSuccess(_op, false); + } + + return _op.Result; + } + + /// + public void OnCompleted(Action continuation) + { + if (!_op.TryAddCompletionCallback(continuation, _options)) + { + continuation(); + } + } + } + + /// + /// Provides an awaitable object that allows for configured awaits on . This type is intended for compiler use only. + /// + /// + public struct ConfiguredAsyncAwaitable + { + private readonly AsyncAwaiter _awaiter; + + /// + /// Initializes a new instance of the struct. + /// + public ConfiguredAsyncAwaitable(IAsyncOperation op, bool continueOnCapturedContext) + { + _awaiter = new AsyncAwaiter(op, continueOnCapturedContext ? AsyncContinuationOptions.CaptureSynchronizationContext : AsyncContinuationOptions.None); + } + + /// + /// Returns the awaiter. + /// + public AsyncAwaiter GetAwaiter() => _awaiter; + } + + /// + /// Provides an awaitable object that allows for configured awaits on . This type is intended for compiler use only. + /// + /// + public struct ConfiguredAsyncAwaitable + { + private readonly AsyncAwaiter _awaiter; + + /// + /// Initializes a new instance of the struct. + /// + public ConfiguredAsyncAwaitable(IAsyncOperation op, bool continueOnCapturedContext) + { + _awaiter = new AsyncAwaiter(op, continueOnCapturedContext ? AsyncContinuationOptions.CaptureSynchronizationContext : AsyncContinuationOptions.None); + } + + /// + /// Returns the awaiter. + /// + public AsyncAwaiter GetAwaiter() => _awaiter; + } + + /// + /// Returns the operation awaiter. This method is intended for compiler rather than use directly in code. + /// + /// The operation to await. + /// + public static AsyncAwaiter GetAwaiter(this IAsyncOperation op) + { + return new AsyncAwaiter(op, AsyncContinuationOptions.CaptureSynchronizationContext); + } + + /// + /// Returns the operation awaiter. This method is intended for compiler rather than use directly in code. + /// + /// The operation to await. + /// + public static AsyncAwaiter GetAwaiter(this IAsyncOperation op) + { + return new AsyncAwaiter(op, AsyncContinuationOptions.CaptureSynchronizationContext); + } + + /// + /// 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 used to await the operation. + public static ConfiguredAsyncAwaitable ConfigureAwait(this IAsyncOperation op, bool continueOnCapturedContext) + { + return new ConfiguredAsyncAwaitable(op, continueOnCapturedContext); + } + + /// + /// 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 used to await the operation. + public static ConfiguredAsyncAwaitable ConfigureAwait(this IAsyncOperation op, bool continueOnCapturedContext) + { + return new ConfiguredAsyncAwaitable(op, continueOnCapturedContext); + } + + #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.InnerException); + } + else if (status == AsyncOperationStatus.Canceled) + { + return Task.FromCanceled(CancellationToken.None); + } + else + { + var result = new TaskCompletionSource(); + + if (!op.TryAddCompletionCallback(asyncOp => AsyncContinuation.InvokeTaskContinuation(asyncOp, result), null)) + { + AsyncContinuation.InvokeTaskContinuation(op, result); + } + + return result.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.InnerException); + } + else if (status == AsyncOperationStatus.Canceled) + { + return Task.FromCanceled(CancellationToken.None); + } + else + { + var result = new TaskCompletionSource(); + + if (!op.TryAddCompletionCallback(asyncOp => AsyncContinuation.InvokeTaskContinuation(asyncOp as IAsyncOperation, result), null)) + { + AsyncContinuation.InvokeTaskContinuation(op, result); + } + + return result.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) + { + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + task.ContinueWith( + t => + { + if (t.IsFaulted) + { + result.SetException(t.Exception); + } + else if (t.IsCanceled) + { + result.SetCanceled(); + } + else + { + result.SetCompleted(); + } + }, + TaskContinuationOptions.ExecuteSynchronously); + + return result; + } + + /// + /// 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) + { + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + task.ContinueWith( + t => + { + if (t.IsFaulted) + { + result.SetException(t.Exception); + } + else if (t.IsCanceled) + { + result.SetCanceled(); + } + else + { + result.SetResult(t.Result); + } + }, + TaskContinuationOptions.ExecuteSynchronously); + + return result; + } + + #endregion + } + +#endif +} diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs new file mode 100644 index 0000000..b152573 --- /dev/null +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs @@ -0,0 +1,257 @@ +// 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. + /// + /// The operation to wait for. + /// Thrown if the operation was canceled or faulted. + /// Thrown is the operation is disposed. + /// + /// + public static void Wait(this IAsyncOperation op) + { + if (!op.IsCompleted) + { + op.AsyncWaitHandle.WaitOne(); + } + + ThrowIfNonSuccess(op, true); + } + + /// + /// Waits for the to complete execution within a specified number of milliseconds. + /// + /// 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 if the operation was canceled or faulted. + /// 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, true); + } + + return result; + } + + /// + /// Waits for the to complete execution within a specified time interval. + /// + /// 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 if the operation was canceled or faulted. + /// 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, true); + } + + return result; + } + + #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, false); + } + + /// + /// 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, false); + } + 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, false); + } + 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 T Join(this IAsyncOperation op) + { + if (!op.IsCompleted) + { + op.AsyncWaitHandle.WaitOne(); + } + + ThrowIfNonSuccess(op, false); + 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 T Join(this IAsyncOperation op, int millisecondsTimeout) + { + var result = true; + + if (!op.IsCompleted) + { + result = op.AsyncWaitHandle.WaitOne(millisecondsTimeout); + } + + if (result) + { + ThrowIfNonSuccess(op, false); + } + else + { + 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 T Join(this IAsyncOperation op, TimeSpan timeout) + { + var result = true; + + if (!op.IsCompleted) + { + result = op.AsyncWaitHandle.WaitOne(timeout); + } + + if (result) + { + ThrowIfNonSuccess(op, false); + } + else + { + throw new TimeoutException(); + } + + return op.Result; + } + + #endregion + } +} diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs new file mode 100644 index 0000000..3c884ed --- /dev/null +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs @@ -0,0 +1,253 @@ +// 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.Threading; + +namespace UnityFx.Async +{ + /// + /// Extension methods for related classes. + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static partial class AsyncExtensions + { + #region Common + + /// + /// Spins until the operation has completed. + /// + 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 + } + + /// + /// Throws exception if the operation has failed or canceled. + /// + internal static void ThrowIfNonSuccess(IAsyncOperation op, bool throwAggregate) + { + if (op is AsyncResult ar) + { + ar.ThrowIfNonSuccess(throwAggregate); + } + else + { + var status = op.Status; + + if (status == AsyncOperationStatus.Faulted) + { + if (throwAggregate) + { + throw op.Exception; + } + else if (!AsyncResult.TryThrowException(op.Exception)) + { + // Should never get here. If faulted state excpetion should not be null. + throw new Exception(); + } + } + else if (status == AsyncOperationStatus.Canceled) + { + if (throwAggregate) + { + throw op.Exception; + } + else if (!AsyncResult.TryThrowException(op.Exception)) + { + throw new OperationCanceledException(); + } + } + } + } + +#if !NET35 + + /// + /// Creates a instance that can be used to track the source operation progress. + /// + /// Type of the operation result. + /// The operation to track. + /// Returns an instance that can be used to track the operation. + public static IObservable ToObservable(this IAsyncOperation op) + { + if (op is AsyncResult ar) + { + return ar; + } + + return new AsyncObservable(op); + } + +#endif + + #endregion + + #region IAsyncCompletionSource + + /// + /// 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(); + } + } + + /// + /// 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(); + } + } + + /// + /// 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(); + } + } + + /// + /// 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(); + } + } + + /// + /// 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(); + } + } + + /// + /// 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(); + } + } + + /// + /// 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(); + } + } + + /// + /// 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, T result) + { + if (!completionSource.TrySetResult(result)) + { + throw new InvalidOperationException(); + } + } + + #endregion + } +} From 370a2a07f38e5c30ce581ddf281e0e343bd46865 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 30 Mar 2018 17:56:25 +0300 Subject: [PATCH 017/128] Removed Action completion callbacks from public interface --- src/UnityFx.Async/Api/Core/AsyncResult.cs | 39 +++++---------- .../AsyncExtensions.Continuations.cs | 38 --------------- .../Api/Extensions/AsyncExtensions.Tasks.cs | 48 ++++++++++++------- .../Api/Interfaces/IAsyncOperationEvents.cs | 21 -------- 4 files changed, 43 insertions(+), 103 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index dd27371..f1779a3 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -1313,6 +1313,19 @@ internal void SetCompleted(int status, bool completedSynchronously) OnCompleted(); } + /// + /// Adds a completion callback for await implementation. + /// + internal void SetContinuationForAwait(Action continuation, SynchronizationContext syncContext) + { + ThrowIfDisposed(); + + if (!TryAddContinuation(continuation, AsyncContinuationOptions.None, syncContext)) + { + continuation(); + } + } + /// /// Rethrows the specified . /// @@ -1421,32 +1434,6 @@ public bool RemoveCompletionCallback(AsyncOperationCallback action) return false; } - /// - public bool TryAddCompletionCallback(Action action, AsyncContinuationOptions options) - { - ThrowIfDisposed(); - - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - return TryAddContinuation(action, options, null); - } - - /// - public bool RemoveCompletionCallback(Action action) - { - ThrowIfDisposed(); - - if (action != null) - { - return TryRemoveContinuation(action); - } - - return false; - } - #endregion #region IAsyncResult diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs index 5e16868..bc1e2f2 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs @@ -28,23 +28,6 @@ public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperation } } - /// - /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked synchronously. - /// - /// The target operation. - /// The callback to be executed when the operation has completed. - /// Thrown if the is . - /// Thrown is the operation has been disposed. - /// - /// - public static void AddCompletionCallback(this IAsyncOperation op, Action action) - { - if (!op.TryAddCompletionCallback(action, AsyncContinuationOptions.CaptureSynchronizationContext)) - { - action(); - } - } - /// /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked synchronously. /// @@ -67,27 +50,6 @@ public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperation } } - /// - /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked synchronously. - /// - /// The target operation. - /// The callback to be executed when the operation has completed. - /// Options for when the callback is executed. - /// Thrown if the is . - /// Thrown is the operation has been disposed. - /// - /// - public static void AddCompletionCallback(this IAsyncOperation op, Action action, AsyncContinuationOptions options) - { - if (!op.TryAddCompletionCallback(action, options)) - { - if (AsyncContinuation.CanInvoke(op, options)) - { - action(); - } - } - } - /// /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked /// on the specified. diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs index 1d20627..560b41a 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs @@ -23,15 +23,15 @@ partial class AsyncExtensions public struct AsyncAwaiter : INotifyCompletion { private readonly IAsyncOperation _op; - private readonly AsyncContinuationOptions _options; + private readonly bool _continueOnCapturedContext; /// /// Initializes a new instance of the struct. /// - public AsyncAwaiter(IAsyncOperation op, AsyncContinuationOptions options) + public AsyncAwaiter(IAsyncOperation op, bool continueOnCapturedContext) { _op = op; - _options = options; + _continueOnCapturedContext = continueOnCapturedContext; } /// @@ -54,10 +54,7 @@ public void GetResult() /// public void OnCompleted(Action continuation) { - if (!_op.TryAddCompletionCallback(continuation, _options)) - { - continuation(); - } + SetAwaitContiniation(_op, continuation, _continueOnCapturedContext); } } @@ -68,15 +65,15 @@ public void OnCompleted(Action continuation) public struct AsyncAwaiter : INotifyCompletion { private readonly IAsyncOperation _op; - private readonly AsyncContinuationOptions _options; + private readonly bool _continueOnCapturedContext; /// /// Initializes a new instance of the struct. /// - public AsyncAwaiter(IAsyncOperation op, AsyncContinuationOptions options) + public AsyncAwaiter(IAsyncOperation op, bool continueOnCapturedContext) { _op = op; - _options = options; + _continueOnCapturedContext = continueOnCapturedContext; } /// @@ -102,10 +99,7 @@ public T GetResult() /// public void OnCompleted(Action continuation) { - if (!_op.TryAddCompletionCallback(continuation, _options)) - { - continuation(); - } + SetAwaitContiniation(_op, continuation, _continueOnCapturedContext); } } @@ -122,7 +116,7 @@ public struct ConfiguredAsyncAwaitable /// public ConfiguredAsyncAwaitable(IAsyncOperation op, bool continueOnCapturedContext) { - _awaiter = new AsyncAwaiter(op, continueOnCapturedContext ? AsyncContinuationOptions.CaptureSynchronizationContext : AsyncContinuationOptions.None); + _awaiter = new AsyncAwaiter(op, continueOnCapturedContext); } /// @@ -144,7 +138,7 @@ public struct ConfiguredAsyncAwaitable /// public ConfiguredAsyncAwaitable(IAsyncOperation op, bool continueOnCapturedContext) { - _awaiter = new AsyncAwaiter(op, continueOnCapturedContext ? AsyncContinuationOptions.CaptureSynchronizationContext : AsyncContinuationOptions.None); + _awaiter = new AsyncAwaiter(op, continueOnCapturedContext); } /// @@ -160,7 +154,7 @@ public ConfiguredAsyncAwaitable(IAsyncOperation op, bool continueOnCapturedCo /// public static AsyncAwaiter GetAwaiter(this IAsyncOperation op) { - return new AsyncAwaiter(op, AsyncContinuationOptions.CaptureSynchronizationContext); + return new AsyncAwaiter(op, false); } /// @@ -170,7 +164,7 @@ public static AsyncAwaiter GetAwaiter(this IAsyncOperation op) /// public static AsyncAwaiter GetAwaiter(this IAsyncOperation op) { - return new AsyncAwaiter(op, AsyncContinuationOptions.CaptureSynchronizationContext); + return new AsyncAwaiter(op, false); } /// @@ -332,6 +326,24 @@ public static AsyncResult ToAsync(this Task task) } #endregion + + #region Implementation + + private static void SetAwaitContiniation(IAsyncOperation op, Action continuation, bool captureSynchronizationContext) + { + var syncContext = captureSynchronizationContext ? SynchronizationContext.Current : null; + + if (op is AsyncResult ar) + { + ar.SetContinuationForAwait(continuation, syncContext); + } + else if (!op.TryAddCompletionCallback(asyncOp => continuation(), syncContext)) + { + continuation(); + } + } + + #endregion } #endif diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs index 81c87d9..2865903 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs @@ -119,26 +119,5 @@ public interface IAsyncOperationEvents /// /// bool RemoveCompletionCallback(AsyncOperationCallback action); - - /// - /// Attempts to add a completion callback to be executed after the operation has finished. If the operation is already completed - /// the method does nothing and just returns . - /// - /// The callback to be executed when the operation has completed. - /// Options for when the callback is executed. - /// Returns if the callback was added; otherwise (the operation is completed). - /// Thrown if the is . - /// Thrown is the operation has been disposed. - /// - bool TryAddCompletionCallback(Action action, AsyncContinuationOptions options); - - /// - /// Removes an existing completion callback. - /// - /// The callback to remove. Can be . - /// Returns if the was removed; otherwise. - /// Thrown is the operation has been disposed. - /// - bool RemoveCompletionCallback(Action action); } } From c76858f081f7acfb2c8bf27cf795671d0554d30a Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 30 Mar 2018 20:12:52 +0300 Subject: [PATCH 018/128] Removed Action continuations support from public API --- src/UnityFx.Async/Api/Core/AsyncResult.cs | 32 ++++--------------- .../AsyncExtensions.Continuations.cs | 24 -------------- .../Api/Interfaces/IAsyncOperationEvents.cs | 16 ---------- .../Implementation/AsyncContinuation.cs | 31 ++++++++---------- 4 files changed, 19 insertions(+), 84 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index f1779a3..d4d3e2e 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -1320,7 +1320,7 @@ internal void SetContinuationForAwait(Action continuation, SynchronizationContex { ThrowIfDisposed(); - if (!TryAddContinuation(continuation, AsyncContinuationOptions.None, syncContext)) + if (!TryAddContinuation(continuation, syncContext)) { continuation(); } @@ -1379,7 +1379,7 @@ public event AsyncOperationCallback Completed throw new ArgumentNullException(nameof(value)); } - if (!TryAddContinuation(value, AsyncContinuationOptions.None, SynchronizationContext.Current)) + if (!TryAddContinuation(value, SynchronizationContext.Current)) { value(this); } @@ -1395,19 +1395,6 @@ public event AsyncOperationCallback Completed } } - /// - public bool TryAddCompletionCallback(AsyncOperationCallback action, AsyncContinuationOptions options) - { - ThrowIfDisposed(); - - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - return TryAddContinuation(action, options, null); - } - /// public bool TryAddCompletionCallback(AsyncOperationCallback action, SynchronizationContext syncContext) { @@ -1418,7 +1405,7 @@ public bool TryAddCompletionCallback(AsyncOperationCallback action, Synchronizat throw new ArgumentNullException(nameof(action)); } - return TryAddContinuation(action, AsyncContinuationOptions.None, syncContext); + return TryAddContinuation(action, syncContext); } /// @@ -1596,20 +1583,13 @@ private AsyncResult(int flags) /// Attempts to register a continuation object. For internal use only. /// /// The continuation object to add. - /// Continuation options. /// A instance to execute continuation on. /// Returns if the continuation was added; otherwise. - private bool TryAddContinuation(object continuation, AsyncContinuationOptions options, SynchronizationContext syncContext) + private bool TryAddContinuation(object continuation, SynchronizationContext syncContext) { - if ((options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0) - { - syncContext = SynchronizationContext.Current; - options &= ~AsyncContinuationOptions.CaptureSynchronizationContext; - } - - if (options != AsyncContinuationOptions.None || (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext))) + if (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) { - continuation = new AsyncContinuation(this, options, syncContext, continuation); + continuation = new AsyncContinuation(this, syncContext, continuation); } return TryAddContinuation(continuation); diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs index bc1e2f2..3306cee 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs @@ -18,7 +18,6 @@ partial class AsyncExtensions /// Thrown if the is . /// Thrown is the operation has been disposed. /// - /// /// public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action) { @@ -28,28 +27,6 @@ public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperation } } - /// - /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked synchronously. - /// - /// The target operation. - /// The callback to be executed when the operation has completed. - /// Options for when the callback is executed. - /// Thrown if the is . - /// Thrown is the operation has been disposed. - /// - /// - /// - public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action, AsyncContinuationOptions options) - { - if (!op.TryAddCompletionCallback(action, options)) - { - if (AsyncContinuation.CanInvoke(op, options)) - { - action(op); - } - } - } - /// /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked /// on the specified. @@ -63,7 +40,6 @@ public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperation /// Thrown is the operation has been disposed. /// /// - /// public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action, SynchronizationContext syncContext) { if (!op.TryAddCompletionCallback(action, syncContext)) diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs index 2865903..b2b84ef 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs @@ -77,24 +77,10 @@ public interface IAsyncOperationEvents /// /// Thrown if the delegate being registered is . /// Thrown is the operation has been disposed. - /// /// /// event AsyncOperationCallback Completed; - /// - /// Attempts to add a completion callback to be executed after the operation has finished. If the operation is already completed - /// the method does nothing and just returns . - /// - /// The callback to be executed when the operation has completed. - /// Options for when the callback is executed. - /// Returns if the callback was added; otherwise (the operation is completed). - /// Thrown if the is . - /// Thrown is the operation has been disposed. - /// - /// - bool TryAddCompletionCallback(AsyncOperationCallback action, AsyncContinuationOptions options); - /// /// Attempts to add a completion callback to be executed after the operation has finished. If the operation is already completed /// the method does nothing and just returns . @@ -106,7 +92,6 @@ public interface IAsyncOperationEvents /// Returns if the callback was added; otherwise (the operation is completed). /// Thrown if the is . /// Thrown is the operation has been disposed. - /// /// bool TryAddCompletionCallback(AsyncOperationCallback action, SynchronizationContext syncContext); @@ -116,7 +101,6 @@ public interface IAsyncOperationEvents /// The callback to remove. Can be . /// Returns if the was removed; otherwise. /// Thrown is the operation has been disposed. - /// /// bool RemoveCompletionCallback(AsyncOperationCallback action); } diff --git a/src/UnityFx.Async/Implementation/AsyncContinuation.cs b/src/UnityFx.Async/Implementation/AsyncContinuation.cs index 803dd86..96f234a 100644 --- a/src/UnityFx.Async/Implementation/AsyncContinuation.cs +++ b/src/UnityFx.Async/Implementation/AsyncContinuation.cs @@ -16,7 +16,6 @@ internal class AsyncContinuation private static SendOrPostCallback _postCallback; private readonly AsyncResult _op; - private readonly AsyncContinuationOptions _options; private readonly SynchronizationContext _syncContext; private readonly object _continuation; @@ -24,35 +23,31 @@ internal class AsyncContinuation #region interface - internal AsyncContinuation(AsyncResult op, AsyncContinuationOptions options, SynchronizationContext syncContext, object continuation) + internal AsyncContinuation(AsyncResult op, SynchronizationContext syncContext, object continuation) { _op = op; - _options = options; _syncContext = syncContext; _continuation = continuation; } internal void Invoke() { - if (CanInvoke(_op, _options)) + if (_syncContext == null || _syncContext == SynchronizationContext.Current) { - if (_syncContext == null || _syncContext == SynchronizationContext.Current) - { - InvokeInternal(_op, _continuation); - } - else + InvokeInternal(_op, _continuation); + } + else + { + if (_postCallback == null) { - if (_postCallback == null) + _postCallback = args => { - _postCallback = args => - { - var c = args as AsyncContinuation; - InvokeInternal(c._op, c._continuation); - }; - } - - _syncContext.Post(_postCallback, this); + var c = args as AsyncContinuation; + InvokeInternal(c._op, c._continuation); + }; } + + _syncContext.Post(_postCallback, this); } } From 53dba9d232072053c73c78d374895c086eeb7b73 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 31 Mar 2018 09:39:47 +0300 Subject: [PATCH 019/128] Added IAsyncContinuation interface --- src/UnityFx.Async/Api/Core/AsyncResult.cs | 26 +++++++ .../Api/Interfaces/IAsyncContinuation.cs | 67 ++++++++++++++++++ .../Api/Interfaces/IAsyncOperationEvents.cs | 68 ++++++------------- .../{ => Continuations}/AsyncContinuation.cs | 54 ++++++++------- .../Continuations/DelegateContinuation.cs | 48 +++++++++++++ 5 files changed, 190 insertions(+), 73 deletions(-) create mode 100644 src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs rename src/UnityFx.Async/Implementation/{ => Continuations}/AsyncContinuation.cs (90%) create mode 100644 src/UnityFx.Async/Implementation/Continuations/DelegateContinuation.cs diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index d4d3e2e..4e5cac1 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -1421,6 +1421,32 @@ public bool RemoveCompletionCallback(AsyncOperationCallback action) return false; } + /// + public bool TryAddContinuation(IAsyncContinuation continuation) + { + ThrowIfDisposed(); + + if (continuation == null) + { + throw new ArgumentNullException(nameof(continuation)); + } + + return TryAddContinuation(continuation, null); + } + + /// + public bool RemoveContinuation(IAsyncContinuation continuation) + { + ThrowIfDisposed(); + + if (continuation != null) + { + return TryRemoveContinuation(continuation); + } + + return false; + } + #endregion #region IAsyncResult diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs new file mode 100644 index 0000000..528d09c --- /dev/null +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs @@ -0,0 +1,67 @@ +// 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 +{ + /// + /// Specifies the behavior of an asynchronous opration continuation. + /// + [Flags] + public enum AsyncContinuationOptions + { + /// + /// When no continuation options are specified, specifies that default behavior should be used when executing a continuation. + /// I.e. continuation is scheduled independently of the operation completion status. + /// + None = 0, + + /// + /// Specifies that the continuation should not be scheduled if its antecedent ran to completion. + /// + NotOnRanToCompletion = 1, + + /// + /// Specifies that the continuation should not be scheduled if its antecedent threw an unhandled exception. + /// + NotOnFaulted = 2, + + /// + /// Specifies that the continuation should not be scheduled if its antecedent was canceled. + /// + NotOnCanceled = 4, + + /// + /// Specifies that the continuation should be scheduled only if its antecedent ran to completion. + /// + OnlyOnRanToCompletion = NotOnFaulted | NotOnCanceled, + + /// + /// Specifies that the continuation should be scheduled only if its antecedent threw an unhandled exception. + /// + OnlyOnFaulted = NotOnRanToCompletion | NotOnCanceled, + + /// + /// Specifies that the continuation should be scheduled only if its antecedent was canceled. + /// + OnlyOnCanceled = NotOnRanToCompletion | NotOnFaulted, + + /// + /// Specifies whether a should be captured when a continuation is registered. + /// + CaptureSynchronizationContext = 8 + } + + /// + /// A generic continuation. + /// + public interface IAsyncContinuation + { + /// + /// Starts the continuation. + /// + void Invoke(); + } +} diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs index b2b84ef..67827be 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs @@ -14,54 +14,6 @@ namespace UnityFx.Async /// public delegate void AsyncOperationCallback(IAsyncOperation op); - /// - /// Specifies the behavior of an asynchronous opration continuation. - /// - [Flags] - public enum AsyncContinuationOptions - { - /// - /// When no continuation options are specified, specifies that default behavior should be used when executing a continuation. - /// I.e. continuation is scheduled independently of the operation completion status. - /// - None = 0, - - /// - /// Specifies that the continuation should not be scheduled if its antecedent ran to completion. - /// - NotOnRanToCompletion = 1, - - /// - /// Specifies that the continuation should not be scheduled if its antecedent threw an unhandled exception. - /// - NotOnFaulted = 2, - - /// - /// Specifies that the continuation should not be scheduled if its antecedent was canceled. - /// - NotOnCanceled = 4, - - /// - /// Specifies that the continuation should be scheduled only if its antecedent ran to completion. - /// - OnlyOnRanToCompletion = NotOnFaulted | NotOnCanceled, - - /// - /// Specifies that the continuation should be scheduled only if its antecedent threw an unhandled exception. - /// - OnlyOnFaulted = NotOnRanToCompletion | NotOnCanceled, - - /// - /// Specifies that the continuation should be scheduled only if its antecedent was canceled. - /// - OnlyOnCanceled = NotOnRanToCompletion | NotOnFaulted, - - /// - /// Specifies whether a should be captured when a continuation is registered. - /// - CaptureSynchronizationContext = 8 - } - /// /// A controller for completion callbacks. /// @@ -103,5 +55,25 @@ public interface IAsyncOperationEvents /// Thrown is the operation has been disposed. /// bool RemoveCompletionCallback(AsyncOperationCallback action); + + /// + /// Attempts to add a completion callback to be executed after the operation has finished. If the operation is already completed + /// the method does nothing and just returns . + /// + /// The cotinuation to be executed when the operation has completed. + /// Returns if the callback was added; otherwise (the operation is completed). + /// Thrown if the is . + /// Thrown is the operation has been disposed. + /// + bool TryAddContinuation(IAsyncContinuation continuation); + + /// + /// Removes an existing completion callback. + /// + /// The continuation to remove. Can be . + /// Returns if the was removed; otherwise. + /// Thrown is the operation has been disposed. + /// + bool RemoveContinuation(IAsyncContinuation continuation); } } diff --git a/src/UnityFx.Async/Implementation/AsyncContinuation.cs b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs similarity index 90% rename from src/UnityFx.Async/Implementation/AsyncContinuation.cs rename to src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs index 96f234a..3c26eac 100644 --- a/src/UnityFx.Async/Implementation/AsyncContinuation.cs +++ b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs @@ -9,13 +9,13 @@ namespace UnityFx.Async { - internal class AsyncContinuation + internal class AsyncContinuation : IAsyncContinuation { #region data private static SendOrPostCallback _postCallback; - private readonly AsyncResult _op; + private readonly IAsyncOperation _op; private readonly SynchronizationContext _syncContext; private readonly object _continuation; @@ -23,34 +23,13 @@ internal class AsyncContinuation #region interface - internal AsyncContinuation(AsyncResult op, SynchronizationContext syncContext, object continuation) + internal AsyncContinuation(IAsyncOperation op, SynchronizationContext syncContext, object continuation) { _op = op; _syncContext = syncContext; _continuation = continuation; } - internal void Invoke() - { - if (_syncContext == null || _syncContext == SynchronizationContext.Current) - { - InvokeInternal(_op, _continuation); - } - else - { - if (_postCallback == null) - { - _postCallback = args => - { - var c = args as AsyncContinuation; - InvokeInternal(c._op, c._continuation); - }; - } - - _syncContext.Post(_postCallback, this); - } - } - internal static bool CanInvoke(IAsyncOperation op, AsyncContinuationOptions options) { if (op.IsCompletedSuccessfully) @@ -68,7 +47,7 @@ internal static bool CanInvoke(IAsyncOperation op, AsyncContinuationOptions opti internal static void Invoke(IAsyncOperation op, object continuation) { - if (continuation is AsyncContinuation c) + if (continuation is IAsyncContinuation c) { c.Invoke(); } @@ -120,6 +99,31 @@ internal static void InvokeTaskContinuation(IAsyncOperation op, TaskComple #endregion + #region IAsyncContinuation + + public void Invoke() + { + if (_syncContext == null || _syncContext == SynchronizationContext.Current) + { + InvokeInternal(_op, _continuation); + } + else + { + if (_postCallback == null) + { + _postCallback = args => + { + var c = args as AsyncContinuation; + InvokeInternal(c._op, c._continuation); + }; + } + + _syncContext.Post(_postCallback, this); + } + } + + #endregion + #region Object public override bool Equals(object obj) diff --git a/src/UnityFx.Async/Implementation/Continuations/DelegateContinuation.cs b/src/UnityFx.Async/Implementation/Continuations/DelegateContinuation.cs new file mode 100644 index 0000000..ce81eea --- /dev/null +++ b/src/UnityFx.Async/Implementation/Continuations/DelegateContinuation.cs @@ -0,0 +1,48 @@ +// 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 +{ + internal class DelegateContinuation : AsyncContinuation + { + #region data + + private readonly AsyncResult _op; + private readonly object _continuation; + + #endregion + + #region interface + + internal DelegateContinuation(AsyncResult op, SynchronizationContext syncContext, object continuation) + : base(op, syncContext, continuation) + { + _op = op; + _continuation = continuation; + } + + #endregion + + #region Object + + public override bool Equals(object obj) + { + if (ReferenceEquals(obj, _continuation)) + { + return true; + } + + return base.Equals(obj); + } + + public override int GetHashCode() + { + return _continuation.GetHashCode(); + } + + #endregion + } +} From 774efb9aa73f432585b7ceaf776c2456fcb4df69 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 31 Mar 2018 16:38:20 +0300 Subject: [PATCH 020/128] ContinuWith optimizations --- src/UnityFx.Async/Api/Core/AsyncResult.cs | 2 +- .../AsyncExtensions.Continuations.cs | 411 ++---------------- .../Api/Interfaces/IAsyncContinuation.cs | 5 +- .../Continuations/AsyncContinuation.cs | 91 ++-- .../Continuations/ContinuationResult{T}.cs | 90 ++++ .../Continuations/DelegateContinuation.cs | 15 +- .../DelegateContinuationResult{T,U}.cs | 62 +++ .../DelegateContinuationResult{T}.cs | 62 +++ 8 files changed, 310 insertions(+), 428 deletions(-) create mode 100644 src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs create mode 100644 src/UnityFx.Async/Implementation/Continuations/DelegateContinuationResult{T,U}.cs create mode 100644 src/UnityFx.Async/Implementation/Continuations/DelegateContinuationResult{T}.cs diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 4e5cac1..738c12a 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -1615,7 +1615,7 @@ private bool TryAddContinuation(object continuation, SynchronizationContext sync { if (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) { - continuation = new AsyncContinuation(this, syncContext, continuation); + continuation = new DelegateContinuation(syncContext, continuation); } return TryAddContinuation(continuation); diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs index 3306cee..d656f1f 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs @@ -8,7 +8,24 @@ namespace UnityFx.Async { partial class AsyncExtensions { - #region AddCompletionCallback + #region AddCompletionCallback/AddContinuation + + /// + /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked synchronously. + /// + /// The target operation. + /// The callback to be executed when the operation has completed. + /// Thrown if the is . + /// Thrown is the operation has been disposed. + /// + /// + public static void AddContinuation(this IAsyncOperation op, IAsyncContinuation continuation) + { + if (!op.TryAddContinuation(continuation)) + { + continuation.Invoke(op); + } + } /// /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked synchronously. @@ -68,27 +85,7 @@ public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperation /// An operation that is executed after completes. public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action action) { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - action(asyncOp); - result.TrySetCompleted(); - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }); - - return result; + return ContinueWith(op, action, AsyncContinuationOptions.CaptureSynchronizationContext); } /// @@ -106,31 +103,8 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action - { - try - { - if (AsyncContinuation.CanInvoke(asyncOp, options)) - { - action(asyncOp); - result.TrySetCompleted(); - } - else - { - result.TrySetCanceled(); - } - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }, - syncContext); - + var result = new DelegateContinuationResult(options, action, null); + op.AddContinuation(result); return result; } @@ -144,27 +118,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, ActionAn operation that is executed after completes. public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action action, object userState) { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - action(asyncOp, userState); - result.TrySetCompleted(); - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }); - - return result; + return ContinueWith(op, action, userState, AsyncContinuationOptions.CaptureSynchronizationContext); } /// @@ -183,31 +137,8 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action - { - try - { - if (AsyncContinuation.CanInvoke(asyncOp, options)) - { - action(asyncOp, userState); - result.TrySetCompleted(); - } - else - { - result.TrySetCanceled(); - } - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }, - syncContext); - + var result = new DelegateContinuationResult(options, action, userState); + op.AddContinuation(result); return result; } @@ -220,27 +151,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, ActionAn operation that is executed after completes. public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func action) { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - var resultValue = action(asyncOp); - result.TrySetResult(resultValue); - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }); - - return result; + return ContinueWith(op, action, AsyncContinuationOptions.CaptureSynchronizationContext); } /// @@ -258,31 +169,8 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func(AsyncOperationStatus.Running); - var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; - - op.AddCompletionCallback( - asyncOp => - { - try - { - if (AsyncContinuation.CanInvoke(asyncOp, options)) - { - var resultValue = action(asyncOp); - result.TrySetResult(resultValue); - } - else - { - result.TrySetCanceled(); - } - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }, - syncContext); - + var result = new DelegateContinuationResult(options, action, null); + op.AddContinuation(result); return result; } @@ -296,27 +184,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, FuncAn operation that is executed after completes. public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func action, object userState) { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - var resultValue = action(asyncOp, userState); - result.TrySetResult(resultValue); - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }); - - return result; + return ContinueWith(op, action, userState, AsyncContinuationOptions.CaptureSynchronizationContext); } /// @@ -335,31 +203,8 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func(AsyncOperationStatus.Running); - var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; - - op.AddCompletionCallback( - asyncOp => - { - try - { - if (AsyncContinuation.CanInvoke(asyncOp, options)) - { - var resultValue = action(asyncOp, userState); - result.TrySetResult(resultValue); - } - else - { - result.TrySetCanceled(); - } - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }, - syncContext); - + var result = new DelegateContinuationResult(options, action, userState); + op.AddContinuation(result); return result; } @@ -372,27 +217,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, FuncAn operation that is executed after completes. public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action> action) { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - action(asyncOp as IAsyncOperation); - result.TrySetCompleted(); - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }); - - return result; + return ContinueWith(op, action, AsyncContinuationOptions.CaptureSynchronizationContext); } /// @@ -410,31 +235,8 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action throw new ArgumentNullException(nameof(action)); } - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; - - op.AddCompletionCallback( - asyncOp => - { - try - { - if (AsyncContinuation.CanInvoke(asyncOp, options)) - { - action(asyncOp as IAsyncOperation); - result.TrySetCompleted(); - } - else - { - result.TrySetCanceled(); - } - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }, - syncContext); - + var result = new DelegateContinuationResult(options, action, null); + op.AddContinuation(result); return result; } @@ -448,27 +250,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action /// An operation that is executed after completes. public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action, object> action, object userState) { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - action(asyncOp as IAsyncOperation, userState); - result.TrySetCompleted(); - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }); - - return result; + return ContinueWith(op, action, userState, AsyncContinuationOptions.CaptureSynchronizationContext); } /// @@ -487,31 +269,8 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action throw new ArgumentNullException(nameof(action)); } - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; - - op.AddCompletionCallback( - asyncOp => - { - try - { - if (AsyncContinuation.CanInvoke(asyncOp, options)) - { - action(asyncOp as IAsyncOperation, userState); - result.TrySetCompleted(); - } - else - { - result.TrySetCanceled(); - } - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }, - syncContext); - + var result = new DelegateContinuationResult(options, action, userState); + op.AddContinuation(result); return result; } @@ -524,27 +283,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action /// An operation that is executed after completes. public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func, U> action) { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - var resultValue = action(asyncOp as IAsyncOperation); - result.TrySetResult(resultValue); - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }); - - return result; + return ContinueWith(op, action, AsyncContinuationOptions.CaptureSynchronizationContext); } /// @@ -562,31 +301,8 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, throw new ArgumentNullException(nameof(action)); } - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; - - op.AddCompletionCallback( - asyncOp => - { - try - { - if (AsyncContinuation.CanInvoke(asyncOp, options)) - { - var resultValue = action(asyncOp as IAsyncOperation); - result.TrySetResult(resultValue); - } - else - { - result.TrySetCanceled(); - } - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }, - syncContext); - + var result = new DelegateContinuationResult(options, action, null); + op.AddContinuation(result); return result; } @@ -600,27 +316,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, /// An operation that is executed after completes. public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func, object, U> action, object userState) { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - var resultValue = action(asyncOp as IAsyncOperation, userState); - result.TrySetResult(resultValue); - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }); - - return result; + return ContinueWith(op, action, userState, AsyncContinuationOptions.CaptureSynchronizationContext); } /// @@ -639,31 +335,8 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, throw new ArgumentNullException(nameof(action)); } - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - var syncContext = (options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0 ? SynchronizationContext.Current : null; - - op.AddCompletionCallback( - asyncOp => - { - try - { - if (AsyncContinuation.CanInvoke(asyncOp, options)) - { - var resultValue = action(asyncOp as IAsyncOperation, userState); - result.TrySetResult(resultValue); - } - else - { - result.TrySetCanceled(); - } - } - catch (Exception e) - { - result.TrySetException(e, false); - } - }, - syncContext); - + var result = new DelegateContinuationResult(options, action, userState); + op.AddContinuation(result); return result; } diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs index 528d09c..2c6fbb9 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs @@ -9,6 +9,7 @@ namespace UnityFx.Async /// /// Specifies the behavior of an asynchronous opration continuation. /// + /// [Flags] public enum AsyncContinuationOptions { @@ -57,11 +58,13 @@ public enum AsyncContinuationOptions /// /// A generic continuation. /// + /// public interface IAsyncContinuation { /// /// Starts the continuation. /// - void Invoke(); + /// The completed antecedent operation. + void Invoke(IAsyncOperation op); } } diff --git a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs index 3c26eac..7ceb475 100644 --- a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs +++ b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs @@ -9,25 +9,22 @@ namespace UnityFx.Async { - internal class AsyncContinuation : IAsyncContinuation + internal abstract class AsyncContinuation : IAsyncContinuation { #region data private static SendOrPostCallback _postCallback; - private readonly IAsyncOperation _op; private readonly SynchronizationContext _syncContext; - private readonly object _continuation; + private IAsyncOperation _op; #endregion #region interface - internal AsyncContinuation(IAsyncOperation op, SynchronizationContext syncContext, object continuation) + internal AsyncContinuation(SynchronizationContext syncContext) { - _op = op; _syncContext = syncContext; - _continuation = continuation; } internal static bool CanInvoke(IAsyncOperation op, AsyncContinuationOptions options) @@ -49,11 +46,37 @@ internal static void Invoke(IAsyncOperation op, object continuation) { if (continuation is IAsyncContinuation c) { - c.Invoke(); + c.Invoke(op); } else { - InvokeInternal(op, continuation); + InvokeDelegate(op, continuation); + } + } + + internal static void InvokeDelegate(IAsyncOperation op, object continuation) + { + switch (continuation) + { + case AsyncOperationCallback aoc: + aoc.Invoke(op); + break; + + ////case Action aop: + //// aop.Invoke(op); + //// break; + + case Action a: + a.Invoke(); + break; + + case AsyncCallback ac: + ac.Invoke(op); + break; + + case EventHandler eh: + eh.Invoke(op, EventArgs.Empty); + break; } } @@ -97,24 +120,28 @@ internal static void InvokeTaskContinuation(IAsyncOperation op, TaskComple #endif + protected abstract void OnInvoke(IAsyncOperation op); + #endregion #region IAsyncContinuation - public void Invoke() + public void Invoke(IAsyncOperation op) { if (_syncContext == null || _syncContext == SynchronizationContext.Current) { - InvokeInternal(_op, _continuation); + OnInvoke(op); } else { + _op = op; + if (_postCallback == null) { _postCallback = args => { var c = args as AsyncContinuation; - InvokeInternal(c._op, c._continuation); + c.OnInvoke(c._op); }; } @@ -124,49 +151,7 @@ public void Invoke() #endregion - #region Object - - public override bool Equals(object obj) - { - if (ReferenceEquals(obj, _continuation)) - { - return true; - } - - return base.Equals(obj); - } - - public override int GetHashCode() - { - return _continuation.GetHashCode(); - } - - #endregion - #region implementation - - private static void InvokeInternal(IAsyncOperation op, object continuation) - { - switch (continuation) - { - case AsyncOperationCallback aoc: - aoc.Invoke(op); - break; - - case Action a: - a.Invoke(); - break; - - case AsyncCallback ac: - ac.Invoke(op); - break; - - case EventHandler eh: - eh.Invoke(op, EventArgs.Empty); - break; - } - } - #endregion } } diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs new file mode 100644 index 0000000..9b9d025 --- /dev/null +++ b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs @@ -0,0 +1,90 @@ +// 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 +{ + internal abstract class ContinuationResult : AsyncResult, IAsyncContinuation + { + #region data + + private static SendOrPostCallback _postCallback; + + private readonly SynchronizationContext _syncContext; + private readonly AsyncContinuationOptions _options; + private IAsyncOperation _op; + + #endregion + + #region interface + + protected ContinuationResult(AsyncContinuationOptions options) + : base(AsyncOperationStatus.Running) + { + if ((options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0) + { + _syncContext = SynchronizationContext.Current; + } + + _options = options; + } + + protected abstract T OnInvoke(IAsyncOperation op); + + #endregion + + #region IAsyncContinuation + + public void Invoke(IAsyncOperation op) + { + if (AsyncContinuation.CanInvoke(op, _options)) + { + if (_syncContext == null || _syncContext == SynchronizationContext.Current) + { + try + { + TrySetResult(OnInvoke(op), op.CompletedSynchronously); + } + catch (Exception e) + { + TrySetException(e, op.CompletedSynchronously); + } + } + else + { + _op = op; + + if (_postCallback == null) + { + _postCallback = args => + { + var c = args as ContinuationResult; + + try + { + c.TrySetResult(c.OnInvoke(c._op), false); + } + catch (Exception e) + { + c.TrySetException(e, false); + } + }; + } + + _syncContext.Post(_postCallback, this); + } + } + else + { + TrySetCanceled(op.CompletedSynchronously); + } + } + + #endregion + + #region implementation + #endregion + } +} diff --git a/src/UnityFx.Async/Implementation/Continuations/DelegateContinuation.cs b/src/UnityFx.Async/Implementation/Continuations/DelegateContinuation.cs index ce81eea..ee56447 100644 --- a/src/UnityFx.Async/Implementation/Continuations/DelegateContinuation.cs +++ b/src/UnityFx.Async/Implementation/Continuations/DelegateContinuation.cs @@ -10,22 +10,29 @@ internal class DelegateContinuation : AsyncContinuation { #region data - private readonly AsyncResult _op; private readonly object _continuation; #endregion #region interface - internal DelegateContinuation(AsyncResult op, SynchronizationContext syncContext, object continuation) - : base(op, syncContext, continuation) + internal DelegateContinuation(SynchronizationContext syncContext, object continuation) + : base(syncContext) { - _op = op; _continuation = continuation; } #endregion + #region AsyncContinuation + + protected override void OnInvoke(IAsyncOperation op) + { + InvokeDelegate(op, _continuation); + } + + #endregion + #region Object public override bool Equals(object obj) diff --git a/src/UnityFx.Async/Implementation/Continuations/DelegateContinuationResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/DelegateContinuationResult{T,U}.cs new file mode 100644 index 0000000..7893d23 --- /dev/null +++ b/src/UnityFx.Async/Implementation/Continuations/DelegateContinuationResult{T,U}.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; + +namespace UnityFx.Async +{ + internal class DelegateContinuationResult : ContinuationResult + { + #region data + + private readonly object _continuation; + private readonly object _userState; + + #endregion + + #region interface + + internal DelegateContinuationResult(AsyncContinuationOptions options, object continuation, object userState) + : base(options) + { + _continuation = continuation; + _userState = userState; + } + + #endregion + + #region AsyncContinuation + + protected override U OnInvoke(IAsyncOperation op) + { + var result = default(U); + + switch (_continuation) + { + case Action> a: + a.Invoke(op as IAsyncOperation); + break; + + case Func, U> f: + result = f.Invoke(op as IAsyncOperation); + break; + + case Action, object> ao: + ao.Invoke(op as IAsyncOperation, _userState); + break; + + case Func, object, U> fo: + result = fo.Invoke(op as IAsyncOperation, _userState); + break; + + default: + // Should not get here. + throw new InvalidOperationException(); + } + + return result; + } + + #endregion + } +} diff --git a/src/UnityFx.Async/Implementation/Continuations/DelegateContinuationResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/DelegateContinuationResult{T}.cs new file mode 100644 index 0000000..6a67742 --- /dev/null +++ b/src/UnityFx.Async/Implementation/Continuations/DelegateContinuationResult{T}.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; + +namespace UnityFx.Async +{ + internal class DelegateContinuationResult : ContinuationResult + { + #region data + + private readonly object _continuation; + private readonly object _userState; + + #endregion + + #region interface + + internal DelegateContinuationResult(AsyncContinuationOptions options, object continuation, object userState) + : base(options) + { + _continuation = continuation; + _userState = userState; + } + + #endregion + + #region AsyncContinuation + + protected override T OnInvoke(IAsyncOperation op) + { + var result = default(T); + + switch (_continuation) + { + case Action a: + a.Invoke(op); + break; + + case Func f: + result = f.Invoke(op); + break; + + case Action ao: + ao.Invoke(op, _userState); + break; + + case Func fo: + result = fo.Invoke(op, _userState); + break; + + default: + // Should not get here. + throw new InvalidOperationException(); + } + + return result; + } + + #endregion + } +} From 31061436e5a29c36618671a28e4888d61f2dd458 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 31 Mar 2018 19:36:18 +0300 Subject: [PATCH 021/128] Removed DelegateContinuation --- src/UnityFx.Async/Api/Core/AsyncResult.cs | 2 +- .../Continuations/AsyncContinuation.cs | 80 +++++++++++-------- .../Continuations/DelegateContinuation.cs | 55 ------------- 3 files changed, 49 insertions(+), 88 deletions(-) delete mode 100644 src/UnityFx.Async/Implementation/Continuations/DelegateContinuation.cs diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 738c12a..6673b81 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -1615,7 +1615,7 @@ private bool TryAddContinuation(object continuation, SynchronizationContext sync { if (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) { - continuation = new DelegateContinuation(syncContext, continuation); + continuation = new AsyncContinuation(syncContext, continuation); } return TryAddContinuation(continuation); diff --git a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs index 7ceb475..ebea3fc 100644 --- a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs +++ b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs @@ -9,22 +9,24 @@ namespace UnityFx.Async { - internal abstract class AsyncContinuation : IAsyncContinuation + internal class AsyncContinuation : IAsyncContinuation { #region data private static SendOrPostCallback _postCallback; private readonly SynchronizationContext _syncContext; + private readonly object _continuation; private IAsyncOperation _op; #endregion #region interface - internal AsyncContinuation(SynchronizationContext syncContext) + internal AsyncContinuation(SynchronizationContext syncContext, object continuation) { _syncContext = syncContext; + _continuation = continuation; } internal static bool CanInvoke(IAsyncOperation op, AsyncContinuationOptions options) @@ -54,32 +56,6 @@ internal static void Invoke(IAsyncOperation op, object continuation) } } - internal static void InvokeDelegate(IAsyncOperation op, object continuation) - { - switch (continuation) - { - case AsyncOperationCallback aoc: - aoc.Invoke(op); - break; - - ////case Action aop: - //// aop.Invoke(op); - //// break; - - case Action a: - a.Invoke(); - break; - - case AsyncCallback ac: - ac.Invoke(op); - break; - - case EventHandler eh: - eh.Invoke(op, EventArgs.Empty); - break; - } - } - #if UNITYFX_SUPPORT_TAP internal static void InvokeTaskContinuation(IAsyncOperation op, TaskCompletionSource tcs) @@ -120,8 +96,6 @@ internal static void InvokeTaskContinuation(IAsyncOperation op, TaskComple #endif - protected abstract void OnInvoke(IAsyncOperation op); - #endregion #region IAsyncContinuation @@ -130,7 +104,7 @@ public void Invoke(IAsyncOperation op) { if (_syncContext == null || _syncContext == SynchronizationContext.Current) { - OnInvoke(op); + InvokeDelegate(op, _continuation); } else { @@ -141,7 +115,7 @@ public void Invoke(IAsyncOperation op) _postCallback = args => { var c = args as AsyncContinuation; - c.OnInvoke(c._op); + InvokeDelegate(c._op, c._continuation); }; } @@ -151,7 +125,49 @@ public void Invoke(IAsyncOperation op) #endregion + #region Object + + public override bool Equals(object obj) + { + if (ReferenceEquals(obj, _continuation)) + { + return true; + } + + return base.Equals(obj); + } + + public override int GetHashCode() + { + return _continuation.GetHashCode(); + } + + #endregion + #region implementation + + private static void InvokeDelegate(IAsyncOperation op, object continuation) + { + switch (continuation) + { + case AsyncOperationCallback aoc: + aoc.Invoke(op); + break; + + case Action a: + a.Invoke(); + break; + + case AsyncCallback ac: + ac.Invoke(op); + break; + + case EventHandler eh: + eh.Invoke(op, EventArgs.Empty); + break; + } + } + #endregion } } diff --git a/src/UnityFx.Async/Implementation/Continuations/DelegateContinuation.cs b/src/UnityFx.Async/Implementation/Continuations/DelegateContinuation.cs deleted file mode 100644 index ee56447..0000000 --- a/src/UnityFx.Async/Implementation/Continuations/DelegateContinuation.cs +++ /dev/null @@ -1,55 +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 -{ - internal class DelegateContinuation : AsyncContinuation - { - #region data - - private readonly object _continuation; - - #endregion - - #region interface - - internal DelegateContinuation(SynchronizationContext syncContext, object continuation) - : base(syncContext) - { - _continuation = continuation; - } - - #endregion - - #region AsyncContinuation - - protected override void OnInvoke(IAsyncOperation op) - { - InvokeDelegate(op, _continuation); - } - - #endregion - - #region Object - - public override bool Equals(object obj) - { - if (ReferenceEquals(obj, _continuation)) - { - return true; - } - - return base.Equals(obj); - } - - public override int GetHashCode() - { - return _continuation.GetHashCode(); - } - - #endregion - } -} From 9b0e05e7fe41858d179ef5716bae11bcbda96f98 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 31 Mar 2018 19:41:55 +0300 Subject: [PATCH 022/128] A few renames --- .../Extensions/AsyncExtensions.Continuations.cs | 16 ++++++++-------- ...t{T,U}.cs => AsyncContinuationResult{T,U}.cs} | 4 ++-- ...esult{T}.cs => AsyncContinuationResult{T}.cs} | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) rename src/UnityFx.Async/Implementation/Continuations/{DelegateContinuationResult{T,U}.cs => AsyncContinuationResult{T,U}.cs} (86%) rename src/UnityFx.Async/Implementation/Continuations/{DelegateContinuationResult{T}.cs => AsyncContinuationResult{T}.cs} (85%) diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs index d656f1f..aa0b544 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs @@ -103,7 +103,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action(options, action, null); + var result = new AsyncContinuationResult(options, action, null); op.AddContinuation(result); return result; } @@ -137,7 +137,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action(options, action, userState); + var result = new AsyncContinuationResult(options, action, userState); op.AddContinuation(result); return result; } @@ -169,7 +169,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func(options, action, null); + var result = new AsyncContinuationResult(options, action, null); op.AddContinuation(result); return result; } @@ -203,7 +203,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func(options, action, userState); + var result = new AsyncContinuationResult(options, action, userState); op.AddContinuation(result); return result; } @@ -235,7 +235,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action throw new ArgumentNullException(nameof(action)); } - var result = new DelegateContinuationResult(options, action, null); + var result = new AsyncContinuationResult(options, action, null); op.AddContinuation(result); return result; } @@ -269,7 +269,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action throw new ArgumentNullException(nameof(action)); } - var result = new DelegateContinuationResult(options, action, userState); + var result = new AsyncContinuationResult(options, action, userState); op.AddContinuation(result); return result; } @@ -301,7 +301,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, throw new ArgumentNullException(nameof(action)); } - var result = new DelegateContinuationResult(options, action, null); + var result = new AsyncContinuationResult(options, action, null); op.AddContinuation(result); return result; } @@ -335,7 +335,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, throw new ArgumentNullException(nameof(action)); } - var result = new DelegateContinuationResult(options, action, userState); + var result = new AsyncContinuationResult(options, action, userState); op.AddContinuation(result); return result; } diff --git a/src/UnityFx.Async/Implementation/Continuations/DelegateContinuationResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuationResult{T,U}.cs similarity index 86% rename from src/UnityFx.Async/Implementation/Continuations/DelegateContinuationResult{T,U}.cs rename to src/UnityFx.Async/Implementation/Continuations/AsyncContinuationResult{T,U}.cs index 7893d23..72566eb 100644 --- a/src/UnityFx.Async/Implementation/Continuations/DelegateContinuationResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuationResult{T,U}.cs @@ -5,7 +5,7 @@ namespace UnityFx.Async { - internal class DelegateContinuationResult : ContinuationResult + internal class AsyncContinuationResult : ContinuationResult { #region data @@ -16,7 +16,7 @@ internal class DelegateContinuationResult : ContinuationResult #region interface - internal DelegateContinuationResult(AsyncContinuationOptions options, object continuation, object userState) + internal AsyncContinuationResult(AsyncContinuationOptions options, object continuation, object userState) : base(options) { _continuation = continuation; diff --git a/src/UnityFx.Async/Implementation/Continuations/DelegateContinuationResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuationResult{T}.cs similarity index 85% rename from src/UnityFx.Async/Implementation/Continuations/DelegateContinuationResult{T}.cs rename to src/UnityFx.Async/Implementation/Continuations/AsyncContinuationResult{T}.cs index 6a67742..4218af0 100644 --- a/src/UnityFx.Async/Implementation/Continuations/DelegateContinuationResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuationResult{T}.cs @@ -5,7 +5,7 @@ namespace UnityFx.Async { - internal class DelegateContinuationResult : ContinuationResult + internal class AsyncContinuationResult : ContinuationResult { #region data @@ -16,7 +16,7 @@ internal class DelegateContinuationResult : ContinuationResult #region interface - internal DelegateContinuationResult(AsyncContinuationOptions options, object continuation, object userState) + internal AsyncContinuationResult(AsyncContinuationOptions options, object continuation, object userState) : base(options) { _continuation = continuation; From 2789a8623c0ac132704ae15978e7bb818637cd2a Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 31 Mar 2018 19:54:53 +0300 Subject: [PATCH 023/128] Minow ContinueWith fixes --- .../Api/Extensions/AsyncExtensions.Continuations.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs index aa0b544..15ea5d6 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs @@ -103,7 +103,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action(options, action, null); + var result = new AsyncContinuationResult(options, action, null); op.AddContinuation(result); return result; } @@ -137,7 +137,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action(options, action, userState); + var result = new AsyncContinuationResult(options, action, userState); op.AddContinuation(result); return result; } @@ -235,7 +235,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action throw new ArgumentNullException(nameof(action)); } - var result = new AsyncContinuationResult(options, action, null); + var result = new AsyncContinuationResult(options, action, null); op.AddContinuation(result); return result; } @@ -269,7 +269,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action throw new ArgumentNullException(nameof(action)); } - var result = new AsyncContinuationResult(options, action, userState); + var result = new AsyncContinuationResult(options, action, userState); op.AddContinuation(result); return result; } From 309ed07378b57dc2ef85f77ca9dd3a27db0c8c48 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 31 Mar 2018 20:41:15 +0300 Subject: [PATCH 024/128] Heavy Then optimizations --- .../Api/Core/AsyncCompletionSource.cs | 28 --- src/UnityFx.Async/Api/Core/AsyncResult.cs | 28 +++ .../Extensions/AsyncExtensions.Promises.cs | 204 +++--------------- .../ThenContinuationResult{T}.cs | 129 +++++++++++ 4 files changed, 182 insertions(+), 207 deletions(-) create mode 100644 src/UnityFx.Async/Implementation/Continuations/ThenContinuationResult{T}.cs diff --git a/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs b/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs index 88dda80..ea966f7 100644 --- a/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs +++ b/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs @@ -288,34 +288,6 @@ public void SetCompleted(bool completedSynchronously) /// public new bool TrySetCompleted(bool completedSynchronously) => base.TrySetCompleted(completedSynchronously); - /// - /// Copies state of the specified operation. - /// - internal void CopyCompletionState(IAsyncOperation patternOp, bool completedSynchronously) - { - if (!TryCopyCompletionState(patternOp, completedSynchronously)) - { - throw new InvalidOperationException(); - } - } - - /// - /// Attemts to copy state of the specified operation. - /// - internal bool TryCopyCompletionState(IAsyncOperation patternOp, bool completedSynchronously) - { - if (patternOp.IsCompletedSuccessfully) - { - return base.TrySetCompleted(completedSynchronously); - } - else if (patternOp.IsFaulted || patternOp.IsCanceled) - { - return base.TrySetException(patternOp.Exception, completedSynchronously); - } - - return false; - } - #endregion #region IAsyncCompletionSource diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 6673b81..278cc86 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -1313,6 +1313,34 @@ internal void SetCompleted(int status, bool completedSynchronously) OnCompleted(); } + /// + /// Copies state of the specified operation. + /// + internal void CopyCompletionState(IAsyncOperation patternOp, bool completedSynchronously) + { + if (!TryCopyCompletionState(patternOp, completedSynchronously)) + { + throw new InvalidOperationException(); + } + } + + /// + /// Attemts to copy state of the specified operation. + /// + internal bool TryCopyCompletionState(IAsyncOperation patternOp, bool completedSynchronously) + { + if (patternOp.IsCompletedSuccessfully) + { + return TrySetCompleted(completedSynchronously); + } + else if (patternOp.IsFaulted || patternOp.IsCanceled) + { + return TrySetException(patternOp.Exception, completedSynchronously); + } + + return false; + } + /// /// Adds a completion callback for await implementation. /// diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs index a614e76..ade64d6 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs @@ -12,9 +12,9 @@ partial class AsyncExtensions /// /// Schedules a callback to be executed after the operation has succeeded. /// - /// The target operation. + /// An operation to be continued. /// The callback to be executed when the operation has completed. - /// TODO + /// Returns a continuation operation that completes after both source operation and the callback has completed. /// public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback) { @@ -23,28 +23,8 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba throw new ArgumentNullException(nameof(successCallback)); } - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback(); - result.TrySetCompleted(); - } - else - { - result.TrySetException(asyncOp.Exception); - } - } - catch (Exception e) - { - result.TrySetException(e); - } - }); - + var result = new ThenContinuationResult(successCallback, null); + op.AddContinuation(result); return result; } @@ -53,7 +33,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba /// /// The target operation. /// The callback to be executed when the operation has completed. - /// TODO + /// Returns a continuation operation that completes after both source operation and the callback has completed. /// public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback) { @@ -62,28 +42,8 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action succ throw new ArgumentNullException(nameof(successCallback)); } - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback((asyncOp as IAsyncOperation).Result); - result.TrySetCompleted(); - } - else - { - result.TrySetException(asyncOp.Exception); - } - } - catch (Exception e) - { - result.TrySetException(e); - } - }); - + var result = new ThenContinuationResult(successCallback, null); + op.AddContinuation(result); return result; } @@ -92,6 +52,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action succ /// /// The target operation. /// The callback to be executed when the operation has completed. + /// Returns a continuation operation that completes after both source operation and the operation returned by has completed. /// public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback) { @@ -100,27 +61,8 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func - { - try - { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback().AddCompletionCallback(asyncOp2 => result.CopyCompletionState(asyncOp2, false), null); - } - else - { - result.TrySetException(asyncOp.Exception); - } - } - catch (Exception e) - { - result.TrySetException(e); - } - }); - + var result = new ThenContinuationResult(successCallback, null); + op.AddContinuation(result); return result; } @@ -129,6 +71,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func /// The target operation. /// The callback to be executed when the operation has completed. + /// Returns a continuation operation that completes after both source operation and the operation returned by has completed. /// public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback) { @@ -137,27 +80,8 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func - { - try - { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback((asyncOp as IAsyncOperation).Result).AddCompletionCallback(asyncOp2 => result.CopyCompletionState(asyncOp2, false), null); - } - else - { - result.TrySetException(asyncOp.Exception); - } - } - catch (Exception e) - { - result.TrySetException(e); - } - }); - + var result = new ThenContinuationResult(successCallback, null); + op.AddContinuation(result); return result; } @@ -167,6 +91,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, FuncThe target operation. /// The callback to be executed when the operation has succeeded. /// The callback to be executed when the operation has faulted/was canceled. + /// Returns a continuation operation that completes after both source operation and the callback has completed. /// public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback, Action errorCallback) { @@ -180,29 +105,8 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba throw new ArgumentNullException(nameof(errorCallback)); } - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback(); - result.TrySetCompleted(); - } - else - { - errorCallback(asyncOp.Exception.InnerException); - result.TrySetException(asyncOp.Exception); - } - } - catch (Exception e) - { - result.TrySetException(e); - } - }); - + var result = new ThenContinuationResult(successCallback, errorCallback); + op.AddContinuation(result); return result; } @@ -212,6 +116,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba /// The target operation. /// The callback to be executed when the operation has succeeded. /// The callback to be executed when the operation has faulted/was canceled. + /// Returns a continuation operation that completes after both source operation and the callback has completed. /// public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback, Action errorCallback) { @@ -225,29 +130,8 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action succ throw new ArgumentNullException(nameof(errorCallback)); } - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback((asyncOp as IAsyncOperation).Result); - result.TrySetCompleted(); - } - else - { - errorCallback(asyncOp.Exception.InnerException); - result.TrySetException(asyncOp.Exception); - } - } - catch (Exception e) - { - result.TrySetException(e); - } - }); - + var result = new ThenContinuationResult(successCallback, errorCallback); + op.AddContinuation(result); return result; } @@ -257,6 +141,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action succ /// The target operation. /// The callback to be executed when the operation has succeeded. /// The callback to be executed when the operation has faulted/was canceled. + /// Returns a continuation operation that completes after both source operation and the operation returned by has completed. /// public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback, Action errorCallback) { @@ -270,28 +155,8 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func - { - try - { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback().AddCompletionCallback(asyncOp2 => result.CopyCompletionState(asyncOp2, false), null); - } - else - { - errorCallback(asyncOp.Exception.InnerException); - result.TrySetException(asyncOp.Exception); - } - } - catch (Exception e) - { - result.TrySetException(e); - } - }); - + var result = new ThenContinuationResult(successCallback, errorCallback); + op.AddContinuation(result); return result; } @@ -301,6 +166,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, FuncThe target operation. /// The callback to be executed when the operation has succeeded. /// The callback to be executed when the operation has faulted/was canceled. + /// Returns a continuation operation that completes after both source operation and the operation returned by has completed. /// public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback, Action errorCallback) { @@ -314,28 +180,8 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func - { - try - { - if (asyncOp.IsCompletedSuccessfully) - { - successCallback((asyncOp as IAsyncOperation).Result).AddCompletionCallback(asyncOp2 => result.CopyCompletionState(asyncOp2, false), null); - } - else - { - errorCallback(asyncOp.Exception.InnerException); - result.TrySetException(asyncOp.Exception); - } - } - catch (Exception e) - { - result.TrySetException(e); - } - }); - + var result = new ThenContinuationResult(successCallback, errorCallback); + op.AddContinuation(result); return result; } diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenContinuationResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ThenContinuationResult{T}.cs new file mode 100644 index 0000000..aa2d5ff --- /dev/null +++ b/src/UnityFx.Async/Implementation/Continuations/ThenContinuationResult{T}.cs @@ -0,0 +1,129 @@ +// 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 +{ + internal class ThenContinuationResult : AsyncResult, IAsyncContinuation + { + #region data + + private static SendOrPostCallback _postCallback; + + private readonly SynchronizationContext _syncContext; + private readonly object _successCallback; + private readonly Action _errorCallback; + private IAsyncOperation _op; + + #endregion + + #region interface + + public ThenContinuationResult(object successCallback, Action errorCallback) + : base(AsyncOperationStatus.Running) + { + _syncContext = SynchronizationContext.Current; + _successCallback = successCallback; + _errorCallback = errorCallback; + } + + protected bool InvokeSuccessCallback(IAsyncOperation op) + { + var result = false; + + switch (_successCallback) + { + case Action a: + a.Invoke(); + result = true; + break; + + case Action a1: + a1.Invoke((op as IAsyncOperation).Result); + result = true; + break; + + case Func f: + f.Invoke().AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); + break; + + case Func f1: + f1.Invoke((op as IAsyncOperation).Result).AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); + break; + + default: + // Should not get here. + throw new InvalidOperationException(); + } + + return result; + } + + protected void InvokeErrorCallback(IAsyncOperation op) + { + _errorCallback?.Invoke(op.Exception.InnerException); + } + + #endregion + + #region IAsyncContinuation + + public void Invoke(IAsyncOperation op) + { + if (_syncContext == null || _syncContext == SynchronizationContext.Current) + { + InvokeCallbacks(op, op.CompletedSynchronously); + } + else if (op.IsCompletedSuccessfully || _errorCallback != null) + { + _op = op; + + if (_postCallback == null) + { + _postCallback = args => + { + var c = args as ThenContinuationResult; + c.InvokeCallbacks(c._op, false); + }; + } + + _syncContext.Post(_postCallback, this); + } + else + { + TrySetException(op.Exception, op.CompletedSynchronously); + } + } + + #endregion + + #region implementation + + private void InvokeCallbacks(IAsyncOperation op, bool completedSynchronously) + { + try + { + if (op.IsCompletedSuccessfully) + { + if (InvokeSuccessCallback(op)) + { + TrySetCompleted(completedSynchronously); + } + } + else + { + InvokeErrorCallback(op); + TrySetException(op.Exception, completedSynchronously); + } + } + catch (Exception e) + { + TrySetException(e, completedSynchronously); + } + } + + #endregion + } +} From 6240ac8afd5b15fe77238a183c4904ca868c1cc3 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 31 Mar 2018 20:48:39 +0300 Subject: [PATCH 025/128] Catch optimizations --- .../Extensions/AsyncExtensions.Promises.cs | 43 +++------- .../Continuations/CatchContinuationResult.cs | 85 +++++++++++++++++++ 2 files changed, 97 insertions(+), 31 deletions(-) create mode 100644 src/UnityFx.Async/Implementation/Continuations/CatchContinuationResult.cs diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs index ade64d6..46ab112 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs @@ -31,7 +31,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba /// /// Schedules a callback to be executed after the operation has succeeded. /// - /// The target operation. + /// An operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the callback has completed. /// @@ -50,7 +50,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action succ /// /// Schedules a callback to be executed after the operation has succeeded. /// - /// The target operation. + /// An operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the operation returned by has completed. /// @@ -69,7 +69,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func /// Schedules a callback to be executed after the operation has succeeded. /// - /// The target operation. + /// An operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the operation returned by has completed. /// @@ -88,7 +88,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func /// Schedules a callbacks to be executed after the operation has completed. /// - /// The target operation. + /// An operation to be continued. /// The callback to be executed when the operation has succeeded. /// The callback to be executed when the operation has faulted/was canceled. /// Returns a continuation operation that completes after both source operation and the callback has completed. @@ -113,7 +113,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba /// /// Schedules a callbacks to be executed after the operation has completed. /// - /// The target operation. + /// An operation to be continued. /// The callback to be executed when the operation has succeeded. /// The callback to be executed when the operation has faulted/was canceled. /// Returns a continuation operation that completes after both source operation and the callback has completed. @@ -138,7 +138,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action succ /// /// Adds a completion callback to be executed after the operation has succeeded. /// - /// The target operation. + /// An operation to be continued. /// The callback to be executed when the operation has succeeded. /// The callback to be executed when the operation has faulted/was canceled. /// Returns a continuation operation that completes after both source operation and the operation returned by has completed. @@ -163,7 +163,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func /// Adds a completion callback to be executed after the operation has succeeded. /// - /// The target operation. + /// An operation to be continued. /// The callback to be executed when the operation has succeeded. /// The callback to be executed when the operation has faulted/was canceled. /// Returns a continuation operation that completes after both source operation and the operation returned by has completed. @@ -192,8 +192,9 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func /// Adds a completion callback to be executed after the operation has faulted or was canceled. /// - /// The target operation. + /// An operation to be continued. /// The callback to be executed when the operation has faulted/was canceled. + /// Returns a continuation operation that completes after both source operation and the callback has completed. /// public static IAsyncOperation Catch(this IAsyncOperation op, Action errorCallback) { @@ -202,28 +203,8 @@ public static IAsyncOperation Catch(this IAsyncOperation op, Action e throw new ArgumentNullException(nameof(errorCallback)); } - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - if (asyncOp.IsCompletedSuccessfully) - { - result.TrySetCompleted(); - } - else - { - errorCallback(asyncOp.Exception.InnerException); - result.TrySetCompleted(); - } - } - catch (Exception e) - { - result.TrySetException(e); - } - }); - + var result = new CatchContinuationResult(errorCallback); + op.AddContinuation(result); return result; } @@ -234,7 +215,7 @@ public static IAsyncOperation Catch(this IAsyncOperation op, Action e /// /// Adds a completion callback to be executed after the operation has completed. /// - /// The target operation. + /// An operation to be continued. /// The callback to be executed when the operation has completed. /// public static IAsyncOperation Finally(this IAsyncOperation op, Action action) diff --git a/src/UnityFx.Async/Implementation/Continuations/CatchContinuationResult.cs b/src/UnityFx.Async/Implementation/Continuations/CatchContinuationResult.cs new file mode 100644 index 0000000..a1822ee --- /dev/null +++ b/src/UnityFx.Async/Implementation/Continuations/CatchContinuationResult.cs @@ -0,0 +1,85 @@ +// 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 +{ + internal class CatchContinuationResult : AsyncResult, IAsyncContinuation + { + #region data + + private static SendOrPostCallback _postCallback; + + private readonly SynchronizationContext _syncContext; + private readonly Action _errorCallback; + private IAsyncOperation _op; + + #endregion + + #region interface + + public CatchContinuationResult(Action errorCallback) + : base(AsyncOperationStatus.Running) + { + _syncContext = SynchronizationContext.Current; + _errorCallback = errorCallback; + } + + protected void InvokeErrorCallback(IAsyncOperation op) + { + _errorCallback?.Invoke(op.Exception.InnerException); + } + + #endregion + + #region IAsyncContinuation + + public void Invoke(IAsyncOperation op) + { + if (op.IsCompletedSuccessfully) + { + TrySetCompleted(op.CompletedSynchronously); + } + else if (_syncContext == null || _syncContext == SynchronizationContext.Current) + { + InvokeErrorCallback(op, op.CompletedSynchronously); + } + else + { + _op = op; + + if (_postCallback == null) + { + _postCallback = args => + { + var c = args as CatchContinuationResult; + c.InvokeErrorCallback(c._op, false); + }; + } + + _syncContext.Post(_postCallback, this); + } + } + + #endregion + + #region implementation + + private void InvokeErrorCallback(IAsyncOperation op, bool completedSynchronously) + { + try + { + _errorCallback.Invoke(op.Exception.InnerException); + TrySetCompleted(completedSynchronously); + } + catch (Exception e) + { + TrySetException(e, completedSynchronously); + } + } + + #endregion + } +} From 2b5d0717fae269686760b5ab19f9adfc28c201e4 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 31 Mar 2018 20:54:43 +0300 Subject: [PATCH 026/128] Finally optimization --- .../Extensions/AsyncExtensions.Promises.cs | 18 +---- .../FinallyContinuationResult.cs | 76 +++++++++++++++++++ 2 files changed, 79 insertions(+), 15 deletions(-) create mode 100644 src/UnityFx.Async/Implementation/Continuations/FinallyContinuationResult.cs diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs index 46ab112..463ac0c 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs @@ -217,6 +217,7 @@ public static IAsyncOperation Catch(this IAsyncOperation op, Action e /// /// An operation to be continued. /// The callback to be executed when the operation has completed. + /// Returns a continuation operation that completes after both source operation and the callback has completed. /// public static IAsyncOperation Finally(this IAsyncOperation op, Action action) { @@ -225,21 +226,8 @@ public static IAsyncOperation Finally(this IAsyncOperation op, Action action) throw new ArgumentNullException(nameof(action)); } - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - op.AddCompletionCallback(asyncOp => - { - try - { - action(); - result.TrySetCompleted(); - } - catch (Exception e) - { - result.TrySetException(e); - } - }); - + var result = new FinallyContinuationResult(action); + op.AddContinuation(result); return result; } diff --git a/src/UnityFx.Async/Implementation/Continuations/FinallyContinuationResult.cs b/src/UnityFx.Async/Implementation/Continuations/FinallyContinuationResult.cs new file mode 100644 index 0000000..1909a64 --- /dev/null +++ b/src/UnityFx.Async/Implementation/Continuations/FinallyContinuationResult.cs @@ -0,0 +1,76 @@ +// 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 +{ + internal class FinallyContinuationResult : AsyncResult, IAsyncContinuation + { + #region data + + private static SendOrPostCallback _postCallback; + + private readonly SynchronizationContext _syncContext; + private readonly Action _continuation; + private IAsyncOperation _op; + + #endregion + + #region interface + + public FinallyContinuationResult(Action action) + : base(AsyncOperationStatus.Running) + { + _syncContext = SynchronizationContext.Current; + _continuation = action; + } + + #endregion + + #region IAsyncContinuation + + public void Invoke(IAsyncOperation op) + { + if (_syncContext == null || _syncContext == SynchronizationContext.Current) + { + try + { + _continuation(); + TrySetCompleted(op.CompletedSynchronously); + } + catch (Exception e) + { + TrySetException(e, op.CompletedSynchronously); + } + } + else + { + _op = op; + + if (_postCallback == null) + { + _postCallback = args => + { + var c = args as FinallyContinuationResult; + + try + { + c._continuation(); + c.TrySetCompleted(false); + } + catch (Exception e) + { + TrySetException(e, false); + } + }; + } + + _syncContext.Post(_postCallback, this); + } + } + + #endregion + } +} From 02dcbabdddbd68cdfc02a76bef620c9cd392aa40 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 31 Mar 2018 20:58:59 +0300 Subject: [PATCH 027/128] Minor AsyncResult refactoring --- src/UnityFx.Async/Api/Core/AsyncResult.cs | 19 +++++++- .../Continuations/AsyncContinuation.cs | 47 +++++++------------ 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 278cc86..6a2d03d 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -534,13 +534,13 @@ protected virtual void OnCompleted() { foreach (var item in continuationList) { - AsyncContinuation.Invoke(this, item); + InvokeContinuation(this, item); } } } else { - AsyncContinuation.Invoke(this, continuation); + InvokeContinuation(this, continuation); } } } @@ -1761,6 +1761,21 @@ private bool TryRemoveContinuation(object valueToRemove) return false; } + /// + /// Invokes the specified continuation instance. + /// + private static void InvokeContinuation(IAsyncOperation op, object continuation) + { + if (continuation is IAsyncContinuation c) + { + c.Invoke(op); + } + else + { + AsyncContinuation.InvokeDelegate(op, continuation); + } + } + #endregion } } diff --git a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs index ebea3fc..753e219 100644 --- a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs +++ b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs @@ -44,15 +44,25 @@ internal static bool CanInvoke(IAsyncOperation op, AsyncContinuationOptions opti return (options & AsyncContinuationOptions.NotOnCanceled) == 0; } - internal static void Invoke(IAsyncOperation op, object continuation) + internal static void InvokeDelegate(IAsyncOperation op, object continuation) { - if (continuation is IAsyncContinuation c) - { - c.Invoke(op); - } - else + switch (continuation) { - InvokeDelegate(op, continuation); + case AsyncOperationCallback aoc: + aoc.Invoke(op); + break; + + case Action a: + a.Invoke(); + break; + + case AsyncCallback ac: + ac.Invoke(op); + break; + + case EventHandler eh: + eh.Invoke(op, EventArgs.Empty); + break; } } @@ -145,29 +155,6 @@ public override int GetHashCode() #endregion #region implementation - - private static void InvokeDelegate(IAsyncOperation op, object continuation) - { - switch (continuation) - { - case AsyncOperationCallback aoc: - aoc.Invoke(op); - break; - - case Action a: - a.Invoke(); - break; - - case AsyncCallback ac: - ac.Invoke(op); - break; - - case EventHandler eh: - eh.Invoke(op, EventArgs.Empty); - break; - } - } - #endregion } } From 6746ffc457a8c68465f4fd3ad83e65a2589af466 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 31 Mar 2018 21:11:50 +0300 Subject: [PATCH 028/128] Changed project structure --- src/UnityFx.Async/{ => Implementation}/AssemblyInfo.cs | 0 src/UnityFx.Async/Implementation/{ => Common}/Constants.cs | 0 src/UnityFx.Async/Implementation/{ => Common}/Disposable.cs | 0 src/UnityFx.Async/Implementation/{ => Common}/VoidResult.cs | 0 .../{ => Observable}/AsyncObservableSubscription.cs | 0 .../Implementation/{ => Observable}/AsyncObservable{T}.cs | 0 src/UnityFx.Async/Implementation/{ => Specialized}/DelayResult.cs | 0 .../Implementation/{ => Specialized}/RetryResult{T}.cs | 0 .../Implementation/{ => Specialized}/WhenAllResult{T}.cs | 0 .../Implementation/{ => Specialized}/WhenAnyResult{T}.cs | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename src/UnityFx.Async/{ => Implementation}/AssemblyInfo.cs (100%) rename src/UnityFx.Async/Implementation/{ => Common}/Constants.cs (100%) rename src/UnityFx.Async/Implementation/{ => Common}/Disposable.cs (100%) rename src/UnityFx.Async/Implementation/{ => Common}/VoidResult.cs (100%) rename src/UnityFx.Async/Implementation/{ => Observable}/AsyncObservableSubscription.cs (100%) rename src/UnityFx.Async/Implementation/{ => Observable}/AsyncObservable{T}.cs (100%) rename src/UnityFx.Async/Implementation/{ => Specialized}/DelayResult.cs (100%) rename src/UnityFx.Async/Implementation/{ => Specialized}/RetryResult{T}.cs (100%) rename src/UnityFx.Async/Implementation/{ => Specialized}/WhenAllResult{T}.cs (100%) rename src/UnityFx.Async/Implementation/{ => Specialized}/WhenAnyResult{T}.cs (100%) diff --git a/src/UnityFx.Async/AssemblyInfo.cs b/src/UnityFx.Async/Implementation/AssemblyInfo.cs similarity index 100% rename from src/UnityFx.Async/AssemblyInfo.cs rename to src/UnityFx.Async/Implementation/AssemblyInfo.cs diff --git a/src/UnityFx.Async/Implementation/Constants.cs b/src/UnityFx.Async/Implementation/Common/Constants.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Constants.cs rename to src/UnityFx.Async/Implementation/Common/Constants.cs diff --git a/src/UnityFx.Async/Implementation/Disposable.cs b/src/UnityFx.Async/Implementation/Common/Disposable.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Disposable.cs rename to src/UnityFx.Async/Implementation/Common/Disposable.cs diff --git a/src/UnityFx.Async/Implementation/VoidResult.cs b/src/UnityFx.Async/Implementation/Common/VoidResult.cs similarity index 100% rename from src/UnityFx.Async/Implementation/VoidResult.cs rename to src/UnityFx.Async/Implementation/Common/VoidResult.cs diff --git a/src/UnityFx.Async/Implementation/AsyncObservableSubscription.cs b/src/UnityFx.Async/Implementation/Observable/AsyncObservableSubscription.cs similarity index 100% rename from src/UnityFx.Async/Implementation/AsyncObservableSubscription.cs rename to src/UnityFx.Async/Implementation/Observable/AsyncObservableSubscription.cs diff --git a/src/UnityFx.Async/Implementation/AsyncObservable{T}.cs b/src/UnityFx.Async/Implementation/Observable/AsyncObservable{T}.cs similarity index 100% rename from src/UnityFx.Async/Implementation/AsyncObservable{T}.cs rename to src/UnityFx.Async/Implementation/Observable/AsyncObservable{T}.cs diff --git a/src/UnityFx.Async/Implementation/DelayResult.cs b/src/UnityFx.Async/Implementation/Specialized/DelayResult.cs similarity index 100% rename from src/UnityFx.Async/Implementation/DelayResult.cs rename to src/UnityFx.Async/Implementation/Specialized/DelayResult.cs diff --git a/src/UnityFx.Async/Implementation/RetryResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs similarity index 100% rename from src/UnityFx.Async/Implementation/RetryResult{T}.cs rename to src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs diff --git a/src/UnityFx.Async/Implementation/WhenAllResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs similarity index 100% rename from src/UnityFx.Async/Implementation/WhenAllResult{T}.cs rename to src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs diff --git a/src/UnityFx.Async/Implementation/WhenAnyResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs similarity index 100% rename from src/UnityFx.Async/Implementation/WhenAnyResult{T}.cs rename to src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs From 57ee92c63b03d841cfe2d9a3e26a088fde983bb1 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 31 Mar 2018 23:57:46 +0300 Subject: [PATCH 029/128] Added generic Catch version for catching specific exceptions --- .../Extensions/AsyncExtensions.Promises.cs | 21 ++++++++++++++++++- .../Api/Interfaces/IAsyncCompletionSource.cs | 2 +- .../Api/Interfaces/IAsyncOperation.cs | 7 ++++++- .../Api/Interfaces/IAsyncOperation{T}.cs | 3 ++- ...=> CatchContinuationResult{TException}.cs} | 17 ++++++--------- 5 files changed, 35 insertions(+), 15 deletions(-) rename src/UnityFx.Async/Implementation/Continuations/{CatchContinuationResult.cs => CatchContinuationResult{TException}.cs} (74%) diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs index 463ac0c..229ff72 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs @@ -203,7 +203,26 @@ public static IAsyncOperation Catch(this IAsyncOperation op, Action e throw new ArgumentNullException(nameof(errorCallback)); } - var result = new CatchContinuationResult(errorCallback); + var result = new CatchContinuationResult(errorCallback); + op.AddContinuation(result); + return result; + } + + /// + /// Adds a completion callback to be executed after the operation has faulted or was canceled. + /// + /// An operation to be continued. + /// The callback to be executed when the operation has faulted/was canceled. + /// Returns a continuation operation that completes after both source operation and the callback has completed. + /// + public static IAsyncOperation Catch(this IAsyncOperation op, Action errorCallback) where TException : Exception + { + if (errorCallback == null) + { + throw new ArgumentNullException(nameof(errorCallback)); + } + + var result = new CatchContinuationResult(errorCallback); op.AddContinuation(result); return result; } diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncCompletionSource.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncCompletionSource.cs index bc76d03..d724726 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncCompletionSource.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncCompletionSource.cs @@ -7,7 +7,7 @@ namespace UnityFx.Async { /// - /// Represents the producer side of a unbound to a delegate, providing access to the consumer side through the property. + /// Represents the producer side of an asynchronous operation unbound to a delegate, providing access to the consumer side through the property. /// /// public interface IAsyncCompletionSource diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperation.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperation.cs index 8915cc7..84ca82d 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperation.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperation.cs @@ -44,7 +44,8 @@ public enum AsyncOperationStatus } /// - /// A disposable with completion status information. + /// Represents the consumer side of an asynchronous operation. A disposable + /// with status information. /// /// Task /// @@ -95,6 +96,10 @@ public interface IAsyncOperation : IAsyncOperationEvents, IAsyncResult, IDisposa /// /// Gets a value indicating whether the operation completed due to being canceled (i.e. with status). /// + /// + /// If is , the operation's will be equal to + /// , and its property will be non-. + /// /// A value indicating whether the operation was canceled. /// /// diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperation{T}.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperation{T}.cs index d09d50a..7aadae4 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperation{T}.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperation{T}.cs @@ -6,7 +6,8 @@ namespace UnityFx.Async { /// - /// Extends an interface with a result value. + /// Represents the consumer side of an asynchronous operation. Extends an + /// interface with a result value. /// /// Task /// diff --git a/src/UnityFx.Async/Implementation/Continuations/CatchContinuationResult.cs b/src/UnityFx.Async/Implementation/Continuations/CatchContinuationResult{TException}.cs similarity index 74% rename from src/UnityFx.Async/Implementation/Continuations/CatchContinuationResult.cs rename to src/UnityFx.Async/Implementation/Continuations/CatchContinuationResult{TException}.cs index a1822ee..2508399 100644 --- a/src/UnityFx.Async/Implementation/Continuations/CatchContinuationResult.cs +++ b/src/UnityFx.Async/Implementation/Continuations/CatchContinuationResult{TException}.cs @@ -6,39 +6,34 @@ namespace UnityFx.Async { - internal class CatchContinuationResult : AsyncResult, IAsyncContinuation + internal class CatchContinuationResult : AsyncResult, IAsyncContinuation where TException : Exception { #region data private static SendOrPostCallback _postCallback; private readonly SynchronizationContext _syncContext; - private readonly Action _errorCallback; + private readonly Action _errorCallback; private IAsyncOperation _op; #endregion #region interface - public CatchContinuationResult(Action errorCallback) + public CatchContinuationResult(Action errorCallback) : base(AsyncOperationStatus.Running) { _syncContext = SynchronizationContext.Current; _errorCallback = errorCallback; } - protected void InvokeErrorCallback(IAsyncOperation op) - { - _errorCallback?.Invoke(op.Exception.InnerException); - } - #endregion #region IAsyncContinuation public void Invoke(IAsyncOperation op) { - if (op.IsCompletedSuccessfully) + if (op.IsCompletedSuccessfully || !(op.Exception.InnerException is TException)) { TrySetCompleted(op.CompletedSynchronously); } @@ -54,7 +49,7 @@ public void Invoke(IAsyncOperation op) { _postCallback = args => { - var c = args as CatchContinuationResult; + var c = args as CatchContinuationResult; c.InvokeErrorCallback(c._op, false); }; } @@ -71,7 +66,7 @@ private void InvokeErrorCallback(IAsyncOperation op, bool completedSynchronously { try { - _errorCallback.Invoke(op.Exception.InnerException); + _errorCallback.Invoke(op.Exception.InnerException as TException); TrySetCompleted(completedSynchronously); } catch (Exception e) From 9a9d863f9061d455e815019c1c4b9c41b8431219 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sun, 1 Apr 2018 00:31:48 +0300 Subject: [PATCH 030/128] Massive generic type parameter renames --- .../UnityFx.Async/Scripts/UnityExtensions.cs | 34 +++++++------- ...}.cs => AsyncCompletionSource{TResult}.cs} | 35 ++++++++------- src/UnityFx.Async/Api/Core/AsyncResult.cs | 30 ++++++------- ...ncResult{T}.cs => AsyncResult{TResult}.cs} | 16 +++---- .../AsyncExtensions.Continuations.cs | 44 +++++++++---------- .../Extensions/AsyncExtensions.Promises.cs | 16 +++---- .../Api/Extensions/AsyncExtensions.Tasks.cs | 34 +++++++------- .../Api/Extensions/AsyncExtensions.Wait.cs | 24 +++++----- .../Api/Extensions/AsyncExtensions.cs | 40 ++++++++--------- ....cs => IAsyncCompletionSource{TResult}.cs} | 25 ++++++----- .../Api/Interfaces/IAsyncOperation.cs | 2 +- ...tion{T}.cs => IAsyncOperation{TResult}.cs} | 5 ++- 12 files changed, 154 insertions(+), 151 deletions(-) rename src/UnityFx.Async/Api/Core/{AsyncCompletionSource{T}.cs => AsyncCompletionSource{TResult}.cs} (92%) rename src/UnityFx.Async/Api/Core/{AsyncResult{T}.cs => AsyncResult{TResult}.cs} (91%) rename src/UnityFx.Async/Api/Interfaces/{IAsyncCompletionSource{T}.cs => IAsyncCompletionSource{TResult}.cs} (77%) rename src/UnityFx.Async/Api/Interfaces/{IAsyncOperation{T}.cs => IAsyncOperation{TResult}.cs} (89%) diff --git a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/UnityExtensions.cs b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/UnityExtensions.cs index e2c4359..1d2ac0b 100644 --- a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/UnityExtensions.cs +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/UnityExtensions.cs @@ -49,7 +49,7 @@ public static IAsyncOperation ToAsync(this AsyncOperation op) } /// - /// Creates an wrapper for the Unity . + /// Creates an wrapper for the Unity . /// /// The source operation. public static IAsyncOperation ToAsync(this ResourceRequest op) where T : UnityEngine.Object @@ -78,7 +78,7 @@ public static IAsyncOperation ToAsync(this ResourceRequest op) where T : U } /// - /// Creates an wrapper for the Unity . + /// Creates an wrapper for the Unity . /// /// The source operation. public static IAsyncOperation ToAsync(this AssetBundleRequest op) where T : UnityEngine.Object @@ -122,7 +122,7 @@ public static AsyncResult ToAsync(this UnityWebRequest request) } /// - /// Creates an wrapper for the specified . + /// Creates an wrapper for the specified . /// /// The source web request. public static WebRequestResult ToAsync(this UnityWebRequest request) where T : class @@ -131,7 +131,7 @@ public static WebRequestResult ToAsync(this UnityWebRequest request) where } /// - /// Creates an wrapper for the specified . + /// Creates an wrapper for the specified . /// /// The source web request. public static WebRequestResult ToAsyncAssetBundle(this UnityWebRequest request) @@ -140,7 +140,7 @@ public static WebRequestResult ToAsyncAssetBundle(this UnityWebRequ } /// - /// Creates an wrapper for the specified . + /// Creates an wrapper for the specified . /// /// The source web request. public static WebRequestResult ToAsyncTexture(this UnityWebRequest request) @@ -149,7 +149,7 @@ public static WebRequestResult ToAsyncTexture(this UnityWebRequest re } /// - /// Creates an wrapper for the specified . + /// Creates an wrapper for the specified . /// /// The source web request. public static WebRequestResult ToAsyncAudioClip(this UnityWebRequest request) @@ -158,7 +158,7 @@ public static WebRequestResult ToAsyncAudioClip(this UnityWebRequest } /// - /// Creates an wrapper for the specified . + /// Creates an wrapper for the specified . /// /// The source web request. public static WebRequestResult ToAsyncMovieTexture(this UnityWebRequest request) @@ -167,7 +167,7 @@ public static WebRequestResult ToAsyncMovieTexture(this UnityWebRe } /// - /// Creates an wrapper for the specified . + /// Creates an wrapper for the specified . /// /// The source web request. public static WebRequestResult ToAsyncByteArray(this UnityWebRequest request) @@ -176,10 +176,10 @@ public static WebRequestResult ToAsyncByteArray(this UnityWebRequest req } /// - /// Creates an wrapper for the specified . + /// Creates an wrapper for the specified . /// /// The source web request. - /// Returns a instance that will complete when the source operation have completed. + /// Returns a instance that will complete when the source operation have completed. public static WebRequestResult ToAsyncString(this UnityWebRequest request) { return WebRequestResult.FromUnityWebRequest(request); @@ -201,7 +201,7 @@ public static AsyncResult ToAsync(this WWW request) } /// - /// Creates an wrapper for the specified . + /// Creates an wrapper for the specified . /// /// The source web request. public static WwwResult ToAsync(this WWW request) where T : class @@ -210,7 +210,7 @@ public static WwwResult ToAsync(this WWW request) where T : class } /// - /// Creates an wrapper for the specified . + /// Creates an wrapper for the specified . /// /// The source web request. public static WwwResult ToAsyncAssetBundle(this WWW request) @@ -219,7 +219,7 @@ public static WwwResult ToAsyncAssetBundle(this WWW request) } /// - /// Creates an wrapper for the specified . + /// Creates an wrapper for the specified . /// /// The source web request. public static WwwResult ToAsyncTexture(this WWW request) @@ -228,7 +228,7 @@ public static WwwResult ToAsyncTexture(this WWW request) } /// - /// Creates an wrapper for the specified . + /// Creates an wrapper for the specified . /// /// The source web request. public static WwwResult ToAsyncAudioClip(this WWW request) @@ -237,7 +237,7 @@ public static WwwResult ToAsyncAudioClip(this WWW request) } /// - /// Creates an wrapper for the specified . + /// Creates an wrapper for the specified . /// /// The source web request. public static WwwResult ToAsyncMovieTexture(this WWW request) @@ -246,7 +246,7 @@ public static WwwResult ToAsyncMovieTexture(this WWW request) } /// - /// Creates an wrapper for the specified . + /// Creates an wrapper for the specified . /// /// The source web request. public static WwwResult ToAsyncByteArray(this WWW request) @@ -255,7 +255,7 @@ public static WwwResult ToAsyncByteArray(this WWW request) } /// - /// Creates an wrapper for the specified . + /// Creates an wrapper for the specified . /// /// The source web request. public static WwwResult ToAsyncString(this WWW request) diff --git a/src/UnityFx.Async/Api/Core/AsyncCompletionSource{T}.cs b/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs similarity index 92% rename from src/UnityFx.Async/Api/Core/AsyncCompletionSource{T}.cs rename to src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs index 4edcbdb..dfbfc39 100644 --- a/src/UnityFx.Async/Api/Core/AsyncCompletionSource{T}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs @@ -7,10 +7,11 @@ namespace UnityFx.Async { /// - /// Represents an asynchronous operation with external completion control. + /// Represents an asynchronous operation with external completion control. /// + /// Type of the operation result value. /// - public sealed class AsyncCompletionSource : AsyncResult, IAsyncCompletionSource + public sealed class AsyncCompletionSource : AsyncResult, IAsyncCompletionSource { #region interface @@ -253,9 +254,9 @@ public void SetExceptions(IEnumerable exceptions, bool completedSynch /// The operation result. /// Thrown if the transition fails. /// Thrown is the operation is disposed. - /// - /// - public void SetResult(T result) + /// + /// + public void SetResult(TResult result) { if (!base.TrySetResult(result, false)) { @@ -270,9 +271,9 @@ public void SetResult(T result) /// Value of the property. /// Thrown if the transition fails. /// Thrown is the operation is disposed. - /// - /// - public void SetResult(T result, bool completedSynchronously) + /// + /// + public void SetResult(TResult result, bool completedSynchronously) { if (!base.TrySetResult(result, completedSynchronously)) { @@ -287,14 +288,14 @@ public void SetResult(T result, bool completedSynchronously) /// Value of the property. /// Thrown is the operation is disposed. /// Returns if the attemp was successfull; otherwise. - /// - /// - public new bool TrySetResult(T result, bool completedSynchronously) => base.TrySetResult(result, completedSynchronously); + /// + /// + public new bool TrySetResult(TResult result, bool completedSynchronously) => base.TrySetResult(result, completedSynchronously); /// /// Copies state of the specified operation. /// - internal void CopyCompletionState(IAsyncOperation patternOp, bool completedSynchronously) + internal void CopyCompletionState(IAsyncOperation patternOp, bool completedSynchronously) { if (!TryCopyCompletionState(patternOp, completedSynchronously)) { @@ -305,7 +306,7 @@ internal void CopyCompletionState(IAsyncOperation patternOp, bool completedSy /// /// Attemts to copy state of the specified operation. /// - internal bool TryCopyCompletionState(IAsyncOperation patternOp, bool completedSynchronously) + internal bool TryCopyCompletionState(IAsyncOperation patternOp, bool completedSynchronously) { if (patternOp.IsCompletedSuccessfully) { @@ -324,7 +325,7 @@ internal bool TryCopyCompletionState(IAsyncOperation patternOp, bool complete #region IAsyncCompletionSource /// - public IAsyncOperation Operation => this; + public IAsyncOperation Operation => this; /// /// @@ -342,9 +343,9 @@ internal bool TryCopyCompletionState(IAsyncOperation patternOp, bool complete public bool TrySetExceptions(IEnumerable exceptions) => base.TrySetExceptions(exceptions, false); /// - /// - /// - public bool TrySetResult(T result) => base.TrySetResult(result, false); + /// + /// + public bool TrySetResult(TResult result) => base.TrySetResult(result, false); #endregion } diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 6a2d03d..c161574 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -631,7 +631,7 @@ public static AsyncResult FromCanceled(object asyncState) } /// - /// Creates a that is canceled. + /// Creates a that is canceled. /// /// A canceled operation. /// @@ -644,7 +644,7 @@ public static AsyncResult FromCanceled() } /// - /// Creates a that is canceled. + /// Creates a that is canceled. /// /// User-defined data returned by . /// A canceled operation. @@ -716,7 +716,7 @@ public static AsyncResult FromExceptions(IEnumerable exceptions, obje } /// - /// Creates a that has completed with a specified exception. + /// Creates a that has completed with a specified exception. /// /// The exception to complete the operation with. /// A faulted operation. @@ -730,7 +730,7 @@ public static AsyncResult FromException(Exception exception) } /// - /// Creates a that has completed with a specified exception. + /// Creates a that has completed with a specified exception. /// /// The exception to complete the operation with. /// User-defined data returned by . @@ -745,7 +745,7 @@ public static AsyncResult FromException(Exception exception, object asyncS } /// - /// Creates a that has completed with specified exceptions. + /// Creates a that has completed with specified exceptions. /// /// Exceptions to complete the operation with. /// A faulted operation. @@ -759,7 +759,7 @@ public static AsyncResult FromExceptions(IEnumerable exceptions } /// - /// Creates a that has completed with specified exceptions. + /// Creates a that has completed with specified exceptions. /// /// Exceptions to complete the operation with. /// User-defined data returned by . @@ -774,7 +774,7 @@ public static AsyncResult FromExceptions(IEnumerable exceptions } /// - /// Creates a that has completed with a specified result. + /// Creates a that has completed with a specified result. /// /// The result value with which to complete the operation. /// A completed operation with the specified result value. @@ -788,7 +788,7 @@ public static AsyncResult FromResult(T result) } /// - /// Creates a that has completed with a specified result. + /// Creates a that has completed with a specified result. /// /// The result value with which to complete the operation. /// User-defined data returned by . @@ -917,8 +917,8 @@ public static AsyncResult Retry(Func opFactory, TimeSpan retryD /// Thrown if the is . /// Thrown if or is less than zero. /// An operation that represents the retry process. - /// - public static AsyncResult Retry(Func> opFactory, int millisecondsRetryDelay, int maxRetryCount = 0) + /// + public static AsyncResult Retry(Func> opFactory, int millisecondsRetryDelay, int maxRetryCount = 0) { if (opFactory == null) { @@ -935,7 +935,7 @@ public static AsyncResult Retry(Func> opFactory, int mi throw new ArgumentOutOfRangeException(nameof(maxRetryCount), maxRetryCount, Constants.ErrorValueIsLessThanZero); } - return new RetryResult(opFactory, millisecondsRetryDelay, maxRetryCount); + return new RetryResult(opFactory, millisecondsRetryDelay, maxRetryCount); } /// @@ -947,8 +947,8 @@ public static AsyncResult Retry(Func> opFactory, int mi /// Thrown if the is . /// Thrown if or is less than zero. /// An operation that represents the retry process. - /// - public static AsyncResult Retry(Func> opFactory, TimeSpan retryDelay, int maxRetryCount = 0) + /// + public static AsyncResult Retry(Func> opFactory, TimeSpan retryDelay, int maxRetryCount = 0) { var millisecondsDelay = (long)retryDelay.TotalMilliseconds; @@ -971,7 +971,7 @@ public static AsyncResult Retry(Func> opFactory, TimeSp /// An operation that represents the completion of all of the supplied operations. /// Thrown if is . /// Thrown if the collection contained a operation.. - /// + /// /// public static AsyncResult WhenAll(IEnumerable ops) { @@ -1008,7 +1008,7 @@ public static AsyncResult WhenAll(IEnumerable ops) /// Thrown if is . /// Thrown if the collection contained a operation.. /// - /// + /// public static AsyncResult WhenAll(IEnumerable> ops) { if (ops == null) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult{T}.cs b/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs similarity index 91% rename from src/UnityFx.Async/Api/Core/AsyncResult{T}.cs rename to src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs index 26b2b55..ada0d5d 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult{T}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs @@ -14,19 +14,19 @@ namespace UnityFx.Async /// /// A lightweight net35-compatible asynchronous operation that can return a value. /// - /// Type of the operation result. + /// Type of the operation result value. /// /// /// #if NET35 - public class AsyncResult : AsyncResult, IAsyncOperation + public class AsyncResult : AsyncResult, IAsyncOperation #else - public class AsyncResult : AsyncResult, IAsyncOperation, IObservable + public class AsyncResult : AsyncResult, IAsyncOperation, IObservable #endif { #region data - private T _result; + private TResult _result; #endregion @@ -104,7 +104,7 @@ internal AsyncResult(IEnumerable exceptions, object asyncState) /// /// Result value. /// User-defined data to assosiate with the operation. - internal AsyncResult(T result, object asyncState) + internal AsyncResult(TResult result, object asyncState) : base(AsyncOperationStatus.RanToCompletion, asyncState) { _result = result; @@ -117,7 +117,7 @@ internal AsyncResult(T result, object asyncState) /// Value of the property. /// Thrown is the operation is disposed. /// Returns if the attemp was successfull; otherwise. - protected internal bool TrySetResult(T result, bool completedSynchronously) + protected internal bool TrySetResult(TResult result, bool completedSynchronously) { ThrowIfDisposed(); @@ -136,7 +136,7 @@ protected internal bool TrySetResult(T result, bool completedSynchronously) #region IAsyncOperation /// - public T Result + public TResult Result { get { @@ -157,7 +157,7 @@ public T Result #if !NET35 /// - public IDisposable Subscribe(IObserver observer) + public IDisposable Subscribe(IObserver observer) { ThrowIfDisposed(); diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs index 15ea5d6..793950e 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs @@ -149,7 +149,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, ActionAn 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) + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func action) { return ContinueWith(op, action, AsyncContinuationOptions.CaptureSynchronizationContext); } @@ -162,14 +162,14 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, FuncOptions 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) + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func action, AsyncContinuationOptions options) { if (action == null) { throw new ArgumentNullException(nameof(action)); } - var result = new AsyncContinuationResult(options, action, null); + var result = new AsyncContinuationResult(options, action, null); op.AddContinuation(result); return result; } @@ -182,7 +182,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, FuncA 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) + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func action, object userState) { return ContinueWith(op, action, userState, AsyncContinuationOptions.CaptureSynchronizationContext); } @@ -196,65 +196,65 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, FuncOptions 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) + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func action, object userState, AsyncContinuationOptions options) { if (action == null) { throw new ArgumentNullException(nameof(action)); } - var result = new AsyncContinuationResult(options, action, userState); + var result = new AsyncContinuationResult(options, action, userState); op.AddContinuation(result); return result; } /// - /// Creates a continuation that executes when the target completes. + /// 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) + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action> action) { return ContinueWith(op, action, AsyncContinuationOptions.CaptureSynchronizationContext); } /// - /// Creates a continuation that executes when the target completes. + /// 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) + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action> action, AsyncContinuationOptions options) { if (action == null) { throw new ArgumentNullException(nameof(action)); } - var result = new AsyncContinuationResult(options, action, null); + var result = new AsyncContinuationResult(options, action, null); op.AddContinuation(result); return result; } /// - /// Creates a continuation that executes when the target completes. + /// 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) + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action, object> action, object userState) { return ContinueWith(op, action, userState, AsyncContinuationOptions.CaptureSynchronizationContext); } /// - /// Creates a continuation that executes when the target completes. + /// Creates a continuation that executes when the target completes. /// /// The operation to continue. /// An action to run when the completes. @@ -262,14 +262,14 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action /// 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) + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action, object> action, object userState, AsyncContinuationOptions options) { if (action == null) { throw new ArgumentNullException(nameof(action)); } - var result = new AsyncContinuationResult(options, action, userState); + var result = new AsyncContinuationResult(options, action, userState); op.AddContinuation(result); return result; } @@ -281,7 +281,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action /// 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, U> action) + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func, TNewResult> action) { return ContinueWith(op, action, AsyncContinuationOptions.CaptureSynchronizationContext); } @@ -294,14 +294,14 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, /// 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, U> action, AsyncContinuationOptions options) + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func, TNewResult> action, AsyncContinuationOptions options) { if (action == null) { throw new ArgumentNullException(nameof(action)); } - var result = new AsyncContinuationResult(options, action, null); + var result = new AsyncContinuationResult(options, action, null); op.AddContinuation(result); return result; } @@ -314,7 +314,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, /// 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, U> action, object userState) + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func, object, TNewResult> action, object userState) { return ContinueWith(op, action, userState, AsyncContinuationOptions.CaptureSynchronizationContext); } @@ -328,14 +328,14 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, /// 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, U> action, object userState, AsyncContinuationOptions options) + public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func, object, TNewResult> action, object userState, AsyncContinuationOptions options) { if (action == null) { throw new ArgumentNullException(nameof(action)); } - var result = new AsyncContinuationResult(options, action, userState); + var result = new AsyncContinuationResult(options, action, userState); op.AddContinuation(result); return result; } diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs index 229ff72..1f4da0b 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs @@ -35,14 +35,14 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the callback has completed. /// - public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback) + public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback) { if (successCallback == null) { throw new ArgumentNullException(nameof(successCallback)); } - var result = new ThenContinuationResult(successCallback, null); + var result = new ThenContinuationResult(successCallback, null); op.AddContinuation(result); return result; } @@ -73,14 +73,14 @@ public static IAsyncOperation Then(this IAsyncOperation op, FuncThe callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the operation returned by has completed. /// - public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback) + public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback) { if (successCallback == null) { throw new ArgumentNullException(nameof(successCallback)); } - var result = new ThenContinuationResult(successCallback, null); + var result = new ThenContinuationResult(successCallback, null); op.AddContinuation(result); return result; } @@ -118,7 +118,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba /// The callback to be executed when the operation has faulted/was canceled. /// Returns a continuation operation that completes after both source operation and the callback has completed. /// - public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback, Action errorCallback) + public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback, Action errorCallback) { if (successCallback == null) { @@ -130,7 +130,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action succ throw new ArgumentNullException(nameof(errorCallback)); } - var result = new ThenContinuationResult(successCallback, errorCallback); + var result = new ThenContinuationResult(successCallback, errorCallback); op.AddContinuation(result); return result; } @@ -168,7 +168,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, FuncThe callback to be executed when the operation has faulted/was canceled. /// Returns a continuation operation that completes after both source operation and the operation returned by has completed. /// - public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback, Action errorCallback) + public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback, Action errorCallback) { if (successCallback == null) { @@ -180,7 +180,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func(successCallback, errorCallback); + var result = new ThenContinuationResult(successCallback, errorCallback); op.AddContinuation(result); return result; } diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs index 560b41a..823533d 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs @@ -61,7 +61,7 @@ public void OnCompleted(Action continuation) /// /// Provides an object that waits for the completion of an asynchronous operation. This type and its members are intended for compiler use only. /// - /// + /// public struct AsyncAwaiter : INotifyCompletion { private readonly IAsyncOperation _op; @@ -126,9 +126,9 @@ public ConfiguredAsyncAwaitable(IAsyncOperation op, bool continueOnCapturedConte } /// - /// Provides an awaitable object that allows for configured awaits on . This type is intended for compiler use only. + /// Provides an awaitable object that allows for configured awaits on . This type is intended for compiler use only. /// - /// + /// public struct ConfiguredAsyncAwaitable { private readonly AsyncAwaiter _awaiter; @@ -151,7 +151,7 @@ public ConfiguredAsyncAwaitable(IAsyncOperation op, bool continueOnCapturedCo /// Returns the operation awaiter. This method is intended for compiler rather than use directly in code. /// /// The operation to await. - /// + /// public static AsyncAwaiter GetAwaiter(this IAsyncOperation op) { return new AsyncAwaiter(op, false); @@ -162,9 +162,9 @@ public static AsyncAwaiter GetAwaiter(this IAsyncOperation op) /// /// The operation to await. /// - public static AsyncAwaiter GetAwaiter(this IAsyncOperation op) + public static AsyncAwaiter GetAwaiter(this IAsyncOperation op) { - return new AsyncAwaiter(op, false); + return new AsyncAwaiter(op, false); } /// @@ -184,9 +184,9 @@ public static ConfiguredAsyncAwaitable ConfigureAwait(this IAsyncOperation op, b /// The operation to await. /// If attempts to marshal the continuation back to the original context captured. /// An object used to await the operation. - public static ConfiguredAsyncAwaitable ConfigureAwait(this IAsyncOperation op, bool continueOnCapturedContext) + public static ConfiguredAsyncAwaitable ConfigureAwait(this IAsyncOperation op, bool continueOnCapturedContext) { - return new ConfiguredAsyncAwaitable(op, continueOnCapturedContext); + return new ConfiguredAsyncAwaitable(op, continueOnCapturedContext); } #endregion @@ -197,7 +197,7 @@ public static ConfiguredAsyncAwaitable ConfigureAwait(this IAsyncOperation /// Creates a instance matching the source . /// /// The target operation. - /// + /// public static Task ToTask(this IAsyncOperation op) { var status = op.Status; @@ -232,7 +232,7 @@ public static Task ToTask(this IAsyncOperation op) /// /// The target operation. /// - public static Task ToTask(this IAsyncOperation op) + public static Task ToTask(this IAsyncOperation op) { var status = op.Status; @@ -242,17 +242,17 @@ public static Task ToTask(this IAsyncOperation op) } else if (status == AsyncOperationStatus.Faulted) { - return Task.FromException(op.Exception.InnerException); + return Task.FromException(op.Exception.InnerException); } else if (status == AsyncOperationStatus.Canceled) { - return Task.FromCanceled(CancellationToken.None); + return Task.FromCanceled(CancellationToken.None); } else { - var result = new TaskCompletionSource(); + var result = new TaskCompletionSource(); - if (!op.TryAddCompletionCallback(asyncOp => AsyncContinuation.InvokeTaskContinuation(asyncOp as IAsyncOperation, result), null)) + if (!op.TryAddCompletionCallback(asyncOp => AsyncContinuation.InvokeTaskContinuation(asyncOp as IAsyncOperation, result), null)) { AsyncContinuation.InvokeTaskContinuation(op, result); } @@ -300,9 +300,9 @@ public static AsyncResult ToAsync(this Task task) /// /// The task to convert to . /// An that represents the . - public static AsyncResult ToAsync(this Task task) + public static AsyncResult ToAsync(this Task task) { - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); task.ContinueWith( t => @@ -327,7 +327,7 @@ public static AsyncResult ToAsync(this Task task) #endregion - #region Implementation + #region implementation private static void SetAwaitContiniation(IAsyncOperation op, Action continuation, bool captureSynchronizationContext) { diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs index b152573..b607f45 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs @@ -116,7 +116,7 @@ public static void Join(this IAsyncOperation op) /// Thrown is the operation is disposed. /// /// - /// + /// public static void Join(this IAsyncOperation op, int millisecondsTimeout) { var result = true; @@ -146,7 +146,7 @@ public static void Join(this IAsyncOperation op, int millisecondsTimeout) /// Thrown is the operation is disposed. /// /// - /// + /// public static void Join(this IAsyncOperation op, TimeSpan timeout) { var result = true; @@ -167,7 +167,7 @@ public static void Join(this IAsyncOperation op, TimeSpan timeout) } /// - /// Waits for the to complete execution. After that rethrows the operation exception (if any). + /// Waits for the to complete execution. After that rethrows the operation exception (if any). /// /// The operation to join. /// The operation result. @@ -175,7 +175,7 @@ public static void Join(this IAsyncOperation op, TimeSpan timeout) /// /// /// - public static T Join(this IAsyncOperation op) + public static TResult Join(this IAsyncOperation op) { if (!op.IsCompleted) { @@ -187,7 +187,7 @@ public static T Join(this IAsyncOperation op) } /// - /// Waits for the to complete execution within a specified number of milliseconds. After that rethrows the operation exception (if any). + /// 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. @@ -195,10 +195,10 @@ public static T Join(this IAsyncOperation op) /// is a negative number other than -1. /// Thrown if the operation did not completed within . /// Thrown is the operation is disposed. - /// - /// + /// + /// /// - public static T Join(this IAsyncOperation op, int millisecondsTimeout) + public static TResult Join(this IAsyncOperation op, int millisecondsTimeout) { var result = true; @@ -220,7 +220,7 @@ public static T Join(this IAsyncOperation op, int millisecondsTimeout) } /// - /// Waits for the to complete execution within a specified timeout. After that rethrows the operation exception (if any). + /// 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. @@ -228,10 +228,10 @@ public static T Join(this IAsyncOperation op, int millisecondsTimeout) /// 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 T Join(this IAsyncOperation op, TimeSpan timeout) + public static TResult Join(this IAsyncOperation op, TimeSpan timeout) { var result = true; diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs index 3c884ed..0e507bf 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs @@ -121,15 +121,15 @@ public static void SetCanceled(this IAsyncCompletionSource completionSource) } /// - /// Transitions the underlying into the state. + /// 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) + /// + /// + /// + public static void SetCanceled(this IAsyncCompletionSource completionSource) { if (!completionSource.TrySetCanceled()) { @@ -157,17 +157,17 @@ public static void SetException(this IAsyncCompletionSource completionSource, Ex } /// - /// Transitions the underlying into the state. + /// 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) + /// + /// + /// + public static void SetException(this IAsyncCompletionSource completionSource, Exception exception) { if (!completionSource.TrySetException(exception)) { @@ -195,17 +195,17 @@ public static void SetExceptions(this IAsyncCompletionSource completionSource, I } /// - /// Transitions the underlying into the state. + /// 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) + /// + /// + /// + public static void SetExceptions(this IAsyncCompletionSource completionSource, IEnumerable exceptions) { if (!completionSource.TrySetExceptions(exceptions)) { @@ -231,16 +231,16 @@ public static void SetCompleted(this IAsyncCompletionSource completionSource) } /// - /// Transitions the underlying into the state. + /// 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, T result) + /// + /// + /// + public static void SetResult(this IAsyncCompletionSource completionSource, TResult result) { if (!completionSource.TrySetResult(result)) { diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncCompletionSource{T}.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncCompletionSource{TResult}.cs similarity index 77% rename from src/UnityFx.Async/Api/Interfaces/IAsyncCompletionSource{T}.cs rename to src/UnityFx.Async/Api/Interfaces/IAsyncCompletionSource{TResult}.cs index 123d5ff..5995b3d 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncCompletionSource{T}.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncCompletionSource{TResult}.cs @@ -7,29 +7,30 @@ namespace UnityFx.Async { /// - /// Represents the producer side of a unbound to a delegate, providing access to the consumer side through the property. + /// Represents the producer side of a unbound to a delegate, providing access to the consumer side through the property. /// - /// - public interface IAsyncCompletionSource + /// Type of the operation result value. + /// + public interface IAsyncCompletionSource { /// /// Gets the operation being controller by the source. /// /// The underlying operation instance. - IAsyncOperation Operation { get; } + IAsyncOperation Operation { get; } /// - /// Attempts to transition the underlying into the state. + /// Attempts to transition the underlying into the state. /// /// Thrown is the operation is disposed. /// Returns if the attemp was successfull; otherwise. /// /// - /// + /// bool TrySetCanceled(); /// - /// Attempts to transition the underlying into the state. + /// Attempts to transition the underlying into the state. /// /// An exception that caused the operation to end prematurely. /// Thrown if is . @@ -37,11 +38,11 @@ public interface IAsyncCompletionSource /// Returns if the attemp was successfull; otherwise. /// /// - /// + /// bool TrySetException(Exception exception); /// - /// Attempts to transition the underlying into the state. + /// Attempts to transition the underlying into the state. /// /// An exception that caused the operation to end prematurely. /// Thrown if is . @@ -49,11 +50,11 @@ public interface IAsyncCompletionSource /// Returns if the attemp was successfull; otherwise. /// /// - /// + /// bool TrySetExceptions(IEnumerable exceptions); /// - /// Attempts to transition the underlying into the state. + /// Attempts to transition the underlying into the state. /// /// The operation result. /// Thrown is the operation is disposed. @@ -61,6 +62,6 @@ public interface IAsyncCompletionSource /// /// /// - bool TrySetResult(T result); + bool TrySetResult(TResult result); } } diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperation.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperation.cs index 84ca82d..94760cb 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperation.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperation.cs @@ -48,7 +48,7 @@ public enum AsyncOperationStatus /// with status information. /// /// Task - /// + /// /// public interface IAsyncOperation : IAsyncOperationEvents, IAsyncResult, IDisposable { diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperation{T}.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperation{TResult}.cs similarity index 89% rename from src/UnityFx.Async/Api/Interfaces/IAsyncOperation{T}.cs rename to src/UnityFx.Async/Api/Interfaces/IAsyncOperation{TResult}.cs index 7aadae4..4e1b44b 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperation{T}.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperation{TResult}.cs @@ -9,10 +9,11 @@ namespace UnityFx.Async /// Represents the consumer side of an asynchronous operation. Extends an /// interface with a result value. /// + /// Type of th operation result value. /// Task /// /// - public interface IAsyncOperation : IAsyncOperation + public interface IAsyncOperation : IAsyncOperation { /// /// Gets the operation result value. @@ -25,6 +26,6 @@ public interface IAsyncOperation : IAsyncOperation /// Result of the operation. /// Thrown either if the property is accessed before operation is completed. /// Thrown if the operation is in or state. - T Result { get; } + TResult Result { get; } } } From 114445c5988ea976000751a1bb9d38136dce3d05 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sun, 1 Apr 2018 19:59:37 +0300 Subject: [PATCH 031/128] README update --- README.md | 87 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 66 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index fb54fee..e51d773 100644 --- a/README.md +++ b/README.md @@ -9,33 +9,35 @@ Unity Asset Store | [![Asynchronous operations for Unity](https://img.shields.io ## Synopsis -*UnityFx.Async* is a set of of classes and interfaces that extend [Unity3d](https://unity3d.com) asynchronous operations and can be used very much like [Task-based Asynchronous Pattern (TAP)](https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming) in .NET. The library at its core defines a container ([AsyncResult](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncResult.html)) for state and result value of an 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). +*UnityFx.Async* is a set of of classes and interfaces that extend [Unity3d](https://unity3d.com) asynchronous operations and can be used very much like [Task-based Asynchronous Pattern (TAP)](https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming) in .NET or [Promises](https://developers.google.com/web/fundamentals/primers/promises) in Javascript. The library at its core defines a container ([AsyncResult](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncResult.html)) for state and result value of an 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). Library is designed as a lightweight [Unity3d](https://unity3d.com)-compatible [Tasks](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) alternative (not a replacement though). Main design goals are: -- Minimum object size and allocations. -- Multithreading support. The library classes can be safely used from different threads (unless explicitly stated otherwise). -- [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task)-like interface and behaviour. In most cases library classes can be used just like corresponding [TPL](https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl) entities. +- Minimum object size and number of allocations. +- Extensibility. The library operations are designed to be inherited (if needed). +- Thread-safe. The library classes can be safely used from different threads (unless explicitly stated otherwise). +- [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task)-like interface and behaviour. In many cases library classes can be used much like corresponding [TPL](https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl) entities. - [Unity3d](https://unity3d.com) compatibility. This includes possibility to yield any operations in coroutines and net35-compilance. -The comparison table below shows how *UnityFx.Async* entities relate to [Tasks](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task): +## Getting Started +### Getting the code +You can get the code by cloning the github repository using your preffered git client UI or you can do it from command line as follows: +```cmd +git clone https://github.com/Arvtesh/UnityFx.Async.git +git submodule -q update --init +``` +### Getting binaries +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) from the editor. -TPL | UnityFx.Async | Notes -----|---------------|------ -[Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) | [AsyncResult](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncResult.html), [IAsyncOperation](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.IAsyncOperation.html) | Represents an asynchronous operation. -[Task<TResult>](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1) | [AsyncResult<TResult>](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncResult-1.html), [IAsyncOperation<TResult>](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.IAsyncOperation-1.html) | Represents an asynchronous operation that can return a value. -[TaskStatus](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskstatus) | [AsyncOperationStatus](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncOperationStatus.html) | Represents the current stage in the lifecycle of an asynchronous operation. -[TaskCreationOptions](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcreationoptions) | - | Specifies flags that control optional behavior for the creation and execution of asynchronous operations. -[TaskContinuationOptions](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcontinuationoptions) | [AsyncContinuationOptions](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncContinuationOptions.html) | Specifies the behavior for an asynchronous operation that is created by using continuation methods (`ContinueWith`). -[TaskCanceledException](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcanceledexception) | - | Represents an exception used to communicate an asynchronous operation cancellation. -[TaskCompletionSource<TResult>](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcompletionsource-1) | [AsyncCompletionSource](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncCompletionSource.html), [IAsyncCompletionSource](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.IAsyncCompletionSource.html), [AsyncCompletionSource<TResult>](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncCompletionSource-1.html), [IAsyncCompletionSource<TResult>](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.IAsyncCompletionSource-1.html) | Represents the producer side of an asyncronous operation unbound to a delegate. -[TaskScheduler](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskscheduler) | - | Represents an object that handles the low-level work of queuing asynchronous operations onto threads. -[TaskFactory](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskfactory), [TaskFactory<TResult>](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskfactory-1) | - | Provides support for creating and scheduling asynchronous operations. -- | [AsyncResultQueue<T>](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncResultQueue-1.html) | A FIFO queue of asynchronous operations executed sequentially. +## Understanding the concepts +The below listed topics are just a quict summary of the problems and the proposed solutions. For more details on the topic please see this [excellent article](http://www.what-could-possibly-go-wrong.com/promises-for-game-development/). +### Why callbacks are bad +TODO -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. -- Memory usage is a concern ([Tasks](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) tend to do quite a lot of allocations). -- An extendable [IAsyncResult](https://docs.microsoft.com/en-us/dotnet/api/system.iasyncresult) implementation is needed. +### Why coroutines are bad +TODO + +### Promises to the Rescue +TODO ## Code Example Typical use-case of the library is wrapping [Unity3d](https://unity3d.com) web requests in [Task-based Asynchronous Pattern](https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming) manner: @@ -152,6 +154,37 @@ void WaitForLoadOperationInAnotherThread(string textureUrl) } ``` +## Comparison to .NET Tasks +The comparison table below shows how *UnityFx.Async* entities relate to [Tasks](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task): + +TPL | UnityFx.Async | Notes +----|---------------|------ +[Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) | [AsyncResult](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncResult.html), [IAsyncOperation](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.IAsyncOperation.html) | Represents an asynchronous operation. +[Task<TResult>](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1) | [AsyncResult<TResult>](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncResult-1.html), [IAsyncOperation<TResult>](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.IAsyncOperation-1.html) | Represents an asynchronous operation that can return a value. +[TaskStatus](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskstatus) | [AsyncOperationStatus](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncOperationStatus.html) | Represents the current stage in the lifecycle of an asynchronous operation. +[TaskCreationOptions](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcreationoptions) | - | Specifies flags that control optional behavior for the creation and execution of asynchronous operations. +[TaskContinuationOptions](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcontinuationoptions) | [AsyncContinuationOptions](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncContinuationOptions.html) | Specifies the behavior for an asynchronous operation that is created by using continuation methods (`ContinueWith`). +[TaskCanceledException](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcanceledexception) | - | Represents an exception used to communicate an asynchronous operation cancellation. +[TaskCompletionSource<TResult>](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcompletionsource-1) | [AsyncCompletionSource](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncCompletionSource.html), [IAsyncCompletionSource](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.IAsyncCompletionSource.html), [AsyncCompletionSource<TResult>](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncCompletionSource-1.html), [IAsyncCompletionSource<TResult>](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.IAsyncCompletionSource-1.html) | Represents the producer side of an asyncronous operation unbound to a delegate. +[TaskScheduler](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskscheduler) | - | Represents an object that handles the low-level work of queuing asynchronous operations onto threads. +[TaskFactory](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskfactory), [TaskFactory<TResult>](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskfactory-1) | - | Provides support for creating and scheduling asynchronous operations. +- | [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. +- Memory usage is a concern ([Tasks](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) tend to do quite a lot of allocations). +- An extendable [IAsyncResult](https://docs.microsoft.com/en-us/dotnet/api/system.iasyncresult) implementation is needed. + +## Performance + +The tables below contains comparison of performance to several other popular frameworks (NOTE: this section needs performing more precise tests, the results below might not be accurate enough): + +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) +-|-|-|- +Operation data size for 32-bit systems (in bytes) | 28+ | 36+ | 40+ +Number of allocations per continuation (`ContinueWith`/`Then`) | 1+ | 5+ | 2+ + + ## 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 the like 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 for the best of human kind. @@ -166,11 +199,23 @@ Please see the links below for extended information on the product: - [Microsoft Visual Studio 2017](https://www.visualstudio.com/vs/community/) - [Unity3d](https://store.unity.com/) - [Unity Asset Store](https://assetstore.unity.com) +- [Promise pattern](https://en.wikipedia.org/wiki/Futures_and_promises) +- [Promises for Game Development](http://www.what-could-possibly-go-wrong.com/promises-for-game-development/) ## Contributing Please see [contributing guide](CONTRIBUTING.md) for details. +## Versioning + +The project uses [SemVer](https://semver.org/) versioning pattern. For the versions available, see [tags in this repository](https://github.com/Arvtesh/UnityFx.Async/tags). + ## License 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): +* [.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. +* Everyone who ever commented or left any feedback on the project. It's always very helpful. From 51b6b98d4ac7b4654bde3b2a7fc7034040320d1a Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sun, 1 Apr 2018 20:33:05 +0300 Subject: [PATCH 032/128] Added ToAsync observable extension --- src/UnityFx.Async/Api/Core/AsyncResult.cs | 1 + .../Extensions/AsyncExtensions.Observables.cs | 41 +++++++++++ .../Api/Extensions/AsyncExtensions.cs | 20 ------ .../Observable/AsyncObservableResult{T}.cs | 68 +++++++++++++++++++ 4 files changed, 110 insertions(+), 20 deletions(-) create mode 100644 src/UnityFx.Async/Api/Extensions/AsyncExtensions.Observables.cs create mode 100644 src/UnityFx.Async/Implementation/Observable/AsyncObservableResult{T}.cs diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index c161574..ec47147 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -36,6 +36,7 @@ namespace UnityFx.Async /// to this class if Unity/net35 compatibility is a concern. /// /// + /// Promises for game development /// How to implement the IAsyncResult design pattern /// Task-based Asynchronous Pattern (TAP) /// Asynchronous Programming Model (APM) diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Observables.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Observables.cs new file mode 100644 index 0000000..81ece22 --- /dev/null +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Observables.cs @@ -0,0 +1,41 @@ +// 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 +{ +#if !NET35 + + partial class AsyncExtensions + { + /// + /// Creates a instance that can be used to track the source operation progress. + /// + /// Type of the operation result. + /// The operation to track. + /// Returns an instance that can be used to track the operation. + public static IObservable ToObservable(this IAsyncOperation op) + { + if (op is AsyncResult ar) + { + return ar; + } + + return new AsyncObservable(op); + } + + /// + /// Creates a instance that can be used to track the source observable progress. + /// + /// Type of the operation result. + /// The source observable. + /// Returns an instance that can be used to track the observable. + public static IAsyncOperation ToAsync(this IObservable observable) + { + return new AsyncObservableResult(observable); + } + } + +#endif +} diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs index 0e507bf..c4933b2 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs @@ -79,26 +79,6 @@ internal static void ThrowIfNonSuccess(IAsyncOperation op, bool throwAggregate) } } -#if !NET35 - - /// - /// Creates a instance that can be used to track the source operation progress. - /// - /// Type of the operation result. - /// The operation to track. - /// Returns an instance that can be used to track the operation. - public static IObservable ToObservable(this IAsyncOperation op) - { - if (op is AsyncResult ar) - { - return ar; - } - - return new AsyncObservable(op); - } - -#endif - #endregion #region IAsyncCompletionSource diff --git a/src/UnityFx.Async/Implementation/Observable/AsyncObservableResult{T}.cs b/src/UnityFx.Async/Implementation/Observable/AsyncObservableResult{T}.cs new file mode 100644 index 0000000..709d3c9 --- /dev/null +++ b/src/UnityFx.Async/Implementation/Observable/AsyncObservableResult{T}.cs @@ -0,0 +1,68 @@ +// 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 +{ +#if !NET35 + + internal sealed class AsyncObservableResult : AsyncResult, IObserver + { + #region data + + private readonly IDisposable _subscription; + + #endregion + + #region interface + + internal AsyncObservableResult(IObservable observable) + : base(AsyncOperationStatus.Running) + { + _subscription = observable.Subscribe(this); + } + + #endregion + + #region AsyncResult + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _subscription.Dispose(); + } + + base.Dispose(disposing); + } + + #endregion + + #region IObserver + + void IObserver.OnNext(T value) + { + if (TrySetResult(value, false)) + { + _subscription.Dispose(); + } + } + + void IObserver.OnError(Exception error) + { + if (TrySetException(error, false)) + { + _subscription.Dispose(); + } + } + + void IObserver.OnCompleted() + { + } + + #endregion + } + +#endif +} From 795d8ec19a05f683736e8bb4b810b5d29d1faa1e Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sun, 1 Apr 2018 20:47:53 +0300 Subject: [PATCH 033/128] Added FromTask/FromObservable helpers --- src/UnityFx.Async/Api/Core/AsyncResult.cs | 101 +++++++++++++++++- .../Extensions/AsyncExtensions.Observables.cs | 2 +- .../Api/Extensions/AsyncExtensions.Tasks.cs | 44 +------- 3 files changed, 103 insertions(+), 44 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index ec47147..095251e 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -5,11 +5,13 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; -using System.Runtime.CompilerServices; #if !NET35 using System.Runtime.ExceptionServices; #endif using System.Threading; +#if UNITYFX_SUPPORT_TAP +using System.Threading.Tasks; +#endif namespace UnityFx.Async { @@ -803,6 +805,103 @@ public static AsyncResult FromResult(T result, object asyncState) return new AsyncResult(result, asyncState); } +#if UNITYFX_SUPPORT_TAP + + /// + /// Creates an instance that completes when the specified completes. + /// + /// The source instance. + /// Thrown if the reference is . + /// An that represents the source . + public static AsyncResult FromTask(Task task) + { + if (task == null) + { + throw new ArgumentNullException(nameof(task)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + task.ContinueWith( + t => + { + if (t.IsFaulted) + { + result.SetException(t.Exception); + } + else if (t.IsCanceled) + { + result.SetCanceled(); + } + else + { + result.SetCompleted(); + } + }, + TaskContinuationOptions.ExecuteSynchronously); + + return result; + } + + /// + /// Creates an instance that completes when the specified completes. + /// + /// The source instance. + /// Thrown if the reference is . + /// An that represents the source . + public static AsyncResult FromTask(Task task) + { + if (task == null) + { + throw new ArgumentNullException(nameof(task)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + task.ContinueWith( + t => + { + if (t.IsFaulted) + { + result.SetException(t.Exception); + } + else if (t.IsCanceled) + { + result.SetCanceled(); + } + else + { + result.SetResult(t.Result); + } + }, + TaskContinuationOptions.ExecuteSynchronously); + + return result; + } + +#endif + +#if !NET35 + + /// + /// Creates a instance that can be used to track the source observable. + /// + /// Type of the operation result. + /// The source observable. + /// Thrown if the reference is . + /// Returns an instance that can be used to track the observable. + public static AsyncResult FromObservable(IObservable observable) + { + if (observable == null) + { + throw new ArgumentNullException(nameof(observable)); + } + + return new AsyncObservableResult(observable); + } + +#endif + #endregion #region Delay diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Observables.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Observables.cs index 81ece22..c77e73c 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Observables.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Observables.cs @@ -26,7 +26,7 @@ public static IObservable ToObservable(this IAsyncOperation op) } /// - /// Creates a instance that can be used to track the source observable progress. + /// Creates a instance that can be used to track the source observable. /// /// Type of the operation result. /// The source observable. diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs index 823533d..c0758b0 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs @@ -272,27 +272,7 @@ public static Task ToTask(this IAsyncOperation op) /// An that represents the . public static AsyncResult ToAsync(this Task task) { - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - task.ContinueWith( - t => - { - if (t.IsFaulted) - { - result.SetException(t.Exception); - } - else if (t.IsCanceled) - { - result.SetCanceled(); - } - else - { - result.SetCompleted(); - } - }, - TaskContinuationOptions.ExecuteSynchronously); - - return result; + return AsyncResult.FromTask(task); } /// @@ -302,27 +282,7 @@ public static AsyncResult ToAsync(this Task task) /// An that represents the . public static AsyncResult ToAsync(this Task task) { - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - task.ContinueWith( - t => - { - if (t.IsFaulted) - { - result.SetException(t.Exception); - } - else if (t.IsCanceled) - { - result.SetCanceled(); - } - else - { - result.SetResult(t.Result); - } - }, - TaskContinuationOptions.ExecuteSynchronously); - - return result; + return AsyncResult.FromTask(task); } #endregion From da749f6b5f0fc988945e7c00f6dd913989f8b963 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sun, 1 Apr 2018 21:02:17 +0300 Subject: [PATCH 034/128] CHANGELOG update --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a3c1f1..ebc48ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,20 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/); this proj ### Added - Added `AsyncContinuationOptions` support. +- Added `Promise`-like extensions `Then`, `Catch` and `Finally`. +- Added `FromTask`/`FromObservable` helpers. +- Added `ToAsync` extension method for `IObservable` interface. +- Added `TryAddContinuation`/`RemoveContinuation` methods to `IAsyncOperationEvents` for non-delegate continuations. + +### Changed +- Changed `ContinueWith` extension signatures to match corresponding `Task` methods. +- Changed `IAsyncOperation.Exception` to always return an exception instance if completed with non-success. + +### Fixed +- Fixed exception not initialized properly for canceled operations sometimes. + +### Removed +- Removed `GetAwaiter`/`ConfigureAwait` instance methods from `AsyncResult` to avoid code duplication (extension methods should be used). ----------------------- ## [0.8.2] - 2018-03-28 From 3bd8a27efa39bbd57af9c87dd4804dfa209f6694 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sun, 1 Apr 2018 21:03:17 +0300 Subject: [PATCH 035/128] Minor comment fixes --- src/UnityFx.Async/Api/Core/AsyncResult.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 095251e..a6a933f 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -813,6 +813,7 @@ public static AsyncResult FromResult(T result, object asyncState) /// The source instance. /// Thrown if the reference is . /// An that represents the source . + /// public static AsyncResult FromTask(Task task) { if (task == null) @@ -849,6 +850,7 @@ public static AsyncResult FromTask(Task task) /// The source instance. /// Thrown if the reference is . /// An that represents the source . + /// public static AsyncResult FromTask(Task task) { if (task == null) From af33829ab095c94ebd336332bd99ec21d3a02026 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sun, 1 Apr 2018 21:26:27 +0300 Subject: [PATCH 036/128] README update --- README.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e51d773..ae34d63 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,11 @@ Library is designed as a lightweight [Unity3d](https://unity3d.com)-compatible [ - [Unity3d](https://unity3d.com) compatibility. This includes possibility to yield any operations in coroutines and net35-compilance. ## Getting Started +### Prerequisites +You may need the following software installed in order to build/use the library: +- [Microsoft Visual Studio 2017](https://www.visualstudio.com/vs/community/). +- [Unity3d](https://store.unity.com/). + ### Getting the code You can get the code by cloning the github repository using your preffered git client UI or you can do it from command line as follows: ```cmd @@ -196,11 +201,13 @@ Please see the links below for extended information on the product: - [CHANGELOG](CHANGELOG.md). ## Useful links -- [Microsoft Visual Studio 2017](https://www.visualstudio.com/vs/community/) -- [Unity3d](https://store.unity.com/) -- [Unity Asset Store](https://assetstore.unity.com) -- [Promise pattern](https://en.wikipedia.org/wiki/Futures_and_promises) -- [Promises for Game Development](http://www.what-could-possibly-go-wrong.com/promises-for-game-development/) +- [Task-based Asynchronous Pattern (TAP)](https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap). +- [Asynchronous programming with async and await (C#)](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/). +- [.NET Task reference source](https://referencesource.microsoft.com/#mscorlib/System/threading/Tasks/Task.cs). +- [Promise pattern](https://en.wikipedia.org/wiki/Futures_and_promises). +- [Promises for Game Development](http://www.what-could-possibly-go-wrong.com/promises-for-game-development/). +- [Promises/A+ Spec](https://promisesaplus.com/). +- [Unity coroutines](https://docs.unity3d.com/Manual/Coroutines.html). ## Contributing From 8030f170838faf7694ac280b81bf5ab0376be54b Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sun, 1 Apr 2018 22:53:13 +0300 Subject: [PATCH 037/128] README update with TODO list --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index ae34d63..fbb8369 100644 --- a/README.md +++ b/README.md @@ -189,6 +189,9 @@ Stat | UnityFx.Async | [C-Sharp-Promise](https://github.com/Real-Serious-Games/C Operation data size for 32-bit systems (in bytes) | 28+ | 36+ | 40+ Number of allocations per continuation (`ContinueWith`/`Then`) | 1+ | 5+ | 2+ +## Future work +* Progress reporting (via [IProgress](https://docs.microsoft.com/en-us/dotnet/api/system.iprogress-1)). +* Cancellation support (via [CancellationToken](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken)). ## 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 the like 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 for the best of human kind. From ab41daf673f1e553135316087285db414d83911d Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 2 Apr 2018 16:05:42 +0300 Subject: [PATCH 038/128] Added Unwrap skeleton + renames --- .../AsyncExtensions.Continuations.cs | 40 ++++++-- .../Extensions/AsyncExtensions.Promises.cs | 22 ++--- .../AsyncContinuationResult{T}.cs | 62 ------------ ...ception}.cs => CatchResult{TException}.cs} | 6 +- .../ContinuationResultBase{T}.cs | 90 ++++++++++++++++++ ...ult{T,U}.cs => ContinuationResult{T,U}.cs} | 4 +- .../Continuations/ContinuationResult{T}.cs | 94 +++++++------------ ...ContinuationResult.cs => FinallyResult.cs} | 6 +- ...tinuationResult{T}.cs => ThenResult{T}.cs} | 6 +- .../Continuations/UnwrapResult{T}.cs | 12 +++ 10 files changed, 189 insertions(+), 153 deletions(-) delete mode 100644 src/UnityFx.Async/Implementation/Continuations/AsyncContinuationResult{T}.cs rename src/UnityFx.Async/Implementation/Continuations/{CatchContinuationResult{TException}.cs => CatchResult{TException}.cs} (86%) create mode 100644 src/UnityFx.Async/Implementation/Continuations/ContinuationResultBase{T}.cs rename src/UnityFx.Async/Implementation/Continuations/{AsyncContinuationResult{T,U}.cs => ContinuationResult{T,U}.cs} (86%) rename src/UnityFx.Async/Implementation/Continuations/{FinallyContinuationResult.cs => FinallyResult.cs} (87%) rename src/UnityFx.Async/Implementation/Continuations/{ThenContinuationResult{T}.cs => ThenResult{T}.cs} (92%) create mode 100644 src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs index 793950e..7051ea9 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs @@ -103,7 +103,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action(options, action, null); + var result = new ContinuationResult(options, action, null); op.AddContinuation(result); return result; } @@ -137,7 +137,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action(options, action, userState); + var result = new ContinuationResult(options, action, userState); op.AddContinuation(result); return result; } @@ -169,7 +169,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperatio throw new ArgumentNullException(nameof(action)); } - var result = new AsyncContinuationResult(options, action, null); + var result = new ContinuationResult(options, action, null); op.AddContinuation(result); return result; } @@ -203,7 +203,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperatio throw new ArgumentNullException(nameof(action)); } - var result = new AsyncContinuationResult(options, action, userState); + var result = new ContinuationResult(options, action, userState); op.AddContinuation(result); return result; } @@ -235,7 +235,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation(options, action, null); + var result = new ContinuationResult(options, action, null); op.AddContinuation(result); return result; } @@ -269,7 +269,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation(options, action, userState); + var result = new ContinuationResult(options, action, userState); op.AddContinuation(result); return result; } @@ -301,7 +301,7 @@ public static IAsyncOperation ContinueWith(this throw new ArgumentNullException(nameof(action)); } - var result = new AsyncContinuationResult(options, action, null); + var result = new ContinuationResult(options, action, null); op.AddContinuation(result); return result; } @@ -335,11 +335,35 @@ public static IAsyncOperation ContinueWith(this throw new ArgumentNullException(nameof(action)); } - var result = new AsyncContinuationResult(options, action, userState); + var result = new ContinuationResult(options, action, userState); op.AddContinuation(result); return result; } #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) + { + throw new NotImplementedException(); + } + + /// + /// 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) + { + throw new NotImplementedException(); + } + + #endregion } } diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs index 1f4da0b..2e41a3c 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs @@ -23,7 +23,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba throw new ArgumentNullException(nameof(successCallback)); } - var result = new ThenContinuationResult(successCallback, null); + var result = new ThenResult(successCallback, null); op.AddContinuation(result); return result; } @@ -42,7 +42,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Ac throw new ArgumentNullException(nameof(successCallback)); } - var result = new ThenContinuationResult(successCallback, null); + var result = new ThenResult(successCallback, null); op.AddContinuation(result); return result; } @@ -61,7 +61,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func(successCallback, null); + var result = new ThenResult(successCallback, null); op.AddContinuation(result); return result; } @@ -80,7 +80,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Fu throw new ArgumentNullException(nameof(successCallback)); } - var result = new ThenContinuationResult(successCallback, null); + var result = new ThenResult(successCallback, null); op.AddContinuation(result); return result; } @@ -105,7 +105,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba throw new ArgumentNullException(nameof(errorCallback)); } - var result = new ThenContinuationResult(successCallback, errorCallback); + var result = new ThenResult(successCallback, errorCallback); op.AddContinuation(result); return result; } @@ -130,7 +130,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Ac throw new ArgumentNullException(nameof(errorCallback)); } - var result = new ThenContinuationResult(successCallback, errorCallback); + var result = new ThenResult(successCallback, errorCallback); op.AddContinuation(result); return result; } @@ -155,7 +155,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func(successCallback, errorCallback); + var result = new ThenResult(successCallback, errorCallback); op.AddContinuation(result); return result; } @@ -180,7 +180,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Fu throw new ArgumentNullException(nameof(errorCallback)); } - var result = new ThenContinuationResult(successCallback, errorCallback); + var result = new ThenResult(successCallback, errorCallback); op.AddContinuation(result); return result; } @@ -203,7 +203,7 @@ public static IAsyncOperation Catch(this IAsyncOperation op, Action e throw new ArgumentNullException(nameof(errorCallback)); } - var result = new CatchContinuationResult(errorCallback); + var result = new CatchResult(errorCallback); op.AddContinuation(result); return result; } @@ -222,7 +222,7 @@ public static IAsyncOperation Catch(this IAsyncOperation op, Action< throw new ArgumentNullException(nameof(errorCallback)); } - var result = new CatchContinuationResult(errorCallback); + var result = new CatchResult(errorCallback); op.AddContinuation(result); return result; } @@ -245,7 +245,7 @@ public static IAsyncOperation Finally(this IAsyncOperation op, Action action) throw new ArgumentNullException(nameof(action)); } - var result = new FinallyContinuationResult(action); + var result = new FinallyResult(action); op.AddContinuation(result); return result; } diff --git a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuationResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuationResult{T}.cs deleted file mode 100644 index 4218af0..0000000 --- a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuationResult{T}.cs +++ /dev/null @@ -1,62 +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 -{ - internal class AsyncContinuationResult : ContinuationResult - { - #region data - - private readonly object _continuation; - private readonly object _userState; - - #endregion - - #region interface - - internal AsyncContinuationResult(AsyncContinuationOptions options, object continuation, object userState) - : base(options) - { - _continuation = continuation; - _userState = userState; - } - - #endregion - - #region AsyncContinuation - - protected override T OnInvoke(IAsyncOperation op) - { - var result = default(T); - - switch (_continuation) - { - case Action a: - a.Invoke(op); - break; - - case Func f: - result = f.Invoke(op); - break; - - case Action ao: - ao.Invoke(op, _userState); - break; - - case Func fo: - result = fo.Invoke(op, _userState); - break; - - default: - // Should not get here. - throw new InvalidOperationException(); - } - - return result; - } - - #endregion - } -} diff --git a/src/UnityFx.Async/Implementation/Continuations/CatchContinuationResult{TException}.cs b/src/UnityFx.Async/Implementation/Continuations/CatchResult{TException}.cs similarity index 86% rename from src/UnityFx.Async/Implementation/Continuations/CatchContinuationResult{TException}.cs rename to src/UnityFx.Async/Implementation/Continuations/CatchResult{TException}.cs index 2508399..2341850 100644 --- a/src/UnityFx.Async/Implementation/Continuations/CatchContinuationResult{TException}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/CatchResult{TException}.cs @@ -6,7 +6,7 @@ namespace UnityFx.Async { - internal class CatchContinuationResult : AsyncResult, IAsyncContinuation where TException : Exception + internal class CatchResult : AsyncResult, IAsyncContinuation where TException : Exception { #region data @@ -20,7 +20,7 @@ internal class CatchContinuationResult : AsyncResult, IAsyncContinua #region interface - public CatchContinuationResult(Action errorCallback) + public CatchResult(Action errorCallback) : base(AsyncOperationStatus.Running) { _syncContext = SynchronizationContext.Current; @@ -49,7 +49,7 @@ public void Invoke(IAsyncOperation op) { _postCallback = args => { - var c = args as CatchContinuationResult; + var c = args as CatchResult; c.InvokeErrorCallback(c._op, false); }; } diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinuationResultBase{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinuationResultBase{T}.cs new file mode 100644 index 0000000..98f1be4 --- /dev/null +++ b/src/UnityFx.Async/Implementation/Continuations/ContinuationResultBase{T}.cs @@ -0,0 +1,90 @@ +// 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 +{ + internal abstract class ContinuationResultBase : AsyncResult, IAsyncContinuation + { + #region data + + private static SendOrPostCallback _postCallback; + + private readonly SynchronizationContext _syncContext; + private readonly AsyncContinuationOptions _options; + private IAsyncOperation _op; + + #endregion + + #region interface + + protected ContinuationResultBase(AsyncContinuationOptions options) + : base(AsyncOperationStatus.Running) + { + if ((options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0) + { + _syncContext = SynchronizationContext.Current; + } + + _options = options; + } + + protected abstract T OnInvoke(IAsyncOperation op); + + #endregion + + #region IAsyncContinuation + + public void Invoke(IAsyncOperation op) + { + if (AsyncContinuation.CanInvoke(op, _options)) + { + if (_syncContext == null || _syncContext == SynchronizationContext.Current) + { + try + { + TrySetResult(OnInvoke(op), op.CompletedSynchronously); + } + catch (Exception e) + { + TrySetException(e, op.CompletedSynchronously); + } + } + else + { + _op = op; + + if (_postCallback == null) + { + _postCallback = args => + { + var c = args as ContinuationResultBase; + + try + { + c.TrySetResult(c.OnInvoke(c._op), false); + } + catch (Exception e) + { + c.TrySetException(e, false); + } + }; + } + + _syncContext.Post(_postCallback, this); + } + } + else + { + TrySetCanceled(op.CompletedSynchronously); + } + } + + #endregion + + #region implementation + #endregion + } +} diff --git a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuationResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T,U}.cs similarity index 86% rename from src/UnityFx.Async/Implementation/Continuations/AsyncContinuationResult{T,U}.cs rename to src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T,U}.cs index 72566eb..bd9bf86 100644 --- a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuationResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T,U}.cs @@ -5,7 +5,7 @@ namespace UnityFx.Async { - internal class AsyncContinuationResult : ContinuationResult + internal class ContinuationResult : ContinuationResultBase { #region data @@ -16,7 +16,7 @@ internal class AsyncContinuationResult : ContinuationResult #region interface - internal AsyncContinuationResult(AsyncContinuationOptions options, object continuation, object userState) + internal ContinuationResult(AsyncContinuationOptions options, object continuation, object userState) : base(options) { _continuation = continuation; diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs index 9b9d025..4b7ea19 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs @@ -2,89 +2,61 @@ // 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 { - internal abstract class ContinuationResult : AsyncResult, IAsyncContinuation + internal class ContinuationResult : ContinuationResultBase { #region data - private static SendOrPostCallback _postCallback; - - private readonly SynchronizationContext _syncContext; - private readonly AsyncContinuationOptions _options; - private IAsyncOperation _op; + private readonly object _continuation; + private readonly object _userState; #endregion #region interface - protected ContinuationResult(AsyncContinuationOptions options) - : base(AsyncOperationStatus.Running) + internal ContinuationResult(AsyncContinuationOptions options, object continuation, object userState) + : base(options) { - if ((options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0) - { - _syncContext = SynchronizationContext.Current; - } - - _options = options; + _continuation = continuation; + _userState = userState; } - protected abstract T OnInvoke(IAsyncOperation op); - #endregion - #region IAsyncContinuation + #region AsyncContinuation - public void Invoke(IAsyncOperation op) + protected override T OnInvoke(IAsyncOperation op) { - if (AsyncContinuation.CanInvoke(op, _options)) - { - if (_syncContext == null || _syncContext == SynchronizationContext.Current) - { - try - { - TrySetResult(OnInvoke(op), op.CompletedSynchronously); - } - catch (Exception e) - { - TrySetException(e, op.CompletedSynchronously); - } - } - else - { - _op = op; - - if (_postCallback == null) - { - _postCallback = args => - { - var c = args as ContinuationResult; - - try - { - c.TrySetResult(c.OnInvoke(c._op), false); - } - catch (Exception e) - { - c.TrySetException(e, false); - } - }; - } - - _syncContext.Post(_postCallback, this); - } - } - else + var result = default(T); + + switch (_continuation) { - TrySetCanceled(op.CompletedSynchronously); + case Action a: + a.Invoke(op); + break; + + case Func f: + result = f.Invoke(op); + break; + + case Action ao: + ao.Invoke(op, _userState); + break; + + case Func fo: + result = fo.Invoke(op, _userState); + break; + + default: + // Should not get here. + throw new InvalidOperationException(); } - } - #endregion + return result; + } - #region implementation #endregion } } diff --git a/src/UnityFx.Async/Implementation/Continuations/FinallyContinuationResult.cs b/src/UnityFx.Async/Implementation/Continuations/FinallyResult.cs similarity index 87% rename from src/UnityFx.Async/Implementation/Continuations/FinallyContinuationResult.cs rename to src/UnityFx.Async/Implementation/Continuations/FinallyResult.cs index 1909a64..d381648 100644 --- a/src/UnityFx.Async/Implementation/Continuations/FinallyContinuationResult.cs +++ b/src/UnityFx.Async/Implementation/Continuations/FinallyResult.cs @@ -6,7 +6,7 @@ namespace UnityFx.Async { - internal class FinallyContinuationResult : AsyncResult, IAsyncContinuation + internal class FinallyResult : AsyncResult, IAsyncContinuation { #region data @@ -20,7 +20,7 @@ internal class FinallyContinuationResult : AsyncResult, IAsyncContinuation #region interface - public FinallyContinuationResult(Action action) + public FinallyResult(Action action) : base(AsyncOperationStatus.Running) { _syncContext = SynchronizationContext.Current; @@ -53,7 +53,7 @@ public void Invoke(IAsyncOperation op) { _postCallback = args => { - var c = args as FinallyContinuationResult; + var c = args as FinallyResult; try { diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenContinuationResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T}.cs similarity index 92% rename from src/UnityFx.Async/Implementation/Continuations/ThenContinuationResult{T}.cs rename to src/UnityFx.Async/Implementation/Continuations/ThenResult{T}.cs index aa2d5ff..444c023 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ThenContinuationResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T}.cs @@ -6,7 +6,7 @@ namespace UnityFx.Async { - internal class ThenContinuationResult : AsyncResult, IAsyncContinuation + internal class ThenResult : AsyncResult, IAsyncContinuation { #region data @@ -21,7 +21,7 @@ internal class ThenContinuationResult : AsyncResult, IAsyncContinuation #region interface - public ThenContinuationResult(object successCallback, Action errorCallback) + public ThenResult(object successCallback, Action errorCallback) : base(AsyncOperationStatus.Running) { _syncContext = SynchronizationContext.Current; @@ -84,7 +84,7 @@ public void Invoke(IAsyncOperation op) { _postCallback = args => { - var c = args as ThenContinuationResult; + var c = args as ThenResult; c.InvokeCallbacks(c._op, false); }; } diff --git a/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs new file mode 100644 index 0000000..2b27b02 --- /dev/null +++ b/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs @@ -0,0 +1,12 @@ +// 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 +{ + internal class UnwrapResult : AsyncResult + { + } +} From 91add4a2625cb7e38554feace8613f35c07ba540 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 2 Apr 2018 16:32:04 +0300 Subject: [PATCH 039/128] Unwrap pilot implementation --- .../Core/AsyncCompletionSource{TResult}.cs | 28 ------ .../Api/Core/AsyncResult{TResult}.cs | 28 ++++++ .../AsyncExtensions.Continuations.cs | 4 +- .../Continuations/UnwrapResult{T}.cs | 97 ++++++++++++++++++- 4 files changed, 126 insertions(+), 31 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs b/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs index dfbfc39..87d6cb1 100644 --- a/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs @@ -292,34 +292,6 @@ public void SetResult(TResult result, bool completedSynchronously) /// public new bool TrySetResult(TResult result, bool completedSynchronously) => base.TrySetResult(result, completedSynchronously); - /// - /// Copies state of the specified operation. - /// - internal void CopyCompletionState(IAsyncOperation patternOp, bool completedSynchronously) - { - if (!TryCopyCompletionState(patternOp, completedSynchronously)) - { - throw new InvalidOperationException(); - } - } - - /// - /// Attemts to copy state of the specified operation. - /// - internal bool TryCopyCompletionState(IAsyncOperation patternOp, bool completedSynchronously) - { - if (patternOp.IsCompletedSuccessfully) - { - return base.TrySetResult(patternOp.Result, completedSynchronously); - } - else if (patternOp.IsFaulted || patternOp.IsCanceled) - { - return base.TrySetException(patternOp.Exception, completedSynchronously); - } - - return false; - } - #endregion #region IAsyncCompletionSource diff --git a/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs b/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs index ada0d5d..cf6fa53 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs @@ -131,6 +131,34 @@ protected internal bool TrySetResult(TResult result, bool completedSynchronously return false; } + /// + /// Copies state of the specified operation. + /// + internal void CopyCompletionState(IAsyncOperation patternOp, bool completedSynchronously) + { + if (!TryCopyCompletionState(patternOp, completedSynchronously)) + { + throw new InvalidOperationException(); + } + } + + /// + /// Attemts to copy state of the specified operation. + /// + internal bool TryCopyCompletionState(IAsyncOperation patternOp, bool completedSynchronously) + { + if (patternOp.IsCompletedSuccessfully) + { + return TrySetResult(patternOp.Result, completedSynchronously); + } + else if (patternOp.IsFaulted || patternOp.IsCanceled) + { + return TrySetException(patternOp.Exception, completedSynchronously); + } + + return false; + } + #endregion #region IAsyncOperation diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs index 7051ea9..aebdde5 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs @@ -351,7 +351,7 @@ public static IAsyncOperation ContinueWith(this /// The unwrapped operation. public static IAsyncOperation Unwrap(this IAsyncOperation op) { - throw new NotImplementedException(); + return new UnwrapResult(op); } /// @@ -361,7 +361,7 @@ public static IAsyncOperation Unwrap(this IAsyncOperation op) /// The unwrapped operation. public static IAsyncOperation Unwrap(this IAsyncOperation> op) { - throw new NotImplementedException(); + return new UnwrapResult(op); } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs index 2b27b02..09248ce 100644 --- a/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs @@ -6,7 +6,102 @@ namespace UnityFx.Async { - internal class UnwrapResult : AsyncResult + internal class UnwrapResult : AsyncResult, IAsyncContinuation { + #region data + + private enum State + { + WaitingForOuterOperation, + WaitingForInnerOperation, + Done + } + + private State _state; + + #endregion + + #region interface + + public UnwrapResult(IAsyncOperation outerOp) + { + outerOp.AddContinuation(this); + } + + #endregion + + #region IAsyncContinuation + + public void Invoke(IAsyncOperation op) + { + if (_state == State.WaitingForOuterOperation) + { + if (op.IsCompletedSuccessfully) + { + switch (op) + { + case IAsyncOperation> innerOp1: + ProcessInnerOperation(innerOp1); + break; + + case IAsyncOperation innerOp2: + ProcessInnerOperation(innerOp2); + break; + + default: + ProcessInnerOperation(null); + break; + } + } + else + { + TrySetException(op.Exception, false); + } + + _state = State.WaitingForInnerOperation; + } + else if (_state == State.WaitingForInnerOperation) + { + if (op.IsCompletedSuccessfully) + { + if (op is IAsyncOperation innerOp) + { + TrySetResult(innerOp.Result, false); + } + else + { + TrySetCompleted(false); + } + } + else + { + TrySetException(op.Exception, false); + } + + _state = State.Done; + } + else + { + // Should not get here. + } + } + + #endregion + + #region implementation + + private void ProcessInnerOperation(IAsyncOperation innerOp) + { + if (innerOp == null) + { + TrySetCanceled(false); + } + else + { + innerOp.AddContinuation(this); + } + } + + #endregion } } From 9de1bb5ca1695b50dfe06ffb06455fdf5eb85c6e Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 2 Apr 2018 18:34:38 +0300 Subject: [PATCH 040/128] Rebind implemented --- .../Extensions/AsyncExtensions.Promises.cs | 177 ++++++++++++++++++ .../Continuations/FinallyResult.cs | 2 +- .../Continuations/RebindResult{T,U}.cs | 95 ++++++++++ 3 files changed, 273 insertions(+), 1 deletion(-) create mode 100644 src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs index 2e41a3c..dd4c4ba 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See the LICENSE.md file in the project root for more information. using System; +using System.Collections.Generic; namespace UnityFx.Async { @@ -187,6 +188,182 @@ public static IAsyncOperation Then(this IAsyncOperation op, Fu #endregion + #region ThenAll + + /// + /// Schedules a callback to be executed after the operation has succeeded. + /// + /// An operation to be continued. + /// The callback to be executed when the operation has completed. + /// Returns a continuation operation that completes after both source operation and the callback has completed. + public static IAsyncOperation ThenAll(this IAsyncOperation op, Func> successCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + throw new NotImplementedException(); + } + + /// + /// Schedules a callback to be executed after the operation has succeeded. + /// + /// An operation to be continued. + /// The callback to be executed when the operation has completed. + /// Returns a continuation operation that completes after both source operation and the callback has completed. + public static IAsyncOperation ThenAll(this IAsyncOperation op, Func> successCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + throw new NotImplementedException(); + } + + /// + /// Schedules a callback to be executed after the operation has succeeded. + /// + /// An operation to be continued. + /// The callback to be executed when the operation has completed. + /// Returns a continuation operation that completes after both source operation and the callback has completed. + public static IAsyncOperation ThenAll(this IAsyncOperation op, Func>> successCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + throw new NotImplementedException(); + } + + /// + /// Schedules a callback to be executed after the operation has succeeded. + /// + /// An operation to be continued. + /// The callback to be executed when the operation has completed. + /// Returns a continuation operation that completes after both source operation and the callback has completed. + public static IAsyncOperation ThenAll(this IAsyncOperation op, Func>> successCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + throw new NotImplementedException(); + } + + #endregion + + #region ThenAny + + /// + /// Schedules a callback to be executed after the operation has succeeded. + /// + /// An operation to be continued. + /// The callback to be executed when the operation has completed. + /// Returns a continuation operation that completes after both source operation and the callback has completed. + public static IAsyncOperation ThenAny(this IAsyncOperation op, Func> successCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + throw new NotImplementedException(); + } + + /// + /// Schedules a callback to be executed after the operation has succeeded. + /// + /// An operation to be continued. + /// The callback to be executed when the operation has completed. + /// Returns a continuation operation that completes after both source operation and the callback has completed. + public static IAsyncOperation ThenAny(this IAsyncOperation op, Func> successCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + throw new NotImplementedException(); + } + + /// + /// Schedules a callback to be executed after the operation has succeeded. + /// + /// An operation to be continued. + /// The callback to be executed when the operation has completed. + /// Returns a continuation operation that completes after both source operation and the callback has completed. + public static IAsyncOperation ThenAny(this IAsyncOperation op, Func>> successCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + throw new NotImplementedException(); + } + + /// + /// Schedules a callback to be executed after the operation has succeeded. + /// + /// An operation to be continued. + /// The callback to be executed when the operation has completed. + /// Returns a continuation operation that completes after both source operation and the callback has completed. + public static IAsyncOperation ThenAny(this IAsyncOperation op, Func>> successCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + throw new NotImplementedException(); + } + + #endregion + + #region Rebind + + /// + /// Schedules a callback to be executed after the operation has succeeded. + /// + /// An operation to be continued. + /// The callback to be executed when the operation has completed. + /// Returns a continuation operation that completes after both source operation and the callback has completed. + public static IAsyncOperation Rebind(this IAsyncOperation op, Func successCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + var result = new RebindResult(successCallback); + op.AddContinuation(result); + return result; + } + + /// + /// Schedules a callback to be executed after the operation has succeeded. + /// + /// An operation to be continued. + /// The callback to be executed when the operation has completed. + /// Returns a continuation operation that completes after both source operation and the callback has completed. + public static IAsyncOperation Rebind(this IAsyncOperation op, Func successCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + var result = new RebindResult(successCallback); + op.AddContinuation(result); + return result; + } + + #endregion + #region Catch /// diff --git a/src/UnityFx.Async/Implementation/Continuations/FinallyResult.cs b/src/UnityFx.Async/Implementation/Continuations/FinallyResult.cs index d381648..3c87cae 100644 --- a/src/UnityFx.Async/Implementation/Continuations/FinallyResult.cs +++ b/src/UnityFx.Async/Implementation/Continuations/FinallyResult.cs @@ -62,7 +62,7 @@ public void Invoke(IAsyncOperation op) } catch (Exception e) { - TrySetException(e, false); + c.TrySetException(e, false); } }; } diff --git a/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs new file mode 100644 index 0000000..c4da383 --- /dev/null +++ b/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.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; + +namespace UnityFx.Async +{ + internal class RebindResult : AsyncResult, IAsyncContinuation + { + #region data + + private static SendOrPostCallback _postCallback; + + private readonly SynchronizationContext _syncContext; + private readonly object _continuation; + private IAsyncOperation _op; + + #endregion + + #region interface + + public RebindResult(object action) + : base(AsyncOperationStatus.Running) + { + _syncContext = SynchronizationContext.Current; + _continuation = action; + } + + #endregion + + #region IAsyncContinuation + + public void Invoke(IAsyncOperation op) + { + if (op.IsCompletedSuccessfully) + { + if (_syncContext == null || _syncContext == SynchronizationContext.Current) + { + InvokeCallback(op, op.CompletedSynchronously); + } + else + { + _op = op; + + if (_postCallback == null) + { + _postCallback = args => + { + var c = args as RebindResult; + c.InvokeCallback(c._op, false); + }; + } + + _syncContext.Post(_postCallback, this); + } + } + else + { + TrySetException(op.Exception, op.CompletedSynchronously); + } + } + + #endregion + + #region implementation + + private void InvokeCallback(IAsyncOperation op, bool completedSynchronously) + { + try + { + switch (_continuation) + { + case Func f1: + TrySetResult(f1(), completedSynchronously); + break; + + case Func f2: + TrySetResult(f2((op as IAsyncOperation).Result), completedSynchronously); + break; + + default: + TrySetCanceled(completedSynchronously); + break; + } + } + catch (Exception e) + { + TrySetException(e, completedSynchronously); + } + } + + #endregion + } +} From 5e78a8dfc1ff4f7ea8520f816da61ddb98d5700f Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 2 Apr 2018 18:45:28 +0300 Subject: [PATCH 041/128] Changed IAsyncContinuation.Invoke signature --- src/UnityFx.Async/Api/Core/AsyncResult.cs | 2 +- .../Extensions/AsyncExtensions.Continuations.cs | 2 +- .../Api/Interfaces/IAsyncContinuation.cs | 3 ++- .../Continuations/AsyncContinuation.cs | 2 +- .../Continuations/CatchResult{TException}.cs | 6 +++--- .../Continuations/ContinuationResultBase{T}.cs | 8 ++++---- .../Implementation/Continuations/FinallyResult.cs | 6 +++--- .../Continuations/RebindResult{T,U}.cs | 6 +++--- .../Implementation/Continuations/ThenResult{T}.cs | 6 +++--- .../Continuations/UnwrapResult{T}.cs | 14 +++++++------- 10 files changed, 28 insertions(+), 27 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index a6a933f..46b19f6 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -1870,7 +1870,7 @@ private static void InvokeContinuation(IAsyncOperation op, object continuation) { if (continuation is IAsyncContinuation c) { - c.Invoke(op); + c.Invoke(op, false); } else { diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs index aebdde5..2c6159a 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs @@ -23,7 +23,7 @@ public static void AddContinuation(this IAsyncOperation op, IAsyncContinuation c { if (!op.TryAddContinuation(continuation)) { - continuation.Invoke(op); + continuation.Invoke(op, true); } } diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs index 2c6fbb9..5c8dd2a 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs @@ -65,6 +65,7 @@ public interface IAsyncContinuation /// Starts the continuation. /// /// The completed antecedent operation. - void Invoke(IAsyncOperation op); + /// Value of the property. + void Invoke(IAsyncOperation op, bool completedSynchronously); } } diff --git a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs index 753e219..8eaf38f 100644 --- a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs +++ b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs @@ -110,7 +110,7 @@ internal static void InvokeTaskContinuation(IAsyncOperation op, TaskComple #region IAsyncContinuation - public void Invoke(IAsyncOperation op) + public void Invoke(IAsyncOperation op, bool completedSynchronously) { if (_syncContext == null || _syncContext == SynchronizationContext.Current) { diff --git a/src/UnityFx.Async/Implementation/Continuations/CatchResult{TException}.cs b/src/UnityFx.Async/Implementation/Continuations/CatchResult{TException}.cs index 2341850..a62b83e 100644 --- a/src/UnityFx.Async/Implementation/Continuations/CatchResult{TException}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/CatchResult{TException}.cs @@ -31,15 +31,15 @@ public CatchResult(Action errorCallback) #region IAsyncContinuation - public void Invoke(IAsyncOperation op) + public void Invoke(IAsyncOperation op, bool completedSynchronously) { if (op.IsCompletedSuccessfully || !(op.Exception.InnerException is TException)) { - TrySetCompleted(op.CompletedSynchronously); + TrySetCompleted(completedSynchronously); } else if (_syncContext == null || _syncContext == SynchronizationContext.Current) { - InvokeErrorCallback(op, op.CompletedSynchronously); + InvokeErrorCallback(op, completedSynchronously); } else { diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinuationResultBase{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinuationResultBase{T}.cs index 98f1be4..cef330b 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinuationResultBase{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinuationResultBase{T}.cs @@ -37,7 +37,7 @@ protected ContinuationResultBase(AsyncContinuationOptions options) #region IAsyncContinuation - public void Invoke(IAsyncOperation op) + public void Invoke(IAsyncOperation op, bool completedSynchronously) { if (AsyncContinuation.CanInvoke(op, _options)) { @@ -45,11 +45,11 @@ public void Invoke(IAsyncOperation op) { try { - TrySetResult(OnInvoke(op), op.CompletedSynchronously); + TrySetResult(OnInvoke(op), completedSynchronously); } catch (Exception e) { - TrySetException(e, op.CompletedSynchronously); + TrySetException(e, completedSynchronously); } } else @@ -78,7 +78,7 @@ public void Invoke(IAsyncOperation op) } else { - TrySetCanceled(op.CompletedSynchronously); + TrySetCanceled(completedSynchronously); } } diff --git a/src/UnityFx.Async/Implementation/Continuations/FinallyResult.cs b/src/UnityFx.Async/Implementation/Continuations/FinallyResult.cs index 3c87cae..2d0b506 100644 --- a/src/UnityFx.Async/Implementation/Continuations/FinallyResult.cs +++ b/src/UnityFx.Async/Implementation/Continuations/FinallyResult.cs @@ -31,18 +31,18 @@ public FinallyResult(Action action) #region IAsyncContinuation - public void Invoke(IAsyncOperation op) + public void Invoke(IAsyncOperation op, bool completedSynchronously) { if (_syncContext == null || _syncContext == SynchronizationContext.Current) { try { _continuation(); - TrySetCompleted(op.CompletedSynchronously); + TrySetCompleted(completedSynchronously); } catch (Exception e) { - TrySetException(e, op.CompletedSynchronously); + TrySetException(e, completedSynchronously); } } else diff --git a/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs index c4da383..3c6fb97 100644 --- a/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs @@ -31,13 +31,13 @@ public RebindResult(object action) #region IAsyncContinuation - public void Invoke(IAsyncOperation op) + public void Invoke(IAsyncOperation op, bool completedSynchronously) { if (op.IsCompletedSuccessfully) { if (_syncContext == null || _syncContext == SynchronizationContext.Current) { - InvokeCallback(op, op.CompletedSynchronously); + InvokeCallback(op, completedSynchronously); } else { @@ -57,7 +57,7 @@ public void Invoke(IAsyncOperation op) } else { - TrySetException(op.Exception, op.CompletedSynchronously); + TrySetException(op.Exception, completedSynchronously); } } diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T}.cs index 444c023..99a76dd 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T}.cs @@ -70,11 +70,11 @@ protected void InvokeErrorCallback(IAsyncOperation op) #region IAsyncContinuation - public void Invoke(IAsyncOperation op) + public void Invoke(IAsyncOperation op, bool completedSynchronously) { if (_syncContext == null || _syncContext == SynchronizationContext.Current) { - InvokeCallbacks(op, op.CompletedSynchronously); + InvokeCallbacks(op, completedSynchronously); } else if (op.IsCompletedSuccessfully || _errorCallback != null) { @@ -93,7 +93,7 @@ public void Invoke(IAsyncOperation op) } else { - TrySetException(op.Exception, op.CompletedSynchronously); + TrySetException(op.Exception, completedSynchronously); } } diff --git a/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs index 09248ce..d7055b2 100644 --- a/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs @@ -32,7 +32,7 @@ public UnwrapResult(IAsyncOperation outerOp) #region IAsyncContinuation - public void Invoke(IAsyncOperation op) + public void Invoke(IAsyncOperation op, bool completedSynchronously) { if (_state == State.WaitingForOuterOperation) { @@ -41,21 +41,21 @@ public void Invoke(IAsyncOperation op) switch (op) { case IAsyncOperation> innerOp1: - ProcessInnerOperation(innerOp1); + ProcessInnerOperation(innerOp1, completedSynchronously); break; case IAsyncOperation innerOp2: - ProcessInnerOperation(innerOp2); + ProcessInnerOperation(innerOp2, completedSynchronously); break; default: - ProcessInnerOperation(null); + ProcessInnerOperation(null, completedSynchronously); break; } } else { - TrySetException(op.Exception, false); + TrySetException(op.Exception, completedSynchronously); } _state = State.WaitingForInnerOperation; @@ -90,11 +90,11 @@ public void Invoke(IAsyncOperation op) #region implementation - private void ProcessInnerOperation(IAsyncOperation innerOp) + private void ProcessInnerOperation(IAsyncOperation innerOp, bool completedSynchronously) { if (innerOp == null) { - TrySetCanceled(false); + TrySetCanceled(completedSynchronously); } else { From bf043e93f33dd5740b38afee69b45ba6daca48ac Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 2 Apr 2018 19:02:10 +0300 Subject: [PATCH 042/128] Added new Then overloads --- .../Extensions/AsyncExtensions.Promises.cs | 54 ++++++++++++++++--- .../{ThenResult{T}.cs => ThenResult{T,U}.cs} | 20 ++++--- 2 files changed, 60 insertions(+), 14 deletions(-) rename src/UnityFx.Async/Implementation/Continuations/{ThenResult{T}.cs => ThenResult{T,U}.cs} (76%) diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs index dd4c4ba..3d0acae 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs @@ -24,7 +24,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba throw new ArgumentNullException(nameof(successCallback)); } - var result = new ThenResult(successCallback, null); + var result = new ThenResult(successCallback, null); op.AddContinuation(result); return result; } @@ -43,7 +43,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Ac throw new ArgumentNullException(nameof(successCallback)); } - var result = new ThenResult(successCallback, null); + var result = new ThenResult(successCallback, null); op.AddContinuation(result); return result; } @@ -62,7 +62,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func(successCallback, null); + var result = new ThenResult(successCallback, null); op.AddContinuation(result); return result; } @@ -81,7 +81,45 @@ public static IAsyncOperation Then(this IAsyncOperation op, Fu throw new ArgumentNullException(nameof(successCallback)); } - var result = new ThenResult(successCallback, null); + var result = new ThenResult(successCallback, null); + op.AddContinuation(result); + return result; + } + + /// + /// Schedules a callback to be executed after the operation has succeeded. + /// + /// An operation to be continued. + /// The callback to be executed when the operation has completed. + /// Returns a continuation operation that completes after both source operation and the operation returned by has completed. + /// + public static IAsyncOperation Then(this IAsyncOperation op, Func> successCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + var result = new ThenResult(successCallback, null); + op.AddContinuation(result); + return result; + } + + /// + /// Schedules a callback to be executed after the operation has succeeded. + /// + /// An operation to be continued. + /// The callback to be executed when the operation has completed. + /// Returns a continuation operation that completes after both source operation and the operation returned by has completed. + /// + public static IAsyncOperation Then(this IAsyncOperation op, Func> successCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + var result = new ThenResult(successCallback, null); op.AddContinuation(result); return result; } @@ -106,7 +144,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba throw new ArgumentNullException(nameof(errorCallback)); } - var result = new ThenResult(successCallback, errorCallback); + var result = new ThenResult(successCallback, errorCallback); op.AddContinuation(result); return result; } @@ -131,7 +169,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Ac throw new ArgumentNullException(nameof(errorCallback)); } - var result = new ThenResult(successCallback, errorCallback); + var result = new ThenResult(successCallback, errorCallback); op.AddContinuation(result); return result; } @@ -156,7 +194,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func(successCallback, errorCallback); + var result = new ThenResult(successCallback, errorCallback); op.AddContinuation(result); return result; } @@ -181,7 +219,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Fu throw new ArgumentNullException(nameof(errorCallback)); } - var result = new ThenResult(successCallback, errorCallback); + var result = new ThenResult(successCallback, errorCallback); op.AddContinuation(result); return result; } diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs similarity index 76% rename from src/UnityFx.Async/Implementation/Continuations/ThenResult{T}.cs rename to src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs index 99a76dd..a780f2d 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs @@ -6,7 +6,7 @@ namespace UnityFx.Async { - internal class ThenResult : AsyncResult, IAsyncContinuation + internal class ThenResult : AsyncResult, IAsyncContinuation { #region data @@ -45,12 +45,20 @@ protected bool InvokeSuccessCallback(IAsyncOperation op) result = true; break; - case Func f: - f.Invoke().AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); + case Func> f3: + f3().AddCompletionCallback(op2 => TryCopyCompletionState(op2 as IAsyncOperation, false), null); break; - case Func f1: - f1.Invoke((op as IAsyncOperation).Result).AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); + case Func f1: + f1().AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); + break; + + case Func> f4: + f4((op as IAsyncOperation).Result).AddCompletionCallback(op2 => TryCopyCompletionState(op2 as IAsyncOperation, false), null); + break; + + case Func f2: + f2((op as IAsyncOperation).Result).AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); break; default: @@ -84,7 +92,7 @@ public void Invoke(IAsyncOperation op, bool completedSynchronously) { _postCallback = args => { - var c = args as ThenResult; + var c = args as ThenResult; c.InvokeCallbacks(c._op, false); }; } From 489769bd5f0ba1d54b5e175a1989cc3dfb761e79 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 2 Apr 2018 19:05:57 +0300 Subject: [PATCH 043/128] CHANGELOG update --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebc48ab..004fb7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,11 +4,12 @@ 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/). ----------------------- -## [Unreleased] +## [0.9.0] - Unreleased ### Added - Added `AsyncContinuationOptions` support. -- Added `Promise`-like extensions `Then`, `Catch` and `Finally`. +- Added `Promise`-like extensions `Then`, `ThenAll`, `ThenAny`, `Rebind`, `Catch` and `Finally`. +- Added `Unwrap` extension methods. - Added `FromTask`/`FromObservable` helpers. - Added `ToAsync` extension method for `IObservable` interface. - Added `TryAddContinuation`/`RemoveContinuation` methods to `IAsyncOperationEvents` for non-delegate continuations. From 27ced45cff404e12dc6fdd2f6c92c15e0f13a588 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 2 Apr 2018 20:08:09 +0300 Subject: [PATCH 044/128] README update --- README.md | 188 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 107 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index fbb8369..0f1f663 100644 --- a/README.md +++ b/README.md @@ -44,19 +44,38 @@ TODO ### Promises to the Rescue TODO -## Code Example -Typical use-case of the library is wrapping [Unity3d](https://unity3d.com) web requests in [Task-based Asynchronous Pattern](https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming) manner: +## Using the library +Reference the DLL and import the namespace: ```csharp -public IAsyncOperation LoadTextureAsync(string textureUrl) +using UnityFx.Async; +``` +Create an operation instance like this: +```csharp +var op = new AsyncCompletionSource(); +``` +The type of the operation should reflect its result type. In this case we have created a special kind of operations - a completion source, that incapsulated both producer and consumer operation interfaces (consumer side is represented via `IAsyncOperation` / `IAsyncOperation` interfaces and producer side is `IAsyncCompletionSource` / `IAsyncCompletionSource`, `AsyncCompletionSource` implements both of the interfaces). + +Upon completion of the asynchronous operation notify its users: +```csharp +op.SetResult(resultValue); +``` +Alternatively, if the operation has failed: +```csharp +op.SetException(ex); +``` + +To see it in context, here is an example of a function that downloads text from URL using [UnityWebRequest](https://docs.unity3d.com/ScriptReference/Networking.UnityWebRequest.html): +```csharp +public IAsyncOperation DownloadTextAsync(string url) { - var result = new AsyncCompletionSource(); - StartCoroutine(LoadTextureInternal(result, textureUrl)); - return result.Operation; + var result = new AsyncCompletionSource(); + StartCoroutine(DownloadTextInternal(result, url)); + return result; } -private IEnumerator LoadTextureInternal(IAsyncCompletionSource op, string textureUrl) +private IEnumerator DownloadTextInternal(IAsyncCompletionSource op, string url) { - var www = UnityWebRequestTexture.GetTexture(textureUrl); + var www = UnityWebRequest.Get(url); yield return www.Send(); if (www.isNetworkError || www.isHttpError) @@ -65,100 +84,107 @@ private IEnumerator LoadTextureInternal(IAsyncCompletionSource op, st } else { - op.SetResult(((DownloadHandlerTexture)www.downloadHandler).texture); + op.SetResult(www.downloadHandler.text); } } ``` -Once that is done we can use `LoadTextureAsync()` result in many ways. For example we can yield it in Unity coroutine to wait for its completion: + +### Waiting for an operation to complete +The simpliest way to get notified of an operation completion is registering a completion handler to be invoked when the operation succeeds (the JS promise-like way): ```csharp -IEnumerator WaitForLoadOperationInCoroutine(string textureUrl) -{ - var op = LoadTextureAsync(textureUrl); - yield return op; +DownloadTextAsync("http://www.google.com").Then(text => Debug.Log(text)); +``` +The above code downloads content of Google's front page and prints it to Unity console. To make this example closer to real life applications let's add simple error handling code to it: +```csharp +DownloadTextAsync("http://www.google.com") + .Then(text => Debug.Log(text)) + .Catch(e => Debug.LogException(e)); +``` +One can also yield the operation in Unity coroutine: +```csharp +var op = DownloadTextAsync("http://www.google.com"); +yield return op; - if (op.IsCompletedSuccessfully) - { - Debug.Log("Yay!"); - } - else if (op.IsFaulted) - { - Debug.LogException(op.Exception); - } - else if (op.IsCanceled) - { - Debug.LogWarning("The operation was canceled."); - } +if (op.IsCompletedSuccessfully) +{ + Debug.Log(op.Result); +} +else if (op.IsFaulted) +{ + Debug.LogException(op.Exception); +} +else if (op.IsCanceled) +{ + Debug.LogWarning("The operation was canceled."); } ``` -With Unity 2017+ and .NET 4.6 scripting backend it can be used just like a [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task). The await continuation is scheduled on the captured [SynchronizationContext](https://docs.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext) (if any): +With Unity 2017+ and .NET 4.6 it can be used just like a [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task). The await continuation is scheduled on the captured [SynchronizationContext](https://docs.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext) (if any): ```csharp -async Task WaitForLoadOperationWithAwait(string textureUrl) +try { - try - { - var texture = await LoadTextureAsync(textureUrl); - Debug.Log("Yay! The texture is loaded!"); - } - catch (OperationCanceledException) - { - Debug.LogWarning("The operation was canceled."); - } - catch (Exception e) - { - Debug.LogException(e); - } + var text = await DownloadTextAsync("http://www.google.com"); + Debug.Log(text); +} +catch (OperationCanceledException) +{ + Debug.LogWarning("The operation was canceled."); +} +catch (Exception e) +{ + Debug.LogException(e); } ``` -An operation can have any number of completion callbacks registered: +Or, you can just block current thread while waiting (don't do that from UI thread!). Note usage of `using` with operation instance: ```csharp -void WaitForLoadOperationInCompletionCallback(string textureUrl) +try { - LoadTextureAsync(textureUrl).AddCompletionCallback(op => + using (var op = DownloadTextAsync("http://www.google.com")) { - if (op.IsCompletedSuccessfully) - { - var texture = (op as IAsyncOperation).Result; - Debug.Log("Yay!"); - } - else if (op.IsFaulted) - { - Debug.LogException(op.Exception); - } - else if (op.IsCanceled) - { - Debug.LogWarning("The operation was canceled."); - } - }); + var text = op.Join(); + Debug.Log(text); + } +} +catch (Exception e) +{ + Debug.LogException(e); } ``` -Also one can access/wait for operations from other threads: + +### Chaining asynchronous operations +Multiple asynchronous operations can be chained one after other using `Then`: +```csharp +DownloadTextAsync("http://www.google.com") + .Then(text => ExtractFirstParagraph(text)) + .Then(firstParagraph => Debug.Log(firstParagraph)) + .Catch(e => Debug.LogException(e)); +``` +Alternatively, with .NET 4.6: ```csharp -void WaitForLoadOperationInAnotherThread(string textureUrl) +try +{ + var text = await DownloadTextAsync("http://www.google.com"); + var firstParagraph = await ExtractFirstParagraph(text); + Debug.Log(firstParagraph); +} +catch (OperationCanceledException) +{ + Debug.LogWarning("The operation was canceled."); +} +catch (Exception e) { - var op = LoadTextureAsync(textureUrl); - - ThreadPool.QueueUserWorkItem( - args => - { - try - { - var texture = (args as IAsyncOperation).Join(); - - // The texture is loaded - } - catch (OperationCanceledException) - { - // The operation was canceled - } - catch (Exception) - { - // Load failed - } - }, - op); + Debug.LogException(e); } ``` +### Error handling +TODO + +### Completed asynchronous operations +TODO + +### Interfaces +TODO + ## Comparison to .NET Tasks The comparison table below shows how *UnityFx.Async* entities relate to [Tasks](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task): From 666039ee1feb54c8c69f1e48a1136dea64a7ce6d Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 2 Apr 2018 23:10:54 +0300 Subject: [PATCH 045/128] Added a single base class for all promise results --- .../Extensions/AsyncExtensions.Promises.cs | 6 +- .../CatchResult{T,TException}.cs | 52 +++++++++ .../Continuations/CatchResult{TException}.cs | 80 ------------- .../Continuations/FinallyResult{T}.cs | 44 +++++++ .../{FinallyResult.cs => PromiseResult{T}.cs} | 26 ++--- .../Continuations/RebindResult{T,U}.cs | 69 +++-------- .../Continuations/ThenResult{T,U}.cs | 110 +++++++----------- 7 files changed, 171 insertions(+), 216 deletions(-) create mode 100644 src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs delete mode 100644 src/UnityFx.Async/Implementation/Continuations/CatchResult{TException}.cs create mode 100644 src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs rename src/UnityFx.Async/Implementation/Continuations/{FinallyResult.cs => PromiseResult{T}.cs} (66%) diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs index 3d0acae..1a5aa94 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs @@ -418,7 +418,7 @@ public static IAsyncOperation Catch(this IAsyncOperation op, Action e throw new ArgumentNullException(nameof(errorCallback)); } - var result = new CatchResult(errorCallback); + var result = new CatchResult(errorCallback); op.AddContinuation(result); return result; } @@ -437,7 +437,7 @@ public static IAsyncOperation Catch(this IAsyncOperation op, Action< throw new ArgumentNullException(nameof(errorCallback)); } - var result = new CatchResult(errorCallback); + var result = new CatchResult(errorCallback); op.AddContinuation(result); return result; } @@ -460,7 +460,7 @@ public static IAsyncOperation Finally(this IAsyncOperation op, Action action) throw new ArgumentNullException(nameof(action)); } - var result = new FinallyResult(action); + var result = new FinallyResult(action); op.AddContinuation(result); return result; } diff --git a/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs b/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs new file mode 100644 index 0000000..01e5a76 --- /dev/null +++ b/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs @@ -0,0 +1,52 @@ +// 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 +{ + internal class CatchResult : PromiseResult, IAsyncContinuation where TException : Exception + { + #region data + + private readonly Action _errorCallback; + + #endregion + + #region interface + + public CatchResult(Action errorCallback) + { + _errorCallback = errorCallback; + } + + #endregion + + #region PromiseResult + + protected override void InvokeCallbacks(IAsyncOperation op, bool completedSynchronously) + { + _errorCallback.Invoke(op.Exception.InnerException as TException); + TrySetCompleted(completedSynchronously); + } + + #endregion + + #region IAsyncContinuation + + public void Invoke(IAsyncOperation op, bool completedSynchronously) + { + if (op.IsCompletedSuccessfully || !(op.Exception.InnerException is TException)) + { + TrySetCompleted(completedSynchronously); + } + else + { + InvokeOnSyncContext(op, completedSynchronously); + } + } + + #endregion + } +} diff --git a/src/UnityFx.Async/Implementation/Continuations/CatchResult{TException}.cs b/src/UnityFx.Async/Implementation/Continuations/CatchResult{TException}.cs deleted file mode 100644 index a62b83e..0000000 --- a/src/UnityFx.Async/Implementation/Continuations/CatchResult{TException}.cs +++ /dev/null @@ -1,80 +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 -{ - internal class CatchResult : AsyncResult, IAsyncContinuation where TException : Exception - { - #region data - - private static SendOrPostCallback _postCallback; - - private readonly SynchronizationContext _syncContext; - private readonly Action _errorCallback; - private IAsyncOperation _op; - - #endregion - - #region interface - - public CatchResult(Action errorCallback) - : base(AsyncOperationStatus.Running) - { - _syncContext = SynchronizationContext.Current; - _errorCallback = errorCallback; - } - - #endregion - - #region IAsyncContinuation - - public void Invoke(IAsyncOperation op, bool completedSynchronously) - { - if (op.IsCompletedSuccessfully || !(op.Exception.InnerException is TException)) - { - TrySetCompleted(completedSynchronously); - } - else if (_syncContext == null || _syncContext == SynchronizationContext.Current) - { - InvokeErrorCallback(op, completedSynchronously); - } - else - { - _op = op; - - if (_postCallback == null) - { - _postCallback = args => - { - var c = args as CatchResult; - c.InvokeErrorCallback(c._op, false); - }; - } - - _syncContext.Post(_postCallback, this); - } - } - - #endregion - - #region implementation - - private void InvokeErrorCallback(IAsyncOperation op, bool completedSynchronously) - { - try - { - _errorCallback.Invoke(op.Exception.InnerException as TException); - TrySetCompleted(completedSynchronously); - } - catch (Exception e) - { - TrySetException(e, completedSynchronously); - } - } - - #endregion - } -} diff --git a/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs new file mode 100644 index 0000000..f3094cb --- /dev/null +++ b/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs @@ -0,0 +1,44 @@ +// 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 FinallyResult : PromiseResult, IAsyncContinuation + { + #region data + + private readonly Action _continuation; + + #endregion + + #region interface + + public FinallyResult(Action action) + { + _continuation = action; + } + + #endregion + + #region PromiseResult + + protected override void InvokeCallbacks(IAsyncOperation op, bool completedSynchronously) + { + _continuation(); + TrySetCompleted(completedSynchronously); + } + + #endregion + + #region IAsyncContinuation + + public void Invoke(IAsyncOperation op, bool completedSynchronously) + { + InvokeOnSyncContext(op, completedSynchronously); + } + + #endregion + } +} diff --git a/src/UnityFx.Async/Implementation/Continuations/FinallyResult.cs b/src/UnityFx.Async/Implementation/Continuations/PromiseResult{T}.cs similarity index 66% rename from src/UnityFx.Async/Implementation/Continuations/FinallyResult.cs rename to src/UnityFx.Async/Implementation/Continuations/PromiseResult{T}.cs index 2d0b506..2e0f562 100644 --- a/src/UnityFx.Async/Implementation/Continuations/FinallyResult.cs +++ b/src/UnityFx.Async/Implementation/Continuations/PromiseResult{T}.cs @@ -6,39 +6,32 @@ namespace UnityFx.Async { - internal class FinallyResult : AsyncResult, IAsyncContinuation - { + internal abstract class PromiseResult : AsyncResult + { #region data private static SendOrPostCallback _postCallback; private readonly SynchronizationContext _syncContext; - private readonly Action _continuation; private IAsyncOperation _op; #endregion #region interface - public FinallyResult(Action action) + protected PromiseResult() : base(AsyncOperationStatus.Running) { _syncContext = SynchronizationContext.Current; - _continuation = action; } - #endregion - - #region IAsyncContinuation - - public void Invoke(IAsyncOperation op, bool completedSynchronously) + protected void InvokeOnSyncContext(IAsyncOperation op, bool completedSynchronously) { if (_syncContext == null || _syncContext == SynchronizationContext.Current) { try { - _continuation(); - TrySetCompleted(completedSynchronously); + InvokeCallbacks(op, completedSynchronously); } catch (Exception e) { @@ -53,16 +46,15 @@ public void Invoke(IAsyncOperation op, bool completedSynchronously) { _postCallback = args => { - var c = args as FinallyResult; + var c = args as PromiseResult; try { - c._continuation(); - c.TrySetCompleted(false); + c.InvokeCallbacks(c._op, false); } catch (Exception e) { - c.TrySetException(e, false); + c.TrySetException(e, completedSynchronously); } }; } @@ -71,6 +63,8 @@ public void Invoke(IAsyncOperation op, bool completedSynchronously) } } + protected abstract void InvokeCallbacks(IAsyncOperation op, bool completedSynchronously); + #endregion } } diff --git a/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs index 3c6fb97..30cf5ac 100644 --- a/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs @@ -6,87 +6,56 @@ namespace UnityFx.Async { - internal class RebindResult : AsyncResult, IAsyncContinuation + internal class RebindResult : PromiseResult, IAsyncContinuation { #region data - private static SendOrPostCallback _postCallback; - - private readonly SynchronizationContext _syncContext; private readonly object _continuation; - private IAsyncOperation _op; #endregion #region interface public RebindResult(object action) - : base(AsyncOperationStatus.Running) { - _syncContext = SynchronizationContext.Current; _continuation = action; } #endregion - #region IAsyncContinuation + #region PromiseResult - public void Invoke(IAsyncOperation op, bool completedSynchronously) + protected override void InvokeCallbacks(IAsyncOperation op, bool completedSynchronously) { - if (op.IsCompletedSuccessfully) + switch (_continuation) { - if (_syncContext == null || _syncContext == SynchronizationContext.Current) - { - InvokeCallback(op, completedSynchronously); - } - else - { - _op = op; + case Func f1: + TrySetResult(f1(), completedSynchronously); + break; - if (_postCallback == null) - { - _postCallback = args => - { - var c = args as RebindResult; - c.InvokeCallback(c._op, false); - }; - } + case Func f2: + TrySetResult(f2((op as IAsyncOperation).Result), completedSynchronously); + break; - _syncContext.Post(_postCallback, this); - } - } - else - { - TrySetException(op.Exception, completedSynchronously); + default: + TrySetCanceled(completedSynchronously); + break; } } #endregion - #region implementation + #region IAsyncContinuation - private void InvokeCallback(IAsyncOperation op, bool completedSynchronously) + public void Invoke(IAsyncOperation op, bool completedSynchronously) { - try + if (op.IsCompletedSuccessfully) { - switch (_continuation) - { - case Func f1: - TrySetResult(f1(), completedSynchronously); - break; - - case Func f2: - TrySetResult(f2((op as IAsyncOperation).Result), completedSynchronously); - break; - - default: - TrySetCanceled(completedSynchronously); - break; - } + InvokeOnSyncContext(op, completedSynchronously); } - catch (Exception e) + else { - TrySetException(e, completedSynchronously); + TrySetException(op.Exception, completedSynchronously); } } diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs index a780f2d..158b0fd 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs @@ -6,30 +6,64 @@ namespace UnityFx.Async { - internal class ThenResult : AsyncResult, IAsyncContinuation + internal class ThenResult : PromiseResult, IAsyncContinuation { #region data - private static SendOrPostCallback _postCallback; - - private readonly SynchronizationContext _syncContext; private readonly object _successCallback; private readonly Action _errorCallback; - private IAsyncOperation _op; #endregion #region interface public ThenResult(object successCallback, Action errorCallback) - : base(AsyncOperationStatus.Running) { - _syncContext = SynchronizationContext.Current; _successCallback = successCallback; _errorCallback = errorCallback; } - protected bool InvokeSuccessCallback(IAsyncOperation op) + #endregion + + #region PromiseResult + + protected override void InvokeCallbacks(IAsyncOperation op, bool completedSynchronously) + { + if (op.IsCompletedSuccessfully) + { + if (InvokeSuccessCallback(op)) + { + TrySetCompleted(completedSynchronously); + } + } + else + { + InvokeErrorCallback(op); + TrySetException(op.Exception, completedSynchronously); + } + } + + #endregion + + #region IAsyncContinuation + + public void Invoke(IAsyncOperation op, bool completedSynchronously) + { + if (op.IsCompletedSuccessfully || _errorCallback != null) + { + InvokeOnSyncContext(op, completedSynchronously); + } + else + { + TrySetException(op.Exception, completedSynchronously); + } + } + + #endregion + + #region implementation + + private bool InvokeSuccessCallback(IAsyncOperation op) { var result = false; @@ -69,69 +103,11 @@ protected bool InvokeSuccessCallback(IAsyncOperation op) return result; } - protected void InvokeErrorCallback(IAsyncOperation op) + private void InvokeErrorCallback(IAsyncOperation op) { _errorCallback?.Invoke(op.Exception.InnerException); } #endregion - - #region IAsyncContinuation - - public void Invoke(IAsyncOperation op, bool completedSynchronously) - { - if (_syncContext == null || _syncContext == SynchronizationContext.Current) - { - InvokeCallbacks(op, completedSynchronously); - } - else if (op.IsCompletedSuccessfully || _errorCallback != null) - { - _op = op; - - if (_postCallback == null) - { - _postCallback = args => - { - var c = args as ThenResult; - c.InvokeCallbacks(c._op, false); - }; - } - - _syncContext.Post(_postCallback, this); - } - else - { - TrySetException(op.Exception, completedSynchronously); - } - } - - #endregion - - #region implementation - - private void InvokeCallbacks(IAsyncOperation op, bool completedSynchronously) - { - try - { - if (op.IsCompletedSuccessfully) - { - if (InvokeSuccessCallback(op)) - { - TrySetCompleted(completedSynchronously); - } - } - else - { - InvokeErrorCallback(op); - TrySetException(op.Exception, completedSynchronously); - } - } - catch (Exception e) - { - TrySetException(e, completedSynchronously); - } - } - - #endregion } } From d598136c21a52a5b8f81ec44f7213567f67809d9 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 2 Apr 2018 23:26:58 +0300 Subject: [PATCH 046/128] Minor promise optimizations --- .../Extensions/AsyncExtensions.Promises.cs | 60 +++++-------------- .../CatchResult{T,TException}.cs | 7 ++- .../Continuations/FinallyResult{T}.cs | 12 +--- .../Continuations/PromiseResult{T}.cs | 22 ++++--- .../Continuations/RebindResult{T,U}.cs | 7 ++- .../Continuations/ThenResult{T,U}.cs | 7 ++- .../Continuations/UnwrapResult{T}.cs | 1 + 7 files changed, 43 insertions(+), 73 deletions(-) diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs index 1a5aa94..cc56c43 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs @@ -24,9 +24,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba throw new ArgumentNullException(nameof(successCallback)); } - var result = new ThenResult(successCallback, null); - op.AddContinuation(result); - return result; + return new ThenResult(op, successCallback, null); } /// @@ -43,9 +41,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Ac throw new ArgumentNullException(nameof(successCallback)); } - var result = new ThenResult(successCallback, null); - op.AddContinuation(result); - return result; + return new ThenResult(op, successCallback, null); } /// @@ -62,9 +58,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func(successCallback, null); - op.AddContinuation(result); - return result; + return new ThenResult(op, successCallback, null); } /// @@ -81,9 +75,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Fu throw new ArgumentNullException(nameof(successCallback)); } - var result = new ThenResult(successCallback, null); - op.AddContinuation(result); - return result; + return new ThenResult(op, successCallback, null); } /// @@ -100,9 +92,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Fu throw new ArgumentNullException(nameof(successCallback)); } - var result = new ThenResult(successCallback, null); - op.AddContinuation(result); - return result; + return new ThenResult(op, successCallback, null); } /// @@ -119,9 +109,7 @@ public static IAsyncOperation Then(this IAsyncO throw new ArgumentNullException(nameof(successCallback)); } - var result = new ThenResult(successCallback, null); - op.AddContinuation(result); - return result; + return new ThenResult(op, successCallback, null); } /// @@ -144,9 +132,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba throw new ArgumentNullException(nameof(errorCallback)); } - var result = new ThenResult(successCallback, errorCallback); - op.AddContinuation(result); - return result; + return new ThenResult(op, successCallback, errorCallback); } /// @@ -169,9 +155,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Ac throw new ArgumentNullException(nameof(errorCallback)); } - var result = new ThenResult(successCallback, errorCallback); - op.AddContinuation(result); - return result; + return new ThenResult(op, successCallback, errorCallback); } /// @@ -194,9 +178,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func(successCallback, errorCallback); - op.AddContinuation(result); - return result; + return new ThenResult(op, successCallback, errorCallback); } /// @@ -219,9 +201,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Fu throw new ArgumentNullException(nameof(errorCallback)); } - var result = new ThenResult(successCallback, errorCallback); - op.AddContinuation(result); - return result; + return new ThenResult(op, successCallback, errorCallback); } #endregion @@ -377,9 +357,7 @@ public static IAsyncOperation Rebind(this IAsyncOperatio throw new ArgumentNullException(nameof(successCallback)); } - var result = new RebindResult(successCallback); - op.AddContinuation(result); - return result; + return new RebindResult(op, successCallback); } /// @@ -395,9 +373,7 @@ public static IAsyncOperation Rebind(this IAsyn throw new ArgumentNullException(nameof(successCallback)); } - var result = new RebindResult(successCallback); - op.AddContinuation(result); - return result; + return new RebindResult(op, successCallback); } #endregion @@ -418,9 +394,7 @@ public static IAsyncOperation Catch(this IAsyncOperation op, Action e throw new ArgumentNullException(nameof(errorCallback)); } - var result = new CatchResult(errorCallback); - op.AddContinuation(result); - return result; + return new CatchResult(op, errorCallback); } /// @@ -437,9 +411,7 @@ public static IAsyncOperation Catch(this IAsyncOperation op, Action< throw new ArgumentNullException(nameof(errorCallback)); } - var result = new CatchResult(errorCallback); - op.AddContinuation(result); - return result; + return new CatchResult(op, errorCallback); } #endregion @@ -460,9 +432,7 @@ public static IAsyncOperation Finally(this IAsyncOperation op, Action action) throw new ArgumentNullException(nameof(action)); } - var result = new FinallyResult(action); - op.AddContinuation(result); - return result; + return new FinallyResult(op, action); } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs b/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs index 01e5a76..d6e2b98 100644 --- a/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs @@ -16,7 +16,8 @@ internal class CatchResult : PromiseResult, IAsyncContinuation #region interface - public CatchResult(Action errorCallback) + public CatchResult(IAsyncOperation op, Action errorCallback) + : base(op) { _errorCallback = errorCallback; } @@ -35,7 +36,7 @@ protected override void InvokeCallbacks(IAsyncOperation op, bool completedSynchr #region IAsyncContinuation - public void Invoke(IAsyncOperation op, bool completedSynchronously) + public override void Invoke(IAsyncOperation op, bool completedSynchronously) { if (op.IsCompletedSuccessfully || !(op.Exception.InnerException is TException)) { @@ -43,7 +44,7 @@ public void Invoke(IAsyncOperation op, bool completedSynchronously) } else { - InvokeOnSyncContext(op, completedSynchronously); + base.Invoke(op, completedSynchronously); } } diff --git a/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs index f3094cb..84f65ba 100644 --- a/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs @@ -15,7 +15,8 @@ internal class FinallyResult : PromiseResult, IAsyncContinuation #region interface - public FinallyResult(Action action) + public FinallyResult(IAsyncOperation op, Action action) + : base(op) { _continuation = action; } @@ -31,14 +32,5 @@ protected override void InvokeCallbacks(IAsyncOperation op, bool completedSynchr } #endregion - - #region IAsyncContinuation - - public void Invoke(IAsyncOperation op, bool completedSynchronously) - { - InvokeOnSyncContext(op, completedSynchronously); - } - - #endregion } } diff --git a/src/UnityFx.Async/Implementation/Continuations/PromiseResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/PromiseResult{T}.cs index 2e0f562..967d470 100644 --- a/src/UnityFx.Async/Implementation/Continuations/PromiseResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/PromiseResult{T}.cs @@ -6,26 +6,34 @@ namespace UnityFx.Async { - internal abstract class PromiseResult : AsyncResult - { + internal abstract class PromiseResult : AsyncResult, IAsyncContinuation + { #region data private static SendOrPostCallback _postCallback; private readonly SynchronizationContext _syncContext; - private IAsyncOperation _op; + private readonly IAsyncOperation _op; #endregion #region interface - protected PromiseResult() + protected PromiseResult(IAsyncOperation op) : base(AsyncOperationStatus.Running) { _syncContext = SynchronizationContext.Current; + _op = op; + _op.AddContinuation(this); } - protected void InvokeOnSyncContext(IAsyncOperation op, bool completedSynchronously) + protected abstract void InvokeCallbacks(IAsyncOperation op, bool completedSynchronously); + + #endregion + + #region IAsyncContinuation + + public virtual void Invoke(IAsyncOperation op, bool completedSynchronously) { if (_syncContext == null || _syncContext == SynchronizationContext.Current) { @@ -40,8 +48,6 @@ protected void InvokeOnSyncContext(IAsyncOperation op, bool completedSynchronous } else { - _op = op; - if (_postCallback == null) { _postCallback = args => @@ -63,8 +69,6 @@ protected void InvokeOnSyncContext(IAsyncOperation op, bool completedSynchronous } } - protected abstract void InvokeCallbacks(IAsyncOperation op, bool completedSynchronously); - #endregion } } diff --git a/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs index 30cf5ac..1a203a8 100644 --- a/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs @@ -16,7 +16,8 @@ internal class RebindResult : PromiseResult, IAsyncContinuation #region interface - public RebindResult(object action) + public RebindResult(IAsyncOperation op, object action) + : base(op) { _continuation = action; } @@ -47,11 +48,11 @@ protected override void InvokeCallbacks(IAsyncOperation op, bool completedSynchr #region IAsyncContinuation - public void Invoke(IAsyncOperation op, bool completedSynchronously) + public override void Invoke(IAsyncOperation op, bool completedSynchronously) { if (op.IsCompletedSuccessfully) { - InvokeOnSyncContext(op, completedSynchronously); + base.Invoke(op, completedSynchronously); } else { diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs index 158b0fd..678fb02 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs @@ -17,7 +17,8 @@ internal class ThenResult : PromiseResult, IAsyncContinuation #region interface - public ThenResult(object successCallback, Action errorCallback) + public ThenResult(IAsyncOperation op, object successCallback, Action errorCallback) + : base(op) { _successCallback = successCallback; _errorCallback = errorCallback; @@ -47,11 +48,11 @@ protected override void InvokeCallbacks(IAsyncOperation op, bool completedSynchr #region IAsyncContinuation - public void Invoke(IAsyncOperation op, bool completedSynchronously) + public override void Invoke(IAsyncOperation op, bool completedSynchronously) { if (op.IsCompletedSuccessfully || _errorCallback != null) { - InvokeOnSyncContext(op, completedSynchronously); + base.Invoke(op, completedSynchronously); } else { diff --git a/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs index d7055b2..883080c 100644 --- a/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs @@ -24,6 +24,7 @@ private enum State #region interface public UnwrapResult(IAsyncOperation outerOp) + : base(AsyncOperationStatus.Running) { outerOp.AddContinuation(this); } From 82cc097b03fe796dff246abd62b3cd1aaa111da8 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 2 Apr 2018 23:39:33 +0300 Subject: [PATCH 047/128] Fixed promise implementation NRE when op is completed --- .../AsyncExtensions.Continuations.cs | 32 +++++-------------- .../CatchResult{T,TException}.cs | 4 ++- .../Continuations/ContinuationResult{T,U}.cs | 5 ++- .../Continuations/ContinuationResult{T}.cs | 5 ++- .../Continuations/FinallyResult{T}.cs | 4 ++- .../Continuations/PromiseResult{T}.cs | 8 ++--- .../Continuations/RebindResult{T,U}.cs | 4 ++- .../Continuations/ThenResult{T,U}.cs | 5 +-- 8 files changed, 32 insertions(+), 35 deletions(-) diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs index 2c6159a..cf619d3 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs @@ -103,9 +103,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action(options, action, null); - op.AddContinuation(result); - return result; + return new ContinuationResult(op, options, action, null); } /// @@ -137,9 +135,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action(options, action, userState); - op.AddContinuation(result); - return result; + return new ContinuationResult(op, options, action, userState); } /// @@ -169,9 +165,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperatio throw new ArgumentNullException(nameof(action)); } - var result = new ContinuationResult(options, action, null); - op.AddContinuation(result); - return result; + return new ContinuationResult(op, options, action, null); } /// @@ -203,9 +197,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperatio throw new ArgumentNullException(nameof(action)); } - var result = new ContinuationResult(options, action, userState); - op.AddContinuation(result); - return result; + return new ContinuationResult(op, options, action, userState); } /// @@ -235,9 +227,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation(options, action, null); - op.AddContinuation(result); - return result; + return new ContinuationResult(op, options, action, null); } /// @@ -269,9 +259,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation(options, action, userState); - op.AddContinuation(result); - return result; + return new ContinuationResult(op, options, action, userState); } /// @@ -301,9 +289,7 @@ public static IAsyncOperation ContinueWith(this throw new ArgumentNullException(nameof(action)); } - var result = new ContinuationResult(options, action, null); - op.AddContinuation(result); - return result; + return new ContinuationResult(op, options, action, null); } /// @@ -335,9 +321,7 @@ public static IAsyncOperation ContinueWith(this throw new ArgumentNullException(nameof(action)); } - var result = new ContinuationResult(options, action, userState); - op.AddContinuation(result); - return result; + return new ContinuationResult(op, options, action, userState); } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs b/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs index d6e2b98..15bdb54 100644 --- a/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs @@ -17,9 +17,11 @@ internal class CatchResult : PromiseResult, IAsyncContinuation #region interface public CatchResult(IAsyncOperation op, Action errorCallback) - : base(op) { _errorCallback = errorCallback; + + // NOTE: Cannot move this to base class because this call might trigger virtual Invoke + op.AddContinuation(this); } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T,U}.cs index bd9bf86..62ca19e 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T,U}.cs @@ -16,11 +16,14 @@ internal class ContinuationResult : ContinuationResultBase #region interface - internal ContinuationResult(AsyncContinuationOptions options, object continuation, object userState) + internal ContinuationResult(IAsyncOperation op, AsyncContinuationOptions options, object continuation, object userState) : base(options) { _continuation = continuation; _userState = userState; + + // NOTE: Cannot move this to base class because this call might trigger _continuation (and it would be uninitialized in base ctor) + op.AddContinuation(this); } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs index 4b7ea19..c8e91af 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs @@ -16,11 +16,14 @@ internal class ContinuationResult : ContinuationResultBase #region interface - internal ContinuationResult(AsyncContinuationOptions options, object continuation, object userState) + internal ContinuationResult(IAsyncOperation op, AsyncContinuationOptions options, object continuation, object userState) : base(options) { _continuation = continuation; _userState = userState; + + // NOTE: Cannot move this to base class because this call might trigger _continuation (and it would be uninitialized in base ctor) + op.AddContinuation(this); } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs index 84f65ba..5be0982 100644 --- a/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs @@ -16,9 +16,11 @@ internal class FinallyResult : PromiseResult, IAsyncContinuation #region interface public FinallyResult(IAsyncOperation op, Action action) - : base(op) { _continuation = action; + + // NOTE: Cannot move this to base class because this call might trigger virtual Invoke + op.AddContinuation(this); } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/PromiseResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/PromiseResult{T}.cs index 967d470..f92ef9c 100644 --- a/src/UnityFx.Async/Implementation/Continuations/PromiseResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/PromiseResult{T}.cs @@ -13,18 +13,16 @@ internal abstract class PromiseResult : AsyncResult, IAsyncContinuation private static SendOrPostCallback _postCallback; private readonly SynchronizationContext _syncContext; - private readonly IAsyncOperation _op; + private IAsyncOperation _op; #endregion #region interface - protected PromiseResult(IAsyncOperation op) + protected PromiseResult() : base(AsyncOperationStatus.Running) { _syncContext = SynchronizationContext.Current; - _op = op; - _op.AddContinuation(this); } protected abstract void InvokeCallbacks(IAsyncOperation op, bool completedSynchronously); @@ -48,6 +46,8 @@ public virtual void Invoke(IAsyncOperation op, bool completedSynchronously) } else { + _op = op; + if (_postCallback == null) { _postCallback = args => diff --git a/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs index 1a203a8..3ea6dea 100644 --- a/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs @@ -17,9 +17,11 @@ internal class RebindResult : PromiseResult, IAsyncContinuation #region interface public RebindResult(IAsyncOperation op, object action) - : base(op) { _continuation = action; + + // NOTE: Cannot move this to base class because this call might trigger virtual Invoke + op.AddContinuation(this); } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs index 678fb02..d3b61d3 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs @@ -2,7 +2,6 @@ // 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 { @@ -18,10 +17,12 @@ internal class ThenResult : PromiseResult, IAsyncContinuation #region interface public ThenResult(IAsyncOperation op, object successCallback, Action errorCallback) - : base(op) { _successCallback = successCallback; _errorCallback = errorCallback; + + // NOTE: Cannot move this to base class because this call might trigger virtual Invoke + op.AddContinuation(this); } #endregion From 8dd7abdd6260fd7ed8bc42d8fe2a0e68e821910a Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Tue, 3 Apr 2018 13:09:37 +0300 Subject: [PATCH 048/128] Changed IAsyncContinuation.Invoke signature --- src/UnityFx.Async/Api/Core/AsyncResult.cs | 2 +- .../AsyncExtensions.Continuations.cs | 2 +- .../Api/Interfaces/IAsyncContinuation.cs | 3 +-- .../Continuations/AsyncContinuation.cs | 2 +- .../CatchResult{T,TException}.cs | 19 ++++++++++++---- .../ContinuationResultBase{T}.cs | 14 +++++------- .../Continuations/ContinuationResult{T,U}.cs | 16 ++++++++++++-- .../Continuations/ContinuationResult{T}.cs | 16 ++++++++++++-- .../Continuations/FinallyResult{T}.cs | 14 +++++++++++- .../Continuations/PromiseResult{T}.cs | 16 +++++--------- .../Continuations/RebindResult{T,U}.cs | 18 ++++++++++++--- .../Continuations/ThenResult{T,U}.cs | 22 +++++++++++++------ .../Continuations/UnwrapResult{T}.cs | 10 ++++----- 13 files changed, 106 insertions(+), 48 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 46b19f6..a6a933f 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -1870,7 +1870,7 @@ private static void InvokeContinuation(IAsyncOperation op, object continuation) { if (continuation is IAsyncContinuation c) { - c.Invoke(op, false); + c.Invoke(op); } else { diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs index cf619d3..2692097 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs @@ -23,7 +23,7 @@ public static void AddContinuation(this IAsyncOperation op, IAsyncContinuation c { if (!op.TryAddContinuation(continuation)) { - continuation.Invoke(op, true); + continuation.Invoke(op); } } diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs index 5c8dd2a..2c6fbb9 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs @@ -65,7 +65,6 @@ public interface IAsyncContinuation /// Starts the continuation. /// /// The completed antecedent operation. - /// Value of the property. - void Invoke(IAsyncOperation op, bool completedSynchronously); + void Invoke(IAsyncOperation op); } } diff --git a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs index 8eaf38f..753e219 100644 --- a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs +++ b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs @@ -110,7 +110,7 @@ internal static void InvokeTaskContinuation(IAsyncOperation op, TaskComple #region IAsyncContinuation - public void Invoke(IAsyncOperation op, bool completedSynchronously) + public void Invoke(IAsyncOperation op) { if (_syncContext == null || _syncContext == SynchronizationContext.Current) { diff --git a/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs b/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs index 15bdb54..deb49cd 100644 --- a/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs @@ -2,7 +2,6 @@ // 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 { @@ -21,7 +20,10 @@ public CatchResult(IAsyncOperation op, Action errorCallback) _errorCallback = errorCallback; // NOTE: Cannot move this to base class because this call might trigger virtual Invoke - op.AddContinuation(this); + if (!op.TryAddContinuation(this)) + { + InvokeInternal(op, true); + } } #endregion @@ -38,7 +40,16 @@ protected override void InvokeCallbacks(IAsyncOperation op, bool completedSynchr #region IAsyncContinuation - public override void Invoke(IAsyncOperation op, bool completedSynchronously) + public void Invoke(IAsyncOperation op) + { + InvokeInternal(op, false); + } + + #endregion + + #region implementation + + private void InvokeInternal(IAsyncOperation op, bool completedSynchronously) { if (op.IsCompletedSuccessfully || !(op.Exception.InnerException is TException)) { @@ -46,7 +57,7 @@ public override void Invoke(IAsyncOperation op, bool completedSynchronously) } else { - base.Invoke(op, completedSynchronously); + InvokeOnSyncContext(op, completedSynchronously); } } diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinuationResultBase{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinuationResultBase{T}.cs index cef330b..d0f9130 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinuationResultBase{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinuationResultBase{T}.cs @@ -6,7 +6,7 @@ namespace UnityFx.Async { - internal abstract class ContinuationResultBase : AsyncResult, IAsyncContinuation + internal abstract class ContinuationResultBase : AsyncResult { #region data @@ -31,17 +31,11 @@ protected ContinuationResultBase(AsyncContinuationOptions options) _options = options; } - protected abstract T OnInvoke(IAsyncOperation op); - - #endregion - - #region IAsyncContinuation - - public void Invoke(IAsyncOperation op, bool completedSynchronously) + protected void InvokeOnSyncContext(IAsyncOperation op, bool completedSynchronously) { if (AsyncContinuation.CanInvoke(op, _options)) { - if (_syncContext == null || _syncContext == SynchronizationContext.Current) + if (completedSynchronously || _syncContext == null || _syncContext == SynchronizationContext.Current) { try { @@ -82,6 +76,8 @@ public void Invoke(IAsyncOperation op, bool completedSynchronously) } } + protected abstract T OnInvoke(IAsyncOperation op); + #endregion #region implementation diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T,U}.cs index 62ca19e..371027e 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T,U}.cs @@ -5,7 +5,7 @@ namespace UnityFx.Async { - internal class ContinuationResult : ContinuationResultBase + internal class ContinuationResult : ContinuationResultBase, IAsyncContinuation { #region data @@ -23,7 +23,10 @@ internal ContinuationResult(IAsyncOperation op, AsyncContinuationOptions options _userState = userState; // NOTE: Cannot move this to base class because this call might trigger _continuation (and it would be uninitialized in base ctor) - op.AddContinuation(this); + if (!op.TryAddContinuation(this)) + { + InvokeOnSyncContext(op, true); + } } #endregion @@ -61,5 +64,14 @@ protected override U OnInvoke(IAsyncOperation op) } #endregion + + #region IAsyncContinuation + + public void Invoke(IAsyncOperation op) + { + InvokeOnSyncContext(op, false); + } + + #endregion } } diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs index c8e91af..6535d2d 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs @@ -5,7 +5,7 @@ namespace UnityFx.Async { - internal class ContinuationResult : ContinuationResultBase + internal class ContinuationResult : ContinuationResultBase, IAsyncContinuation { #region data @@ -23,7 +23,10 @@ internal ContinuationResult(IAsyncOperation op, AsyncContinuationOptions options _userState = userState; // NOTE: Cannot move this to base class because this call might trigger _continuation (and it would be uninitialized in base ctor) - op.AddContinuation(this); + if (!op.TryAddContinuation(this)) + { + InvokeOnSyncContext(op, true); + } } #endregion @@ -61,5 +64,14 @@ protected override T OnInvoke(IAsyncOperation op) } #endregion + + #region IAsyncContinuation + + public void Invoke(IAsyncOperation op) + { + InvokeOnSyncContext(op, false); + } + + #endregion } } diff --git a/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs index 5be0982..0b36592 100644 --- a/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs @@ -20,7 +20,10 @@ public FinallyResult(IAsyncOperation op, Action action) _continuation = action; // NOTE: Cannot move this to base class because this call might trigger virtual Invoke - op.AddContinuation(this); + if (!op.TryAddContinuation(this)) + { + InvokeOnSyncContext(op, true); + } } #endregion @@ -34,5 +37,14 @@ protected override void InvokeCallbacks(IAsyncOperation op, bool completedSynchr } #endregion + + #region IAsyncContinuation + + public void Invoke(IAsyncOperation op) + { + InvokeOnSyncContext(op, false); + } + + #endregion } } diff --git a/src/UnityFx.Async/Implementation/Continuations/PromiseResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/PromiseResult{T}.cs index f92ef9c..b755d42 100644 --- a/src/UnityFx.Async/Implementation/Continuations/PromiseResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/PromiseResult{T}.cs @@ -6,7 +6,7 @@ namespace UnityFx.Async { - internal abstract class PromiseResult : AsyncResult, IAsyncContinuation + internal abstract class PromiseResult : AsyncResult { #region data @@ -25,15 +25,9 @@ protected PromiseResult() _syncContext = SynchronizationContext.Current; } - protected abstract void InvokeCallbacks(IAsyncOperation op, bool completedSynchronously); - - #endregion - - #region IAsyncContinuation - - public virtual void Invoke(IAsyncOperation op, bool completedSynchronously) + protected void InvokeOnSyncContext(IAsyncOperation op, bool completedSynchronously) { - if (_syncContext == null || _syncContext == SynchronizationContext.Current) + if (completedSynchronously || _syncContext == null || _syncContext == SynchronizationContext.Current) { try { @@ -60,7 +54,7 @@ public virtual void Invoke(IAsyncOperation op, bool completedSynchronously) } catch (Exception e) { - c.TrySetException(e, completedSynchronously); + c.TrySetException(e, false); } }; } @@ -69,6 +63,8 @@ public virtual void Invoke(IAsyncOperation op, bool completedSynchronously) } } + protected abstract void InvokeCallbacks(IAsyncOperation op, bool completedSynchronously); + #endregion } } diff --git a/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs index 3ea6dea..f854a6f 100644 --- a/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs @@ -21,7 +21,10 @@ public RebindResult(IAsyncOperation op, object action) _continuation = action; // NOTE: Cannot move this to base class because this call might trigger virtual Invoke - op.AddContinuation(this); + if (!op.TryAddContinuation(this)) + { + InvokeInternal(op, true); + } } #endregion @@ -50,11 +53,20 @@ protected override void InvokeCallbacks(IAsyncOperation op, bool completedSynchr #region IAsyncContinuation - public override void Invoke(IAsyncOperation op, bool completedSynchronously) + public void Invoke(IAsyncOperation op) + { + InvokeOnSyncContext(op, false); + } + + #endregion + + #region implementation + + private void InvokeInternal(IAsyncOperation op, bool completedSynchronously) { if (op.IsCompletedSuccessfully) { - base.Invoke(op, completedSynchronously); + InvokeOnSyncContext(op, completedSynchronously); } else { diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs index d3b61d3..e26d59a 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs @@ -22,7 +22,10 @@ public ThenResult(IAsyncOperation op, object successCallback, Action _errorCallback = errorCallback; // NOTE: Cannot move this to base class because this call might trigger virtual Invoke - op.AddContinuation(this); + if (!op.TryAddContinuation(this)) + { + InvokeInternal(op, true); + } } #endregion @@ -49,11 +52,20 @@ protected override void InvokeCallbacks(IAsyncOperation op, bool completedSynchr #region IAsyncContinuation - public override void Invoke(IAsyncOperation op, bool completedSynchronously) + public void Invoke(IAsyncOperation op) + { + InvokeInternal(op, false); + } + + #endregion + + #region implementation + + private void InvokeInternal(IAsyncOperation op, bool completedSynchronously) { if (op.IsCompletedSuccessfully || _errorCallback != null) { - base.Invoke(op, completedSynchronously); + InvokeOnSyncContext(op, completedSynchronously); } else { @@ -61,10 +73,6 @@ public override void Invoke(IAsyncOperation op, bool completedSynchronously) } } - #endregion - - #region implementation - private bool InvokeSuccessCallback(IAsyncOperation op) { var result = false; diff --git a/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs index 883080c..d7f74b1 100644 --- a/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs @@ -33,7 +33,7 @@ public UnwrapResult(IAsyncOperation outerOp) #region IAsyncContinuation - public void Invoke(IAsyncOperation op, bool completedSynchronously) + public void Invoke(IAsyncOperation op) { if (_state == State.WaitingForOuterOperation) { @@ -42,21 +42,21 @@ public void Invoke(IAsyncOperation op, bool completedSynchronously) switch (op) { case IAsyncOperation> innerOp1: - ProcessInnerOperation(innerOp1, completedSynchronously); + ProcessInnerOperation(innerOp1, false); break; case IAsyncOperation innerOp2: - ProcessInnerOperation(innerOp2, completedSynchronously); + ProcessInnerOperation(innerOp2, false); break; default: - ProcessInnerOperation(null, completedSynchronously); + ProcessInnerOperation(null, false); break; } } else { - TrySetException(op.Exception, completedSynchronously); + TrySetException(op.Exception, false); } _state = State.WaitingForInnerOperation; From 928bc02d3e0bb862e0f443229eafdbb502ba32d9 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Tue, 3 Apr 2018 18:47:17 +0300 Subject: [PATCH 049/128] ContinueWith and Promise continuations now have the same base class --- .../AsyncExtensions.Continuations.cs | 16 ++-- .../CatchResult{T,TException}.cs | 4 +- .../ContinuationResultBase{T}.cs | 86 ----------------- .../Continuations/ContinuationResult{T}.cs | 96 ++++++++++--------- ...ult{T,U}.cs => ContinueWithResult{T,U}.cs} | 38 ++++++-- .../Continuations/ContinueWithResult{T}.cs | 95 ++++++++++++++++++ .../Continuations/FinallyResult{T}.cs | 4 +- .../Continuations/PromiseResult{T}.cs | 70 -------------- .../Continuations/RebindResult{T,U}.cs | 4 +- .../Continuations/ThenResult{T,U}.cs | 4 +- 10 files changed, 188 insertions(+), 229 deletions(-) delete mode 100644 src/UnityFx.Async/Implementation/Continuations/ContinuationResultBase{T}.cs rename src/UnityFx.Async/Implementation/Continuations/{ContinuationResult{T,U}.cs => ContinueWithResult{T,U}.cs} (58%) create mode 100644 src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs delete mode 100644 src/UnityFx.Async/Implementation/Continuations/PromiseResult{T}.cs diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs index 2692097..f50d8da 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs @@ -103,7 +103,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action(op, options, action, null); + return new ContinueWithResult(op, options, action, null); } /// @@ -135,7 +135,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action(op, options, action, userState); + return new ContinueWithResult(op, options, action, userState); } /// @@ -165,7 +165,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperatio throw new ArgumentNullException(nameof(action)); } - return new ContinuationResult(op, options, action, null); + return new ContinueWithResult(op, options, action, null); } /// @@ -197,7 +197,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperatio throw new ArgumentNullException(nameof(action)); } - return new ContinuationResult(op, options, action, userState); + return new ContinueWithResult(op, options, action, userState); } /// @@ -227,7 +227,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation(op, options, action, null); + return new ContinueWithResult(op, options, action, null); } /// @@ -259,7 +259,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation(op, options, action, userState); + return new ContinueWithResult(op, options, action, userState); } /// @@ -289,7 +289,7 @@ public static IAsyncOperation ContinueWith(this throw new ArgumentNullException(nameof(action)); } - return new ContinuationResult(op, options, action, null); + return new ContinueWithResult(op, options, action, null); } /// @@ -321,7 +321,7 @@ public static IAsyncOperation ContinueWith(this throw new ArgumentNullException(nameof(action)); } - return new ContinuationResult(op, options, action, userState); + return new ContinueWithResult(op, options, action, userState); } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs b/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs index deb49cd..c4c85f7 100644 --- a/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs @@ -5,7 +5,7 @@ namespace UnityFx.Async { - internal class CatchResult : PromiseResult, IAsyncContinuation where TException : Exception + internal class CatchResult : ContinuationResult, IAsyncContinuation where TException : Exception { #region data @@ -30,7 +30,7 @@ public CatchResult(IAsyncOperation op, Action errorCallback) #region PromiseResult - protected override void InvokeCallbacks(IAsyncOperation op, bool completedSynchronously) + protected override void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously) { _errorCallback.Invoke(op.Exception.InnerException as TException); TrySetCompleted(completedSynchronously); diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinuationResultBase{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinuationResultBase{T}.cs deleted file mode 100644 index d0f9130..0000000 --- a/src/UnityFx.Async/Implementation/Continuations/ContinuationResultBase{T}.cs +++ /dev/null @@ -1,86 +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 -{ - internal abstract class ContinuationResultBase : AsyncResult - { - #region data - - private static SendOrPostCallback _postCallback; - - private readonly SynchronizationContext _syncContext; - private readonly AsyncContinuationOptions _options; - private IAsyncOperation _op; - - #endregion - - #region interface - - protected ContinuationResultBase(AsyncContinuationOptions options) - : base(AsyncOperationStatus.Running) - { - if ((options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0) - { - _syncContext = SynchronizationContext.Current; - } - - _options = options; - } - - protected void InvokeOnSyncContext(IAsyncOperation op, bool completedSynchronously) - { - if (AsyncContinuation.CanInvoke(op, _options)) - { - if (completedSynchronously || _syncContext == null || _syncContext == SynchronizationContext.Current) - { - try - { - TrySetResult(OnInvoke(op), completedSynchronously); - } - catch (Exception e) - { - TrySetException(e, completedSynchronously); - } - } - else - { - _op = op; - - if (_postCallback == null) - { - _postCallback = args => - { - var c = args as ContinuationResultBase; - - try - { - c.TrySetResult(c.OnInvoke(c._op), false); - } - catch (Exception e) - { - c.TrySetException(e, false); - } - }; - } - - _syncContext.Post(_postCallback, this); - } - } - else - { - TrySetCanceled(completedSynchronously); - } - } - - protected abstract T OnInvoke(IAsyncOperation op); - - #endregion - - #region implementation - #endregion - } -} diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs index 6535d2d..3cc6645 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs @@ -2,75 +2,77 @@ // 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 { - internal class ContinuationResult : ContinuationResultBase, IAsyncContinuation + internal abstract class ContinuationResult : AsyncResult { #region data - private readonly object _continuation; - private readonly object _userState; + private static SendOrPostCallback _postCallback; + + private readonly SynchronizationContext _syncContext; + private IAsyncOperation _op; #endregion #region interface - internal ContinuationResult(IAsyncOperation op, AsyncContinuationOptions options, object continuation, object userState) - : base(options) + protected ContinuationResult() + : base(AsyncOperationStatus.Running) { - _continuation = continuation; - _userState = userState; + _syncContext = SynchronizationContext.Current; + } - // NOTE: Cannot move this to base class because this call might trigger _continuation (and it would be uninitialized in base ctor) - if (!op.TryAddContinuation(this)) + protected ContinuationResult(bool captureSynchronizationContext) + : base(AsyncOperationStatus.Running) + { + if (captureSynchronizationContext) { - InvokeOnSyncContext(op, true); + _syncContext = SynchronizationContext.Current; } } - #endregion - - #region AsyncContinuation - - protected override T OnInvoke(IAsyncOperation op) + protected void InvokeOnSyncContext(IAsyncOperation op, bool completedSynchronously) { - var result = default(T); - - switch (_continuation) + if (completedSynchronously || _syncContext == null || _syncContext == SynchronizationContext.Current) { - case Action a: - a.Invoke(op); - break; - - case Func f: - result = f.Invoke(op); - break; - - case Action ao: - ao.Invoke(op, _userState); - break; - - case Func fo: - result = fo.Invoke(op, _userState); - break; - - default: - // Should not get here. - throw new InvalidOperationException(); + try + { + InvokeUnsafe(op, completedSynchronously); + } + catch (Exception e) + { + TrySetException(e, completedSynchronously); + } + } + else + { + _op = op; + + if (_postCallback == null) + { + _postCallback = args => + { + var c = args as ContinuationResult; + + try + { + c.InvokeUnsafe(c._op, false); + } + catch (Exception e) + { + c.TrySetException(e, false); + } + }; + } + + _syncContext.Post(_postCallback, this); } - - return result; } - #endregion - - #region IAsyncContinuation - - public void Invoke(IAsyncOperation op) - { - InvokeOnSyncContext(op, false); - } + protected abstract void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously); #endregion } diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs similarity index 58% rename from src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T,U}.cs rename to src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs index 371027e..99ba47c 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs @@ -5,10 +5,11 @@ namespace UnityFx.Async { - internal class ContinuationResult : ContinuationResultBase, IAsyncContinuation + internal class ContinueWithResult : ContinuationResult, IAsyncContinuation { #region data + private readonly AsyncContinuationOptions _options; private readonly object _continuation; private readonly object _userState; @@ -16,24 +17,25 @@ internal class ContinuationResult : ContinuationResultBase, IAsyncConti #region interface - internal ContinuationResult(IAsyncOperation op, AsyncContinuationOptions options, object continuation, object userState) - : base(options) + internal ContinueWithResult(IAsyncOperation op, AsyncContinuationOptions options, object continuation, object userState) + : base((options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0) { + _options = options; _continuation = continuation; _userState = userState; // NOTE: Cannot move this to base class because this call might trigger _continuation (and it would be uninitialized in base ctor) if (!op.TryAddContinuation(this)) { - InvokeOnSyncContext(op, true); + InvokeInternal(op, true); } } #endregion - #region AsyncContinuation + #region PromiseResult - protected override U OnInvoke(IAsyncOperation op) + protected override void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously) { var result = default(U); @@ -56,11 +58,11 @@ protected override U OnInvoke(IAsyncOperation op) break; default: - // Should not get here. - throw new InvalidOperationException(); + TrySetCanceled(completedSynchronously); + return; } - return result; + TrySetResult(result, completedSynchronously); } #endregion @@ -69,7 +71,23 @@ protected override U OnInvoke(IAsyncOperation op) public void Invoke(IAsyncOperation op) { - InvokeOnSyncContext(op, false); + InvokeInternal(op, false); + } + + #endregion + + #region implementation + + private void InvokeInternal(IAsyncOperation op, bool completedSynchronously) + { + if (AsyncContinuation.CanInvoke(op, _options)) + { + InvokeOnSyncContext(op, completedSynchronously); + } + else + { + TrySetCanceled(completedSynchronously); + } } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs new file mode 100644 index 0000000..b1e8d08 --- /dev/null +++ b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.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; + +namespace UnityFx.Async +{ + internal class ContinueWithResult : ContinuationResult, IAsyncContinuation + { + #region data + + private readonly AsyncContinuationOptions _options; + private readonly object _continuation; + private readonly object _userState; + + #endregion + + #region interface + + internal ContinueWithResult(IAsyncOperation op, AsyncContinuationOptions options, object continuation, object userState) + : base((options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0) + { + _options = options; + _continuation = continuation; + _userState = userState; + + // NOTE: Cannot move this to base class because this call might trigger _continuation (and it would be uninitialized in base ctor) + if (!op.TryAddContinuation(this)) + { + InvokeInternal(op, true); + } + } + + #endregion + + #region PromiseResult + + protected override void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously) + { + var result = default(T); + + switch (_continuation) + { + case Action a: + a.Invoke(op); + break; + + case Func f: + result = f.Invoke(op); + break; + + case Action ao: + ao.Invoke(op, _userState); + break; + + case Func fo: + result = fo.Invoke(op, _userState); + break; + + default: + TrySetCanceled(completedSynchronously); + return; + } + + TrySetResult(result, completedSynchronously); + } + + #endregion + + #region IAsyncContinuation + + public void Invoke(IAsyncOperation op) + { + InvokeInternal(op, false); + } + + #endregion + + #region implementation + + private void InvokeInternal(IAsyncOperation op, bool completedSynchronously) + { + if (AsyncContinuation.CanInvoke(op, _options)) + { + InvokeOnSyncContext(op, completedSynchronously); + } + else + { + TrySetCanceled(completedSynchronously); + } + } + + #endregion + } +} diff --git a/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs index 0b36592..c969dfc 100644 --- a/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs @@ -5,7 +5,7 @@ namespace UnityFx.Async { - internal class FinallyResult : PromiseResult, IAsyncContinuation + internal class FinallyResult : ContinuationResult, IAsyncContinuation { #region data @@ -30,7 +30,7 @@ public FinallyResult(IAsyncOperation op, Action action) #region PromiseResult - protected override void InvokeCallbacks(IAsyncOperation op, bool completedSynchronously) + protected override void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously) { _continuation(); TrySetCompleted(completedSynchronously); diff --git a/src/UnityFx.Async/Implementation/Continuations/PromiseResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/PromiseResult{T}.cs deleted file mode 100644 index b755d42..0000000 --- a/src/UnityFx.Async/Implementation/Continuations/PromiseResult{T}.cs +++ /dev/null @@ -1,70 +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 -{ - internal abstract class PromiseResult : AsyncResult - { - #region data - - private static SendOrPostCallback _postCallback; - - private readonly SynchronizationContext _syncContext; - private IAsyncOperation _op; - - #endregion - - #region interface - - protected PromiseResult() - : base(AsyncOperationStatus.Running) - { - _syncContext = SynchronizationContext.Current; - } - - protected void InvokeOnSyncContext(IAsyncOperation op, bool completedSynchronously) - { - if (completedSynchronously || _syncContext == null || _syncContext == SynchronizationContext.Current) - { - try - { - InvokeCallbacks(op, completedSynchronously); - } - catch (Exception e) - { - TrySetException(e, completedSynchronously); - } - } - else - { - _op = op; - - if (_postCallback == null) - { - _postCallback = args => - { - var c = args as PromiseResult; - - try - { - c.InvokeCallbacks(c._op, false); - } - catch (Exception e) - { - c.TrySetException(e, false); - } - }; - } - - _syncContext.Post(_postCallback, this); - } - } - - protected abstract void InvokeCallbacks(IAsyncOperation op, bool completedSynchronously); - - #endregion - } -} diff --git a/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs index f854a6f..c8fe490 100644 --- a/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs @@ -6,7 +6,7 @@ namespace UnityFx.Async { - internal class RebindResult : PromiseResult, IAsyncContinuation + internal class RebindResult : ContinuationResult, IAsyncContinuation { #region data @@ -31,7 +31,7 @@ public RebindResult(IAsyncOperation op, object action) #region PromiseResult - protected override void InvokeCallbacks(IAsyncOperation op, bool completedSynchronously) + protected override void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously) { switch (_continuation) { diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs index e26d59a..22ad682 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs @@ -5,7 +5,7 @@ namespace UnityFx.Async { - internal class ThenResult : PromiseResult, IAsyncContinuation + internal class ThenResult : ContinuationResult, IAsyncContinuation { #region data @@ -32,7 +32,7 @@ public ThenResult(IAsyncOperation op, object successCallback, Action #region PromiseResult - protected override void InvokeCallbacks(IAsyncOperation op, bool completedSynchronously) + protected override void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously) { if (op.IsCompletedSuccessfully) { From e2691a304ef19c69caa30395b87af7a7c99fac74 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Tue, 3 Apr 2018 18:59:12 +0300 Subject: [PATCH 050/128] Optimized WhenAll/WhenAny/Retry implementations --- .../Specialized/RetryResult{T}.cs | 78 ++++++++++--------- .../Specialized/WhenAllResult{T}.cs | 12 ++- .../Specialized/WhenAnyResult{T}.cs | 10 +-- 3 files changed, 49 insertions(+), 51 deletions(-) diff --git a/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs index 5ae58f5..c4b44fb 100644 --- a/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs @@ -7,12 +7,11 @@ namespace UnityFx.Async { - internal class RetryResult : AsyncResult + internal class RetryResult : AsyncResult, IAsyncContinuation { #region data private readonly object _opFactory; - private readonly AsyncOperationCallback _opCompletionCallback; private readonly int _millisecondsRetryDelay; private readonly int _maxRetryCount; @@ -31,7 +30,6 @@ internal RetryResult(object opFactory, int millisecondsRetryDelay, int maxRetryC _opFactory = opFactory; _millisecondsRetryDelay = millisecondsRetryDelay; _maxRetryCount = maxRetryCount; - _opCompletionCallback = OnOperationCompleted; _numberOfRetriesLeft = maxRetryCount; StartOperation(true); @@ -54,6 +52,44 @@ protected override void OnCompleted() #endregion + #region IAsyncContinuation + + public void Invoke(IAsyncOperation op) + { + Debug.Assert(_op == op); + Debug.Assert(_op.IsCompleted); + + if (!IsCompleted) + { + if (_op.IsCompletedSuccessfully) + { + SetResult(false); + } + else if (_millisecondsRetryDelay > 0) + { + if (_timerCallback == null) + { + _timerCallback = OnTimer; + } + + if (_timer == null) + { + _timer = new Timer(_timerCallback, null, _millisecondsRetryDelay, Timeout.Infinite); + } + else + { + _timer.Change(_millisecondsRetryDelay, Timeout.Infinite); + } + } + else + { + Retry(false); + } + } + } + + #endregion + #region implementation private void StartOperation(bool calledFromConstructor) @@ -73,7 +109,7 @@ private void StartOperation(bool calledFromConstructor) throw new InvalidOperationException("Invalid delegate type."); } - if (!_op.TryAddCompletionCallback(_opCompletionCallback, null)) + if (!_op.TryAddContinuation(this)) { if (_op.IsCompletedSuccessfully) { @@ -91,40 +127,6 @@ private void StartOperation(bool calledFromConstructor) } } - private void OnOperationCompleted(IAsyncOperation op) - { - Debug.Assert(_op == op); - Debug.Assert(_op.IsCompleted); - - if (!IsCompleted) - { - if (_op.IsCompletedSuccessfully) - { - SetResult(false); - } - else if (_millisecondsRetryDelay > 0) - { - if (_timerCallback == null) - { - _timerCallback = OnTimer; - } - - if (_timer == null) - { - _timer = new Timer(_timerCallback, null, _millisecondsRetryDelay, Timeout.Infinite); - } - else - { - _timer.Change(_millisecondsRetryDelay, Timeout.Infinite); - } - } - else - { - Retry(false); - } - } - } - private void OnTimer(object args) { if (!IsCompleted) diff --git a/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs index 4a54ff9..2fe80ed 100644 --- a/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs @@ -7,11 +7,10 @@ namespace UnityFx.Async { - internal class WhenAllResult : AsyncResult + internal class WhenAllResult : AsyncResult, IAsyncContinuation { #region data - private readonly AsyncOperationCallback _completionAction; private readonly IAsyncOperation[] _ops; private int _count; @@ -24,16 +23,15 @@ internal class WhenAllResult : AsyncResult public WhenAllResult(IAsyncOperation[] ops) : base(AsyncOperationStatus.Running) { - _completionAction = OnOperationCompleted; _ops = ops; _count = ops.Length; _completedSynchronously = true; foreach (var op in ops) { - if (!op.TryAddCompletionCallback(_completionAction, null)) + if (!op.TryAddContinuation(this)) { - OnOperationCompleted(op); + Invoke(op); } } @@ -47,9 +45,9 @@ public void Cancel() #endregion - #region implementation + #region IAsyncContinuation - private void OnOperationCompleted(IAsyncOperation asyncOp) + public void Invoke(IAsyncOperation asyncOp) { if (IsCompleted) { diff --git a/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs index 99524cb..ae0db5a 100644 --- a/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs @@ -5,11 +5,10 @@ namespace UnityFx.Async { - internal class WhenAnyResult : AsyncResult where T : IAsyncOperation + internal class WhenAnyResult : AsyncResult, IAsyncContinuation where T : IAsyncOperation { #region data - private readonly AsyncOperationCallback _completionAction; private readonly T[] _ops; #endregion @@ -19,12 +18,11 @@ internal class WhenAnyResult : AsyncResult where T : IAsyncOperation public WhenAnyResult(T[] ops) : base(AsyncOperationStatus.Running) { - _completionAction = OnOperationCompleted; _ops = ops; foreach (var op in ops) { - if (!op.TryAddCompletionCallback(_completionAction, null)) + if (!op.TryAddContinuation(this)) { TrySetResult(op, true); break; @@ -39,9 +37,9 @@ public void Cancel() #endregion - #region implementation + #region IAsyncContinuation - private void OnOperationCompleted(IAsyncOperation op) + public void Invoke(IAsyncOperation op) { TrySetResult((T)op, false); } From ea9776a4a3dda589fff774ddc9bd64a2bef080d7 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Tue, 3 Apr 2018 19:42:21 +0300 Subject: [PATCH 051/128] ThenAny implementation --- .../Api/Core/AsyncResult{TResult}.cs | 35 ++++++++ .../Extensions/AsyncExtensions.Promises.cs | 12 +-- .../Continuations/ThenAnyResult{T,U}.cs | 66 ++++++++++++++ .../Continuations/ThenResult{T,U}.cs | 89 +++++++++---------- 4 files changed, 148 insertions(+), 54 deletions(-) create mode 100644 src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs diff --git a/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs b/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs index cf6fa53..be372f9 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs @@ -131,6 +131,17 @@ protected internal bool TrySetResult(TResult result, bool completedSynchronously return false; } + /// + /// Copies state of the specified operation. + /// + internal new void CopyCompletionState(IAsyncOperation patternOp, bool completedSynchronously) + { + if (!TryCopyCompletionState(patternOp, completedSynchronously)) + { + throw new InvalidOperationException(); + } + } + /// /// Copies state of the specified operation. /// @@ -142,6 +153,30 @@ internal void CopyCompletionState(IAsyncOperation patternOp, bool compl } } + /// + /// Attemts to copy state of the specified operation. + /// + internal new bool TryCopyCompletionState(IAsyncOperation patternOp, bool completedSynchronously) + { + if (patternOp.IsCompletedSuccessfully) + { + if (patternOp is IAsyncOperation op) + { + return TrySetResult(op.Result, completedSynchronously); + } + else + { + return TrySetCompleted(completedSynchronously); + } + } + else if (patternOp.IsFaulted || patternOp.IsCanceled) + { + return TrySetException(patternOp.Exception, completedSynchronously); + } + + return false; + } + /// /// Attemts to copy state of the specified operation. /// diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs index cc56c43..6506849 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs @@ -289,7 +289,7 @@ public static IAsyncOperation ThenAny(this IAsyncOperation op, Func(op, successCallback, null); } /// @@ -305,7 +305,7 @@ public static IAsyncOperation ThenAny(this IAsyncOperation op, Func(op, successCallback, null); } /// @@ -314,14 +314,14 @@ public static IAsyncOperation ThenAny(this IAsyncOperation op, FuncAn operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the callback has completed. - public static IAsyncOperation ThenAny(this IAsyncOperation op, Func>> successCallback) + public static IAsyncOperation ThenAny(this IAsyncOperation op, Func>> successCallback) { if (successCallback == null) { throw new ArgumentNullException(nameof(successCallback)); } - throw new NotImplementedException(); + return new ThenAnyResult(op, successCallback, null); } /// @@ -330,14 +330,14 @@ public static IAsyncOperation ThenAny(this IAsyncOperation op, FuncAn operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the callback has completed. - public static IAsyncOperation ThenAny(this IAsyncOperation op, Func>> successCallback) + public static IAsyncOperation ThenAny(this IAsyncOperation op, Func>> successCallback) { if (successCallback == null) { throw new ArgumentNullException(nameof(successCallback)); } - throw new NotImplementedException(); + return new ThenAnyResult(op, successCallback, null); } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs new file mode 100644 index 0000000..92811e0 --- /dev/null +++ b/src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs @@ -0,0 +1,66 @@ +// 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.Linq; + +namespace UnityFx.Async +{ + internal class ThenAnyResult : ThenResult + { + #region data + + private IAsyncOperation _op2; + + #endregion + + #region interface + + public ThenAnyResult(IAsyncOperation op, object successCallback, Action errorCallback) + : base(op, successCallback, errorCallback) + { + } + + #endregion + + #region ThenAnyResult + + protected override void InvokeSuccessCallback(IAsyncOperation op, bool completedSynchronously, object continuation) + { + switch (continuation) + { + case Func>> f1: + _op2 = new WhenAnyResult(f1().ToArray()); + break; + + case Func> f2: + _op2 = new WhenAnyResult(f2().ToArray()); + break; + + case Func>> f3: + _op2 = new WhenAnyResult(f3((op as IAsyncOperation).Result).ToArray()); + break; + + case Func> f4: + _op2 = new WhenAnyResult(f4((op as IAsyncOperation).Result).ToArray()); + break; + } + + if (_op2 != null) + { + _op2.AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); + } + else + { + TrySetCanceled(completedSynchronously); + } + + } + + #endregion + + #region implementation + #endregion + } +} diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs index 22ad682..b58ed70 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs @@ -28,23 +28,55 @@ public ThenResult(IAsyncOperation op, object successCallback, Action } } + protected virtual void InvokeSuccessCallback(IAsyncOperation op, bool completedSynchronously, object continuation) + { + switch (continuation) + { + case Action a: + a.Invoke(); + TrySetCompleted(completedSynchronously); + break; + + case Action a1: + a1.Invoke((op as IAsyncOperation).Result); + TrySetCompleted(completedSynchronously); + break; + + case Func> f3: + f3().AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); + break; + + case Func f1: + f1().AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); + break; + + case Func> f4: + f4((op as IAsyncOperation).Result).AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); + break; + + case Func f2: + f2((op as IAsyncOperation).Result).AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); + break; + + default: + TrySetCanceled(completedSynchronously); + break; + } + } + #endregion #region PromiseResult - protected override void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously) + protected sealed override void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously) { if (op.IsCompletedSuccessfully) { - if (InvokeSuccessCallback(op)) - { - TrySetCompleted(completedSynchronously); - } + InvokeSuccessCallback(op, completedSynchronously, _successCallback); } else { - InvokeErrorCallback(op); - TrySetException(op.Exception, completedSynchronously); + InvokeErrorCallback(op, completedSynchronously); } } @@ -73,49 +105,10 @@ private void InvokeInternal(IAsyncOperation op, bool completedSynchronously) } } - private bool InvokeSuccessCallback(IAsyncOperation op) - { - var result = false; - - switch (_successCallback) - { - case Action a: - a.Invoke(); - result = true; - break; - - case Action a1: - a1.Invoke((op as IAsyncOperation).Result); - result = true; - break; - - case Func> f3: - f3().AddCompletionCallback(op2 => TryCopyCompletionState(op2 as IAsyncOperation, false), null); - break; - - case Func f1: - f1().AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); - break; - - case Func> f4: - f4((op as IAsyncOperation).Result).AddCompletionCallback(op2 => TryCopyCompletionState(op2 as IAsyncOperation, false), null); - break; - - case Func f2: - f2((op as IAsyncOperation).Result).AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); - break; - - default: - // Should not get here. - throw new InvalidOperationException(); - } - - return result; - } - - private void InvokeErrorCallback(IAsyncOperation op) + private void InvokeErrorCallback(IAsyncOperation op, bool completedSynchronously) { _errorCallback?.Invoke(op.Exception.InnerException); + TrySetException(op.Exception, completedSynchronously); } #endregion From 606dc9d2296da7ae6d9159778138d4b4057733e5 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Tue, 3 Apr 2018 19:59:19 +0300 Subject: [PATCH 052/128] ThenAll implemented --- .../Extensions/AsyncExtensions.Promises.cs | 8 +-- .../Continuations/ThenAllResult{T,U}.cs | 66 +++++++++++++++++++ .../Continuations/ThenAnyResult{T,U}.cs | 23 ++++++- 3 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 src/UnityFx.Async/Implementation/Continuations/ThenAllResult{T,U}.cs diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs index 6506849..0fb8768 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs @@ -221,7 +221,7 @@ public static IAsyncOperation ThenAll(this IAsyncOperation op, Func(op, successCallback, null); } /// @@ -237,7 +237,7 @@ public static IAsyncOperation ThenAll(this IAsyncOperation op, Func(op, successCallback, null); } /// @@ -253,7 +253,7 @@ public static IAsyncOperation ThenAll(this IAsyncOperation op, Func(op, successCallback, null); } /// @@ -269,7 +269,7 @@ public static IAsyncOperation ThenAll(this IAsyncOperation op, Fun throw new ArgumentNullException(nameof(successCallback)); } - throw new NotImplementedException(); + return new ThenAllResult(op, successCallback, null); } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenAllResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ThenAllResult{T,U}.cs new file mode 100644 index 0000000..10ec5b4 --- /dev/null +++ b/src/UnityFx.Async/Implementation/Continuations/ThenAllResult{T,U}.cs @@ -0,0 +1,66 @@ +// 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.Linq; + +namespace UnityFx.Async +{ + internal class ThenAllResult : ThenResult + { + #region data + + private IAsyncOperation _op2; + + #endregion + + #region interface + + public ThenAllResult(IAsyncOperation op, object successCallback, Action errorCallback) + : base(op, successCallback, errorCallback) + { + } + + #endregion + + #region ThenAnyResult + + protected override void InvokeSuccessCallback(IAsyncOperation op, bool completedSynchronously, object continuation) + { + switch (continuation) + { + case Func>> f1: + _op2 = new WhenAllResult(f1().ToArray()); + break; + + case Func> f2: + _op2 = new WhenAllResult(f2().ToArray()); + break; + + case Func>> f3: + _op2 = new WhenAllResult(f3((op as IAsyncOperation).Result).ToArray()); + break; + + case Func> f4: + _op2 = new WhenAllResult(f4((op as IAsyncOperation).Result).ToArray()); + break; + } + + if (_op2 != null) + { + _op2.AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); + } + else + { + TrySetCanceled(completedSynchronously); + } + + } + + #endregion + + #region implementation + #endregion + } +} diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs index 92811e0..8b29379 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs @@ -49,7 +49,28 @@ protected override void InvokeSuccessCallback(IAsyncOperation op, bool completed if (_op2 != null) { - _op2.AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); + _op2.AddCompletionCallback( + op2 => + { + if (op2.IsCompletedSuccessfully) + { + var op3 = (op2 as IAsyncOperation).Result; + + if (op3 is IAsyncOperation op4) + { + TrySetResult(op4.Result, false); + } + else + { + TrySetCompleted(false); + } + } + else + { + TrySetException(op2.Exception, false); + } + }, + null); } else { From ae4e42c5ba12cf099484519fd6f97ec0b1617321 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 4 Apr 2018 10:36:31 +0300 Subject: [PATCH 053/128] Added ExecuteSynchronously continuation option to match .NET options --- .../Tests/AsyncExtensionsTests.ContinueWith.cs | 14 ++++++++++++++ .../Tests/AsyncExtensionsTests.cs | 8 +------- .../Extensions/AsyncExtensions.Continuations.cs | 16 ++++++++-------- .../Api/Interfaces/IAsyncContinuation.cs | 9 +++++---- .../Continuations/ContinueWithResult{T,U}.cs | 2 +- .../Continuations/ContinueWithResult{T}.cs | 2 +- 6 files changed, 30 insertions(+), 21 deletions(-) create mode 100644 src/UnityFx.Async.Tests/Tests/AsyncExtensionsTests.ContinueWith.cs diff --git a/src/UnityFx.Async.Tests/Tests/AsyncExtensionsTests.ContinueWith.cs b/src/UnityFx.Async.Tests/Tests/AsyncExtensionsTests.ContinueWith.cs new file mode 100644 index 0000000..7d3651e --- /dev/null +++ b/src/UnityFx.Async.Tests/Tests/AsyncExtensionsTests.ContinueWith.cs @@ -0,0 +1,14 @@ +// 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 +{ + partial class AsyncExtensionsTests + { + } +} diff --git a/src/UnityFx.Async.Tests/Tests/AsyncExtensionsTests.cs b/src/UnityFx.Async.Tests/Tests/AsyncExtensionsTests.cs index ab3ca7f..057ce19 100644 --- a/src/UnityFx.Async.Tests/Tests/AsyncExtensionsTests.cs +++ b/src/UnityFx.Async.Tests/Tests/AsyncExtensionsTests.cs @@ -8,7 +8,7 @@ namespace UnityFx.Async { - public class AsyncExtensionsTests + public partial class AsyncExtensionsTests { #region ToObservable @@ -92,12 +92,6 @@ public void ToObservable_OnErrorIsCalled() #endregion - #region ContinueWith - - // TODO - - #endregion - #region ToTask [Fact] diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs index f50d8da..03658ba 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs @@ -85,7 +85,7 @@ public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperation /// An operation that is executed after completes. public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action action) { - return ContinueWith(op, action, AsyncContinuationOptions.CaptureSynchronizationContext); + return ContinueWith(op, action, AsyncContinuationOptions.None); } /// @@ -116,7 +116,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, ActionAn operation that is executed after completes. public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action action, object userState) { - return ContinueWith(op, action, userState, AsyncContinuationOptions.CaptureSynchronizationContext); + return ContinueWith(op, action, userState, AsyncContinuationOptions.None); } /// @@ -147,7 +147,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, ActionAn operation that is executed after completes. public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func action) { - return ContinueWith(op, action, AsyncContinuationOptions.CaptureSynchronizationContext); + return ContinueWith(op, action, AsyncContinuationOptions.None); } /// @@ -178,7 +178,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperatio /// An operation that is executed after completes. public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func action, object userState) { - return ContinueWith(op, action, userState, AsyncContinuationOptions.CaptureSynchronizationContext); + return ContinueWith(op, action, userState, AsyncContinuationOptions.None); } /// @@ -209,7 +209,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperatio /// An operation that is executed after completes. public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action> action) { - return ContinueWith(op, action, AsyncContinuationOptions.CaptureSynchronizationContext); + return ContinueWith(op, action, AsyncContinuationOptions.None); } /// @@ -240,7 +240,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperationAn operation that is executed after completes. public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action, object> action, object userState) { - return ContinueWith(op, action, userState, AsyncContinuationOptions.CaptureSynchronizationContext); + return ContinueWith(op, action, userState, AsyncContinuationOptions.None); } /// @@ -271,7 +271,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperationAn operation that is executed after completes. public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func, TNewResult> action) { - return ContinueWith(op, action, AsyncContinuationOptions.CaptureSynchronizationContext); + return ContinueWith(op, action, AsyncContinuationOptions.None); } /// @@ -302,7 +302,7 @@ public static IAsyncOperation ContinueWith(this /// 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.CaptureSynchronizationContext); + return ContinueWith(op, action, userState, AsyncContinuationOptions.None); } /// diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs index 2c6fbb9..139f5b2 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs @@ -1,4 +1,4 @@ -// Copyright (c) Alexander Bogarsukov. + // Copyright (c) Alexander Bogarsukov. // Licensed under the MIT license. See the LICENSE.md file in the project root for more information. using System; @@ -15,7 +15,7 @@ public enum AsyncContinuationOptions { /// /// When no continuation options are specified, specifies that default behavior should be used when executing a continuation. - /// I.e. continuation is scheduled independently of the operation completion status. + /// I.e. continuation is scheduled оn the same that was active when the continuation was created. /// None = 0, @@ -50,9 +50,10 @@ public enum AsyncContinuationOptions OnlyOnCanceled = NotOnRanToCompletion | NotOnFaulted, /// - /// Specifies whether a should be captured when a continuation is registered. + /// Specifies that the continuation should be executed synchronously. With this option specified, the continuation runs on + /// the same thread that causes the antecedent task to transition into its final state. /// - CaptureSynchronizationContext = 8 + ExecuteSynchronously = 8 } /// diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs index 99ba47c..dfa24a8 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs @@ -18,7 +18,7 @@ internal class ContinueWithResult : ContinuationResult, IAsyncContinuat #region interface internal ContinueWithResult(IAsyncOperation op, AsyncContinuationOptions options, object continuation, object userState) - : base((options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0) + : base((options & AsyncContinuationOptions.ExecuteSynchronously) == 0) { _options = options; _continuation = continuation; diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs index b1e8d08..3723eb9 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs @@ -18,7 +18,7 @@ internal class ContinueWithResult : ContinuationResult, IAsyncContinuation #region interface internal ContinueWithResult(IAsyncOperation op, AsyncContinuationOptions options, object continuation, object userState) - : base((options & AsyncContinuationOptions.CaptureSynchronizationContext) != 0) + : base((options & AsyncContinuationOptions.ExecuteSynchronously) == 0) { _options = options; _continuation = continuation; From 39aecd9da82940816d4bd514cfd834130d68575b Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 4 Apr 2018 11:35:44 +0300 Subject: [PATCH 054/128] Added ContinueWith tests --- .../Tests/AsyncExtensionsTests.cs | 2 +- .../Tests/ContinueWithTests.cs | 106 ++++++++++++++++++ ...sTests.ContinueWith.cs => PromiseTests.cs} | 2 +- .../Api/Interfaces/IAsyncContinuation.cs | 2 +- 4 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 src/UnityFx.Async.Tests/Tests/ContinueWithTests.cs rename src/UnityFx.Async.Tests/Tests/{AsyncExtensionsTests.ContinueWith.cs => PromiseTests.cs} (87%) diff --git a/src/UnityFx.Async.Tests/Tests/AsyncExtensionsTests.cs b/src/UnityFx.Async.Tests/Tests/AsyncExtensionsTests.cs index 057ce19..dcab222 100644 --- a/src/UnityFx.Async.Tests/Tests/AsyncExtensionsTests.cs +++ b/src/UnityFx.Async.Tests/Tests/AsyncExtensionsTests.cs @@ -8,7 +8,7 @@ namespace UnityFx.Async { - public partial class AsyncExtensionsTests + public class AsyncExtensionsTests { #region ToObservable diff --git a/src/UnityFx.Async.Tests/Tests/ContinueWithTests.cs b/src/UnityFx.Async.Tests/Tests/ContinueWithTests.cs new file mode 100644 index 0000000..4a8b1ae --- /dev/null +++ b/src/UnityFx.Async.Tests/Tests/ContinueWithTests.cs @@ -0,0 +1,106 @@ +// 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 ContinuWithTests + { + [Theory] + [InlineData(AsyncContinuationOptions.None, true)] + [InlineData(AsyncContinuationOptions.NotOnRanToCompletion, false)] + [InlineData(AsyncContinuationOptions.NotOnFaulted, true)] + [InlineData(AsyncContinuationOptions.NotOnCanceled, true)] + [InlineData(AsyncContinuationOptions.OnlyOnRanToCompletion, true)] + [InlineData(AsyncContinuationOptions.OnlyOnFaulted, false)] + [InlineData(AsyncContinuationOptions.OnlyOnCanceled, false)] + public async Task ContinueWth_ExecutesOnAntecedentSuccess(AsyncContinuationOptions options, bool expectedCalled) + { + // Arrange + var op = AsyncResult.Delay(1); + var continuationCalled = false; + var continuationCanceled = false; + var continuation = op.ContinueWith(o => continuationCalled = true, options); + + // Act + try + { + await continuation; + } + catch (OperationCanceledException) + { + continuationCanceled = true; + } + + // Assert + Assert.Equal(expectedCalled, continuationCalled); + Assert.Equal(!expectedCalled, continuationCanceled); + } + + [Theory] + [InlineData(AsyncContinuationOptions.None, true)] + [InlineData(AsyncContinuationOptions.NotOnRanToCompletion, true)] + [InlineData(AsyncContinuationOptions.NotOnFaulted, false)] + [InlineData(AsyncContinuationOptions.NotOnCanceled, true)] + [InlineData(AsyncContinuationOptions.OnlyOnRanToCompletion, false)] + [InlineData(AsyncContinuationOptions.OnlyOnFaulted, true)] + [InlineData(AsyncContinuationOptions.OnlyOnCanceled, false)] + public async Task ContinueWth_ExecutesOnAntecedentFailure(AsyncContinuationOptions options, bool expectedCalled) + { + // Arrange + var op = AsyncResult.FromException(new Exception()); + var continuationCalled = false; + var continuationCanceled = false; + var continuation = op.ContinueWith(o => continuationCalled = true, options); + + // Act + try + { + await continuation; + } + catch (OperationCanceledException) + { + continuationCanceled = true; + } + + // Assert + Assert.Equal(expectedCalled, continuationCalled); + Assert.Equal(!expectedCalled, continuationCanceled); + } + + [Theory] + [InlineData(AsyncContinuationOptions.None, true)] + [InlineData(AsyncContinuationOptions.NotOnRanToCompletion, true)] + [InlineData(AsyncContinuationOptions.NotOnFaulted, true)] + [InlineData(AsyncContinuationOptions.NotOnCanceled, false)] + [InlineData(AsyncContinuationOptions.OnlyOnRanToCompletion, false)] + [InlineData(AsyncContinuationOptions.OnlyOnFaulted, false)] + [InlineData(AsyncContinuationOptions.OnlyOnCanceled, true)] + public async Task ContinueWth_ExecutesOnAntecedentCancellation(AsyncContinuationOptions options, bool expectedCalled) + { + // Arrange + var op = AsyncResult.FromCanceled(); + var continuationCalled = false; + var continuationCanceled = false; + var continuation = op.ContinueWith(o => continuationCalled = true, options); + + // Act + try + { + await continuation; + } + catch (OperationCanceledException) + { + continuationCanceled = true; + } + + // Assert + Assert.Equal(expectedCalled, continuationCalled); + Assert.Equal(!expectedCalled, continuationCanceled); + } + } +} diff --git a/src/UnityFx.Async.Tests/Tests/AsyncExtensionsTests.ContinueWith.cs b/src/UnityFx.Async.Tests/Tests/PromiseTests.cs similarity index 87% rename from src/UnityFx.Async.Tests/Tests/AsyncExtensionsTests.ContinueWith.cs rename to src/UnityFx.Async.Tests/Tests/PromiseTests.cs index 7d3651e..1dbe225 100644 --- a/src/UnityFx.Async.Tests/Tests/AsyncExtensionsTests.ContinueWith.cs +++ b/src/UnityFx.Async.Tests/Tests/PromiseTests.cs @@ -8,7 +8,7 @@ namespace UnityFx.Async { - partial class AsyncExtensionsTests + public class PromiseTests { } } diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs index 139f5b2..115b683 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs @@ -51,7 +51,7 @@ public enum AsyncContinuationOptions /// /// Specifies that the continuation should be executed synchronously. With this option specified, the continuation runs on - /// the same thread that causes the antecedent task to transition into its final state. + /// the same thread that causes the antecedent operation to transition into its final state. /// ExecuteSynchronously = 8 } From acce58e2d381db2d7c3460a04cc00ba059bae948 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 4 Apr 2018 12:21:49 +0300 Subject: [PATCH 055/128] Changed tests project structure --- .../Tests/AsyncResultTests.cs | 500 ------------------ .../Tests/{PromiseTests.cs => CatchTests.cs} | 2 +- .../Tests/CompletionCallbackTests.cs | 72 +++ .../Tests/CompletionSourceTests.cs | 481 +++++++++++++++++ src/UnityFx.Async.Tests/Tests/FinallyTests.cs | 14 + src/UnityFx.Async.Tests/Tests/ThenAllTests.cs | 14 + src/UnityFx.Async.Tests/Tests/ThenAnyTests.cs | 14 + src/UnityFx.Async.Tests/Tests/ThenTests.cs | 14 + ...xtensionsTests.cs => ToObservableTests.cs} | 46 +- src/UnityFx.Async.Tests/Tests/ToTaskTests.cs | 50 ++ .../UnityFx.Async.Tests.csproj | 1 + 11 files changed, 662 insertions(+), 546 deletions(-) rename src/UnityFx.Async.Tests/Tests/{PromiseTests.cs => CatchTests.cs} (90%) create mode 100644 src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs create mode 100644 src/UnityFx.Async.Tests/Tests/CompletionSourceTests.cs create mode 100644 src/UnityFx.Async.Tests/Tests/FinallyTests.cs create mode 100644 src/UnityFx.Async.Tests/Tests/ThenAllTests.cs create mode 100644 src/UnityFx.Async.Tests/Tests/ThenAnyTests.cs create mode 100644 src/UnityFx.Async.Tests/Tests/ThenTests.cs rename src/UnityFx.Async.Tests/Tests/{AsyncExtensionsTests.cs => ToObservableTests.cs} (69%) create mode 100644 src/UnityFx.Async.Tests/Tests/ToTaskTests.cs diff --git a/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs b/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs index b34baf1..dcb3cc6 100644 --- a/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs +++ b/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs @@ -423,506 +423,6 @@ public async Task Await_ShouldThrowIfCanceled() #endregion - #region IAsyncCompletionSource - - #region SetCanceled/TrySetCanceled - - [Theory] - [InlineData(AsyncOperationStatus.RanToCompletion)] - [InlineData(AsyncOperationStatus.Faulted)] - [InlineData(AsyncOperationStatus.Canceled)] - public void SetCanceled_ThrowsIfOperationIsCompleted(AsyncOperationStatus status) - { - // Arrange - var op = new AsyncCompletionSource(status); - - // Act/Assert - Assert.Throws(() => op.SetCanceled()); - Assert.True(op.CompletedSynchronously); - } - - [Theory] - [InlineData(AsyncOperationStatus.Created)] - [InlineData(AsyncOperationStatus.Scheduled)] - [InlineData(AsyncOperationStatus.Running)] - public void TrySetCanceled_SetsStatusToCanceled(AsyncOperationStatus status) - { - // Arrange - var op = new AsyncCompletionSource(status); - - // Act - var result = op.TrySetCanceled(); - - // Assert - AssertCanceled(op); - Assert.True(result); - } - - [Fact] - public void TrySetCanceled_RaisesCompletionCallbacks() - { - // Arrange - var asyncCallbackCalled1 = false; - var asyncCallbackCalled2 = false; - var op = new AsyncCompletionSource(asyncResult => asyncCallbackCalled1 = true, null); - op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, null); - - // Act - op.TrySetCanceled(); - - // Assert - Assert.True(asyncCallbackCalled1); - Assert.True(asyncCallbackCalled2); - } - - [Fact] - public void TrySetCanceled_CallsOnCompleted() - { - // Arrange - var op = new AsyncResultOverrides(); - - // Act - op.TrySetCanceled(); - - // Assert - Assert.True(op.OnCompletedCalled); - Assert.True(op.OnStatusChangedCalled); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void TrySetCanceled_SetsCompletedSynchronously(bool completedSynchronously) - { - // Arrange - var op = new AsyncCompletionSource(); - - // Act - op.TrySetCanceled(completedSynchronously); - - // Assert - Assert.Equal(completedSynchronously, op.CompletedSynchronously); - } - - [Fact] - public void TrySetCanceled_FailsIfOperationIsCompleted() - { - // Arrange - var op = new AsyncCompletionSource(AsyncOperationStatus.RanToCompletion); - - // Act - var result = op.TrySetCanceled(); - - // Assert - Assert.False(result); - Assert.True(op.CompletedSynchronously); - AssertCompleted(op); - } - - [Fact] - public void TrySetCanceled_ThrowsIfOperationIsDisposed() - { - // Arrange - var op = new AsyncCompletionSource(AsyncOperationStatus.RanToCompletion); - op.Dispose(); - - // Act/Assert - Assert.Throws(() => op.TrySetCanceled()); - } - - #endregion - - #region SetException/TrySetException - - [Theory] - [InlineData(AsyncOperationStatus.RanToCompletion)] - [InlineData(AsyncOperationStatus.Faulted)] - [InlineData(AsyncOperationStatus.Canceled)] - public void SetException_ThrowsIfOperationIsCompleted(AsyncOperationStatus status) - { - // Arrange - var e = new Exception(); - var op = new AsyncCompletionSource(status); - - // Act/Assert - Assert.Throws(() => op.SetException(e)); - Assert.True(op.CompletedSynchronously); - } - - [Theory] - [InlineData(AsyncOperationStatus.Created)] - [InlineData(AsyncOperationStatus.Scheduled)] - [InlineData(AsyncOperationStatus.Running)] - public void TrySetException_SetsStatusToFaulted(AsyncOperationStatus status) - { - // Arrange - var e = new Exception(); - var op = new AsyncCompletionSource(status); - - // Act - var result = op.TrySetException(e); - - // Assert - AssertFaulted(op, e); - Assert.True(result); - } - - [Theory] - [InlineData(AsyncOperationStatus.Created)] - [InlineData(AsyncOperationStatus.Scheduled)] - [InlineData(AsyncOperationStatus.Running)] - public void TrySetException_SetsStatusToCanceled(AsyncOperationStatus status) - { - // Arrange - var e = new OperationCanceledException(); - var op = new AsyncCompletionSource(status); - - // Act - var result = op.TrySetException(e); - - // Assert - AssertCanceled(op); - Assert.True(result); - } - - [Fact] - public void TrySetException_RaisesCompletionCallbacks() - { - // Arrange - var e = new Exception(); - var asyncCallbackCalled1 = false; - var asyncCallbackCalled2 = false; - var op = new AsyncCompletionSource(asyncResult => asyncCallbackCalled1 = true, null); - op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, null); - - // Act - op.TrySetException(e); - - // Assert - Assert.True(asyncCallbackCalled1); - Assert.True(asyncCallbackCalled2); - } - - [Fact] - public void TrySetException_CallsOnCompleted() - { - // Arrange - var e = new Exception(); - var op = new AsyncResultOverrides(); - - // Act - op.TrySetException(e); - - // Assert - Assert.Equal(e, op.OnCompletedException); - Assert.True(op.OnCompletedCalled); - Assert.True(op.OnStatusChangedCalled); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void TrySetException_SetsCompletedSynchronously(bool completedSynchronously) - { - // Arrange - var e = new Exception(); - var op = new AsyncCompletionSource(); - - // Act - op.TrySetException(e, completedSynchronously); - - // Assert - Assert.Equal(completedSynchronously, op.CompletedSynchronously); - } - - [Fact] - public void TrySetException_FailsIfOperationIsCompleted() - { - // Arrange - var op = new AsyncCompletionSource(AsyncOperationStatus.RanToCompletion); - - // Act - var result = op.TrySetCanceled(); - - // Assert - Assert.False(result); - Assert.True(op.CompletedSynchronously); - AssertCompleted(op); - } - - [Fact] - public void TrySetException_ThrowsIfExceptionIsNull() - { - // Arrange - var op = new AsyncCompletionSource(); - - // Act/Assert - Assert.Throws(() => op.TrySetException(null)); - } - - [Fact] - public void TrySetException_ThrowsIfOperationIsDisposed() - { - // Arrange - var e = new Exception(); - var op = new AsyncCompletionSource(AsyncOperationStatus.RanToCompletion); - op.Dispose(); - - // Act/Assert - Assert.Throws(() => op.TrySetException(e)); - } - - #endregion - - #region SetCompleted/TrySetCompleted - - [Theory] - [InlineData(AsyncOperationStatus.RanToCompletion)] - [InlineData(AsyncOperationStatus.Faulted)] - [InlineData(AsyncOperationStatus.Canceled)] - public void SetCompleted_ThrowsIfOperationIsCompleted(AsyncOperationStatus status) - { - // Arrange - var op = new AsyncCompletionSource(status); - - // Act/Assert - Assert.Throws(() => op.SetCompleted()); - Assert.True(op.CompletedSynchronously); - } - - [Theory] - [InlineData(AsyncOperationStatus.Created)] - [InlineData(AsyncOperationStatus.Scheduled)] - [InlineData(AsyncOperationStatus.Running)] - public void TrySetCompleted_SetsStatusToRanToCompletion(AsyncOperationStatus status) - { - // Arrange - var op = new AsyncCompletionSource(status); - - // Act - var result = op.TrySetCompleted(); - - // Assert - AssertCompleted(op); - Assert.True(result); - } - - [Fact] - public void TrySetCompleted_RaisesCompletionCallbacks() - { - // Arrange - var asyncCallbackCalled1 = false; - var asyncCallbackCalled2 = false; - var op = new AsyncCompletionSource(asyncResult => asyncCallbackCalled1 = true, null); - op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, null); - - // Act - op.TrySetCompleted(); - - // Assert - Assert.True(asyncCallbackCalled1); - Assert.True(asyncCallbackCalled2); - } - - [Fact] - public void TrySetCompleted_CallsOnCompleted() - { - // Arrange - var op = new AsyncResultOverrides(); - - // Act - op.TrySetCompleted(); - - // Assert - Assert.True(op.OnCompletedCalled); - Assert.True(op.OnStatusChangedCalled); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void TrySetCompleted_SetsCompletedSynchronously(bool completedSynchronously) - { - // Arrange - var op = new AsyncCompletionSource(); - - // Act - op.TrySetCompleted(completedSynchronously); - - // Assert - Assert.Equal(completedSynchronously, op.CompletedSynchronously); - } - - [Fact] - public void TrySetCompleted_FailsIfOperationIsCompleted() - { - // Arrange - var op = new AsyncCompletionSource(AsyncOperationStatus.Canceled); - - // Act - var result = op.TrySetCompleted(); - - // Assert - Assert.False(result); - Assert.True(op.CompletedSynchronously); - AssertCanceled(op); - } - - [Fact] - public void TrySetCompleted_ThrowsIfOperationIsDisposed() - { - // Arrange - var op = new AsyncCompletionSource(AsyncOperationStatus.Canceled); - op.Dispose(); - - // Act/Assert - Assert.Throws(() => op.TrySetCompleted()); - } - - #endregion - - #region SetResult/TrySetResult - - [Theory] - [InlineData(AsyncOperationStatus.RanToCompletion)] - [InlineData(AsyncOperationStatus.Faulted)] - [InlineData(AsyncOperationStatus.Canceled)] - public void SetResult_ThrowsIfOperationIsCompleted(AsyncOperationStatus status) - { - // Arrange - var op = new AsyncCompletionSource(status); - - // Act/Assert - Assert.Throws(() => op.SetResult(10)); - Assert.True(op.CompletedSynchronously); - } - - [Theory] - [InlineData(AsyncOperationStatus.Created)] - [InlineData(AsyncOperationStatus.Scheduled)] - [InlineData(AsyncOperationStatus.Running)] - public void TrySetResult_SetsStatusToRanToCompletion(AsyncOperationStatus status) - { - // Arrange - var resultValue = new object(); - var op = new AsyncCompletionSource(status); - - // Act - var result = op.TrySetResult(resultValue); - - // Assert - AssertCompletedWithResult(op, resultValue); - Assert.True(result); - } - - [Fact] - public void TrySetResult_RaisesCompletionCallbacks() - { - // Arrange - var asyncCallbackCalled1 = false; - var asyncCallbackCalled2 = false; - var op = new AsyncCompletionSource(asyncResult => asyncCallbackCalled1 = true, null); - op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, null); - - // Act - op.TrySetResult(10); - - // Assert - Assert.True(asyncCallbackCalled1); - Assert.True(asyncCallbackCalled2); - } - - [Fact] - public void TrySetResult_CallsOnCompleted() - { - // Arrange - var op = new AsyncResultOverrides(); - - // Act - op.TrySetResult(10); - - // Assert - Assert.True(op.OnCompletedCalled); - Assert.True(op.OnStatusChangedCalled); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void TrySetResult_SetsCompletedSynchronously(bool completedSynchronously) - { - // Arrange - var result = new object(); - var op = new AsyncCompletionSource(); - - // Act - op.TrySetResult(result, completedSynchronously); - - // Assert - Assert.Equal(completedSynchronously, op.CompletedSynchronously); - } - - [Fact] - public void TrySetResult_FailsIfOperationIsCompleted() - { - // Arrange - var op = new AsyncCompletionSource(AsyncOperationStatus.Canceled); - - // Act - var result = op.TrySetResult(10); - - // Assert - Assert.False(result); - Assert.True(op.CompletedSynchronously); - AssertCanceled(op); - } - - [Fact] - public void TrySetResult_ThrowsIfOperationIsDisposed() - { - // Arrange - var op = new AsyncCompletionSource(AsyncOperationStatus.Canceled); - op.Dispose(); - - // Act/Assert - Assert.Throws(() => op.TrySetResult(15)); - } - - #endregion - - #endregion - - #region IAsyncOperationEvents - - [Fact] - public void TryAddCompletionCallback_FailsIfOperationIsCompleted() - { - // Arrange - var op = new AsyncCompletionSource(); - op.SetCanceled(); - - // Act - var result = op.TryAddCompletionCallback(_ => { }, null); - - // Assert - Assert.False(result); - } - - [Fact] - public void TryAddCompletionCallback_FailsIfOperationIsCompletedSynchronously() - { - // Arrange - var op = AsyncResult.CompletedOperation; - - // Act - var result = op.TryAddCompletionCallback(_ => { }, null); - - // Assert - Assert.False(result); - } - - #endregion - #region IAsyncResult [Fact] diff --git a/src/UnityFx.Async.Tests/Tests/PromiseTests.cs b/src/UnityFx.Async.Tests/Tests/CatchTests.cs similarity index 90% rename from src/UnityFx.Async.Tests/Tests/PromiseTests.cs rename to src/UnityFx.Async.Tests/Tests/CatchTests.cs index 1dbe225..e20ceac 100644 --- a/src/UnityFx.Async.Tests/Tests/PromiseTests.cs +++ b/src/UnityFx.Async.Tests/Tests/CatchTests.cs @@ -8,7 +8,7 @@ namespace UnityFx.Async { - public class PromiseTests + public class CatchTests { } } diff --git a/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs b/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs new file mode 100644 index 0000000..2bec968 --- /dev/null +++ b/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs @@ -0,0 +1,72 @@ +// 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 NSubstitute; +using Xunit; + +namespace UnityFx.Async +{ + public class CompletionCallbackTests + { + [Fact] + public void TryAddCompletionCallback_FailsIfOperationIsCompleted() + { + // Arrange + var op = new AsyncCompletionSource(); + op.SetCanceled(); + + // Act + var result = op.TryAddCompletionCallback(_ => { }, null); + + // Assert + Assert.False(result); + } + + [Fact] + public void TryAddCompletionCallback_FailsIfOperationIsCompletedSynchronously() + { + // Arrange + var op = AsyncResult.CompletedOperation; + + // Act + var result = op.TryAddCompletionCallback(_ => { }, null); + + // Assert + Assert.False(result); + } + + [Fact] + public async Task TryAddCompletionCallback_ExecutesWhenOperationCompletes() + { + // Arrange + var op = AsyncResult.Delay(1); + var callbackCalled = false; + + op.TryAddCompletionCallback(_ => callbackCalled = true, null); + + // Act + await op; + + // Assert + Assert.True(callbackCalled); + } + + [Fact] + public void TryAddContinuation_ExecutesWhenOperationCompletes() + { + // Arrange + var op = new AsyncCompletionSource(); + var continuation = Substitute.For(); + op.TryAddContinuation(continuation); + + // Act + op.SetCompleted(); + + // Assert + continuation.Received(1).Invoke(op); + } + } +} diff --git a/src/UnityFx.Async.Tests/Tests/CompletionSourceTests.cs b/src/UnityFx.Async.Tests/Tests/CompletionSourceTests.cs new file mode 100644 index 0000000..11a98b3 --- /dev/null +++ b/src/UnityFx.Async.Tests/Tests/CompletionSourceTests.cs @@ -0,0 +1,481 @@ +// 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 CompletionSourceTests + { + #region SetCanceled/TrySetCanceled + + [Theory] + [InlineData(AsyncOperationStatus.RanToCompletion)] + [InlineData(AsyncOperationStatus.Faulted)] + [InlineData(AsyncOperationStatus.Canceled)] + public void SetCanceled_ThrowsIfOperationIsCompleted(AsyncOperationStatus status) + { + // Arrange + var op = new AsyncCompletionSource(status); + + // Act/Assert + Assert.Throws(() => op.SetCanceled()); + Assert.True(op.CompletedSynchronously); + } + + [Theory] + [InlineData(AsyncOperationStatus.Created)] + [InlineData(AsyncOperationStatus.Scheduled)] + [InlineData(AsyncOperationStatus.Running)] + public void TrySetCanceled_SetsStatusToCanceled(AsyncOperationStatus status) + { + // Arrange + var op = new AsyncCompletionSource(status); + + // Act + var result = op.TrySetCanceled(); + + // Assert + Assert.True(op.IsCanceled); + Assert.True(result); + } + + [Fact] + public void TrySetCanceled_RaisesCompletionCallbacks() + { + // Arrange + var asyncCallbackCalled1 = false; + var asyncCallbackCalled2 = false; + var op = new AsyncCompletionSource(asyncResult => asyncCallbackCalled1 = true, null); + op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, null); + + // Act + op.TrySetCanceled(); + + // Assert + Assert.True(asyncCallbackCalled1); + Assert.True(asyncCallbackCalled2); + } + + [Fact] + public void TrySetCanceled_CallsOnCompleted() + { + // Arrange + var op = new AsyncResultOverrides(); + + // Act + op.TrySetCanceled(); + + // Assert + Assert.True(op.OnCompletedCalled); + Assert.True(op.OnStatusChangedCalled); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void TrySetCanceled_SetsCompletedSynchronously(bool completedSynchronously) + { + // Arrange + var op = new AsyncCompletionSource(); + + // Act + op.TrySetCanceled(completedSynchronously); + + // Assert + Assert.Equal(completedSynchronously, op.CompletedSynchronously); + } + + [Fact] + public void TrySetCanceled_FailsIfOperationIsCompleted() + { + // Arrange + var op = new AsyncCompletionSource(AsyncOperationStatus.RanToCompletion); + + // Act + var result = op.TrySetCanceled(); + + // Assert + Assert.False(result); + Assert.True(op.CompletedSynchronously); + Assert.True(op.IsCompletedSuccessfully); + } + + [Fact] + public void TrySetCanceled_ThrowsIfOperationIsDisposed() + { + // Arrange + var op = new AsyncCompletionSource(AsyncOperationStatus.RanToCompletion); + op.Dispose(); + + // Act/Assert + Assert.Throws(() => op.TrySetCanceled()); + } + + #endregion + + #region SetException/TrySetException + + [Theory] + [InlineData(AsyncOperationStatus.RanToCompletion)] + [InlineData(AsyncOperationStatus.Faulted)] + [InlineData(AsyncOperationStatus.Canceled)] + public void SetException_ThrowsIfOperationIsCompleted(AsyncOperationStatus status) + { + // Arrange + var e = new Exception(); + var op = new AsyncCompletionSource(status); + + // Act/Assert + Assert.Throws(() => op.SetException(e)); + Assert.True(op.CompletedSynchronously); + } + + [Theory] + [InlineData(AsyncOperationStatus.Created)] + [InlineData(AsyncOperationStatus.Scheduled)] + [InlineData(AsyncOperationStatus.Running)] + public void TrySetException_SetsStatusToFaulted(AsyncOperationStatus status) + { + // Arrange + var e = new Exception(); + var op = new AsyncCompletionSource(status); + + // Act + var result = op.TrySetException(e); + + // Assert + Assert.True(op.IsFaulted); + Assert.Equal(e, op.Exception.InnerException); + Assert.True(result); + } + + [Theory] + [InlineData(AsyncOperationStatus.Created)] + [InlineData(AsyncOperationStatus.Scheduled)] + [InlineData(AsyncOperationStatus.Running)] + public void TrySetException_SetsStatusToCanceled(AsyncOperationStatus status) + { + // Arrange + var e = new OperationCanceledException(); + var op = new AsyncCompletionSource(status); + + // Act + var result = op.TrySetException(e); + + // Assert + Assert.True(op.IsCanceled); + Assert.True(result); + Assert.Equal(e, op.Exception.InnerException); + } + + [Fact] + public void TrySetException_RaisesCompletionCallbacks() + { + // Arrange + var e = new Exception(); + var asyncCallbackCalled1 = false; + var asyncCallbackCalled2 = false; + var op = new AsyncCompletionSource(asyncResult => asyncCallbackCalled1 = true, null); + op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, null); + + // Act + op.TrySetException(e); + + // Assert + Assert.True(asyncCallbackCalled1); + Assert.True(asyncCallbackCalled2); + } + + [Fact] + public void TrySetException_CallsOnCompleted() + { + // Arrange + var e = new Exception(); + var op = new AsyncResultOverrides(); + + // Act + op.TrySetException(e); + + // Assert + Assert.Equal(e, op.OnCompletedException); + Assert.True(op.OnCompletedCalled); + Assert.True(op.OnStatusChangedCalled); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void TrySetException_SetsCompletedSynchronously(bool completedSynchronously) + { + // Arrange + var e = new Exception(); + var op = new AsyncCompletionSource(); + + // Act + op.TrySetException(e, completedSynchronously); + + // Assert + Assert.Equal(completedSynchronously, op.CompletedSynchronously); + } + + [Fact] + public void TrySetException_FailsIfOperationIsCompleted() + { + // Arrange + var op = new AsyncCompletionSource(AsyncOperationStatus.RanToCompletion); + + // Act + var result = op.TrySetCanceled(); + + // Assert + Assert.False(result); + Assert.True(op.CompletedSynchronously); + Assert.True(op.IsCompletedSuccessfully); + } + + [Fact] + public void TrySetException_ThrowsIfExceptionIsNull() + { + // Arrange + var op = new AsyncCompletionSource(); + + // Act/Assert + Assert.Throws(() => op.TrySetException(null)); + } + + [Fact] + public void TrySetException_ThrowsIfOperationIsDisposed() + { + // Arrange + var e = new Exception(); + var op = new AsyncCompletionSource(AsyncOperationStatus.RanToCompletion); + op.Dispose(); + + // Act/Assert + Assert.Throws(() => op.TrySetException(e)); + } + + #endregion + + #region SetCompleted/TrySetCompleted + + [Theory] + [InlineData(AsyncOperationStatus.RanToCompletion)] + [InlineData(AsyncOperationStatus.Faulted)] + [InlineData(AsyncOperationStatus.Canceled)] + public void SetCompleted_ThrowsIfOperationIsCompleted(AsyncOperationStatus status) + { + // Arrange + var op = new AsyncCompletionSource(status); + + // Act/Assert + Assert.Throws(() => op.SetCompleted()); + Assert.True(op.CompletedSynchronously); + } + + [Theory] + [InlineData(AsyncOperationStatus.Created)] + [InlineData(AsyncOperationStatus.Scheduled)] + [InlineData(AsyncOperationStatus.Running)] + public void TrySetCompleted_SetsStatusToRanToCompletion(AsyncOperationStatus status) + { + // Arrange + var op = new AsyncCompletionSource(status); + + // Act + var result = op.TrySetCompleted(); + + // Assert + Assert.True(op.IsCompletedSuccessfully); + Assert.True(result); + } + + [Fact] + public void TrySetCompleted_RaisesCompletionCallbacks() + { + // Arrange + var asyncCallbackCalled1 = false; + var asyncCallbackCalled2 = false; + var op = new AsyncCompletionSource(asyncResult => asyncCallbackCalled1 = true, null); + op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, null); + + // Act + op.TrySetCompleted(); + + // Assert + Assert.True(asyncCallbackCalled1); + Assert.True(asyncCallbackCalled2); + } + + [Fact] + public void TrySetCompleted_CallsOnCompleted() + { + // Arrange + var op = new AsyncResultOverrides(); + + // Act + op.TrySetCompleted(); + + // Assert + Assert.True(op.OnCompletedCalled); + Assert.True(op.OnStatusChangedCalled); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void TrySetCompleted_SetsCompletedSynchronously(bool completedSynchronously) + { + // Arrange + var op = new AsyncCompletionSource(); + + // Act + op.TrySetCompleted(completedSynchronously); + + // Assert + Assert.Equal(completedSynchronously, op.CompletedSynchronously); + } + + [Fact] + public void TrySetCompleted_FailsIfOperationIsCompleted() + { + // Arrange + var op = new AsyncCompletionSource(AsyncOperationStatus.Canceled); + + // Act + var result = op.TrySetCompleted(); + + // Assert + Assert.False(result); + Assert.True(op.CompletedSynchronously); + Assert.True(op.IsCanceled); + } + + [Fact] + public void TrySetCompleted_ThrowsIfOperationIsDisposed() + { + // Arrange + var op = new AsyncCompletionSource(AsyncOperationStatus.Canceled); + op.Dispose(); + + // Act/Assert + Assert.Throws(() => op.TrySetCompleted()); + } + + #endregion + + #region SetResult/TrySetResult + + [Theory] + [InlineData(AsyncOperationStatus.RanToCompletion)] + [InlineData(AsyncOperationStatus.Faulted)] + [InlineData(AsyncOperationStatus.Canceled)] + public void SetResult_ThrowsIfOperationIsCompleted(AsyncOperationStatus status) + { + // Arrange + var op = new AsyncCompletionSource(status); + + // Act/Assert + Assert.Throws(() => op.SetResult(10)); + Assert.True(op.CompletedSynchronously); + } + + [Theory] + [InlineData(AsyncOperationStatus.Created)] + [InlineData(AsyncOperationStatus.Scheduled)] + [InlineData(AsyncOperationStatus.Running)] + public void TrySetResult_SetsStatusToRanToCompletion(AsyncOperationStatus status) + { + // Arrange + var resultValue = new object(); + var op = new AsyncCompletionSource(status); + + // Act + var result = op.TrySetResult(resultValue); + + // Assert + Assert.True(op.IsCompletedSuccessfully); + Assert.Equal(resultValue, op.Result); + Assert.True(result); + } + + [Fact] + public void TrySetResult_RaisesCompletionCallbacks() + { + // Arrange + var asyncCallbackCalled1 = false; + var asyncCallbackCalled2 = false; + var op = new AsyncCompletionSource(asyncResult => asyncCallbackCalled1 = true, null); + op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, null); + + // Act + op.TrySetResult(10); + + // Assert + Assert.True(asyncCallbackCalled1); + Assert.True(asyncCallbackCalled2); + } + + [Fact] + public void TrySetResult_CallsOnCompleted() + { + // Arrange + var op = new AsyncResultOverrides(); + + // Act + op.TrySetResult(10); + + // Assert + Assert.True(op.OnCompletedCalled); + Assert.True(op.OnStatusChangedCalled); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void TrySetResult_SetsCompletedSynchronously(bool completedSynchronously) + { + // Arrange + var result = new object(); + var op = new AsyncCompletionSource(); + + // Act + op.TrySetResult(result, completedSynchronously); + + // Assert + Assert.Equal(completedSynchronously, op.CompletedSynchronously); + } + + [Fact] + public void TrySetResult_FailsIfOperationIsCompleted() + { + // Arrange + var op = new AsyncCompletionSource(AsyncOperationStatus.Canceled); + + // Act + var result = op.TrySetResult(10); + + // Assert + Assert.False(result); + Assert.True(op.CompletedSynchronously); + Assert.True(op.IsCanceled); + } + + [Fact] + public void TrySetResult_ThrowsIfOperationIsDisposed() + { + // Arrange + var op = new AsyncCompletionSource(AsyncOperationStatus.Canceled); + op.Dispose(); + + // Act/Assert + Assert.Throws(() => op.TrySetResult(15)); + } + + #endregion + } +} diff --git a/src/UnityFx.Async.Tests/Tests/FinallyTests.cs b/src/UnityFx.Async.Tests/Tests/FinallyTests.cs new file mode 100644 index 0000000..ce49617 --- /dev/null +++ b/src/UnityFx.Async.Tests/Tests/FinallyTests.cs @@ -0,0 +1,14 @@ +// 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 FinallyTests + { + } +} diff --git a/src/UnityFx.Async.Tests/Tests/ThenAllTests.cs b/src/UnityFx.Async.Tests/Tests/ThenAllTests.cs new file mode 100644 index 0000000..0c83a19 --- /dev/null +++ b/src/UnityFx.Async.Tests/Tests/ThenAllTests.cs @@ -0,0 +1,14 @@ +// 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 ThenAllTests + { + } +} diff --git a/src/UnityFx.Async.Tests/Tests/ThenAnyTests.cs b/src/UnityFx.Async.Tests/Tests/ThenAnyTests.cs new file mode 100644 index 0000000..00fc71d --- /dev/null +++ b/src/UnityFx.Async.Tests/Tests/ThenAnyTests.cs @@ -0,0 +1,14 @@ +// 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 ThenAnyTests + { + } +} diff --git a/src/UnityFx.Async.Tests/Tests/ThenTests.cs b/src/UnityFx.Async.Tests/Tests/ThenTests.cs new file mode 100644 index 0000000..eaaabf9 --- /dev/null +++ b/src/UnityFx.Async.Tests/Tests/ThenTests.cs @@ -0,0 +1,14 @@ +// 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 ThenTests + { + } +} diff --git a/src/UnityFx.Async.Tests/Tests/AsyncExtensionsTests.cs b/src/UnityFx.Async.Tests/Tests/ToObservableTests.cs similarity index 69% rename from src/UnityFx.Async.Tests/Tests/AsyncExtensionsTests.cs rename to src/UnityFx.Async.Tests/Tests/ToObservableTests.cs index dcab222..97376b1 100644 --- a/src/UnityFx.Async.Tests/Tests/AsyncExtensionsTests.cs +++ b/src/UnityFx.Async.Tests/Tests/ToObservableTests.cs @@ -8,10 +8,8 @@ namespace UnityFx.Async { - public class AsyncExtensionsTests + public class ToObservableTests { - #region ToObservable - [Fact] public void ToObservable_OnNextIsCalled() { @@ -89,47 +87,5 @@ public void ToObservable_OnErrorIsCalled() Assert.Equal(1, observer.OnErrorCount); Assert.Equal(e, observer.Exception); } - - #endregion - - #region ToTask - - [Fact] - public async Task ToTask_CompletesWhenSourceCompletes() - { - // Arrange - var op = AsyncResult.Delay(1); - var task = op.ToTask(); - - // Act - await task; - - // Assert - Assert.True(op.IsCompleted); - } - - [Fact] - public async Task ToTask_FailsWhenSourceFails() - { - // Arrange - var op = AsyncResult.Delay(1).Then(() => AsyncResult.FromException(new Exception())); - var task = op.ToTask(); - - // Act/Assert - await Assert.ThrowsAsync(() => task); - } - - [Fact] - public async Task ToTask_FailsWhenSourceIsCanceled() - { - // Arrange - var op = AsyncResult.Delay(1).Then(() => AsyncResult.FromCanceled()); - var task = op.ToTask(); - - // Act/Assert - await Assert.ThrowsAsync(() => task); - } - - #endregion } } diff --git a/src/UnityFx.Async.Tests/Tests/ToTaskTests.cs b/src/UnityFx.Async.Tests/Tests/ToTaskTests.cs new file mode 100644 index 0000000..e5c4a26 --- /dev/null +++ b/src/UnityFx.Async.Tests/Tests/ToTaskTests.cs @@ -0,0 +1,50 @@ +// 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 ToTaskTests + { + + [Fact] + public async Task ToTask_CompletesWhenSourceCompletes() + { + // Arrange + var op = AsyncResult.Delay(1); + var task = op.ToTask(); + + // Act + await task; + + // Assert + Assert.True(op.IsCompleted); + } + + [Fact] + public async Task ToTask_FailsWhenSourceFails() + { + // Arrange + var op = AsyncResult.Delay(1).Then(() => AsyncResult.FromException(new Exception())); + var task = op.ToTask(); + + // Act/Assert + await Assert.ThrowsAsync(() => task); + } + + [Fact] + public async Task ToTask_FailsWhenSourceIsCanceled() + { + // Arrange + var op = AsyncResult.Delay(1).Then(() => AsyncResult.FromCanceled()); + var task = op.ToTask(); + + // Act/Assert + await Assert.ThrowsAsync(() => task); + } + } +} diff --git a/src/UnityFx.Async.Tests/UnityFx.Async.Tests.csproj b/src/UnityFx.Async.Tests/UnityFx.Async.Tests.csproj index 39ce8ea..439a6f3 100644 --- a/src/UnityFx.Async.Tests/UnityFx.Async.Tests.csproj +++ b/src/UnityFx.Async.Tests/UnityFx.Async.Tests.csproj @@ -8,6 +8,7 @@ + From ad6cb2916cdc54631c4388bffacb1b192a2fefc0 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 4 Apr 2018 21:59:36 +0300 Subject: [PATCH 056/128] Added updatables support --- .../UnityFx.Async/Scripts/AsyncUtility.cs | 165 ++++++++++++++---- src/UnityFx.Async/Api/Core/AsyncResult.cs | 56 +++++- .../Api/Interfaces/IAsyncUpdatable.cs | 20 +++ .../Api/Interfaces/IAsyncUpdateSource.cs | 27 +++ .../{DelayResult.cs => TimerDelayResult.cs} | 4 +- .../Specialized/UpdatableDelayResult.cs | 44 +++++ 6 files changed, 282 insertions(+), 34 deletions(-) create mode 100644 src/UnityFx.Async/Api/Interfaces/IAsyncUpdatable.cs create mode 100644 src/UnityFx.Async/Api/Interfaces/IAsyncUpdateSource.cs rename src/UnityFx.Async/Implementation/Specialized/{DelayResult.cs => TimerDelayResult.cs} (90%) create mode 100644 src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs diff --git a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncUtility.cs b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncUtility.cs index cde5544..7d6415c 100644 --- a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncUtility.cs +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncUtility.cs @@ -45,6 +45,14 @@ public static GameObject GetRootGo() return _go; } + /// + /// Returns an instance of an . + /// + public static IAsyncUpdateSource GetDefaultUpdateSource() + { + return GetCoroutineRunner(); + } + /// /// Starts a coroutine. /// @@ -209,11 +217,21 @@ internal static void AddCompletionCallback(WWW request, Action completionCallbac #region implementation - private class CoroutineRunner : MonoBehaviour + private class CoroutineRunner : MonoBehaviour, IAsyncUpdateSource { + #region data + private Dictionary _ops; private List _opsToRemove; private List _updateCallbacks; + private List _updateCallbacksToRemove; + private List _updatables; + private List _updatablesToRemove; + private bool _updating; + + #endregion + + #region interface public void AddCompletionCallback(object op, Action cb) { @@ -231,6 +249,7 @@ public void AddUpdateCallback(Action updateCallback) if (_updateCallbacks == null) { _updateCallbacks = new List(); + _updateCallbacksToRemove = new List(); } _updateCallbacks.Add(updateCallback); @@ -238,65 +257,149 @@ public void AddUpdateCallback(Action updateCallback) public void RemoveUpdateCallback(Action updateCallback) { - _updateCallbacks.Remove(updateCallback); + if (_updating) + { + if (updateCallback != null) + { + _updateCallbacksToRemove.Add(updateCallback); + } + } + else + { + _updateCallbacks.Remove(updateCallback); + } } + #endregion + + #region MonoBehavoiur + private void Update() { - if (_ops != null && _ops.Count > 0) + try { - foreach (var item in _ops) + _updating = true; + + if (_ops != null && _ops.Count > 0) { - if (item.Key is AsyncOperation) + foreach (var item in _ops) { - var asyncOp = item.Key as AsyncOperation; - - if (asyncOp.isDone) + if (item.Key is AsyncOperation) { - item.Value(); - _opsToRemove.Add(asyncOp); + var asyncOp = item.Key as AsyncOperation; + + if (asyncOp.isDone) + { + item.Value(); + _opsToRemove.Add(asyncOp); + } } - } #if UNITY_5_2_OR_NEWER || UNITY_5_3_OR_NEWER || UNITY_2017 || UNITY_2018 - else if (item.Key is UnityWebRequest) - { - var asyncOp = item.Key as UnityWebRequest; + else if (item.Key is UnityWebRequest) + { + var asyncOp = item.Key as UnityWebRequest; - if (asyncOp.isDone) + if (asyncOp.isDone) + { + item.Value(); + _opsToRemove.Add(asyncOp); + } + } +#endif + else if (item.Key is WWW) { - item.Value(); - _opsToRemove.Add(asyncOp); + var asyncOp = item.Key as WWW; + + if (asyncOp.isDone) + { + item.Value(); + _opsToRemove.Add(asyncOp); + } } } -#endif - else if (item.Key is WWW) + + foreach (var item in _opsToRemove) { - var asyncOp = item.Key as WWW; + _ops.Remove(item); + } - if (asyncOp.isDone) - { - item.Value(); - _opsToRemove.Add(asyncOp); - } + _opsToRemove.Clear(); + } + + if (_updateCallbacks != null) + { + foreach (var callback in _updateCallbacks) + { + callback(); + } + + foreach (var callback in _updateCallbacksToRemove) + { + _updateCallbacksToRemove.Remove(callback); } + + _updateCallbacksToRemove.Clear(); } - foreach (var item in _opsToRemove) + if (_updatables != null) { - _ops.Remove(item); + var frameTime = Time.deltaTime; + + foreach (var item in _updatables) + { + item.Update(frameTime); + } + + foreach (var item in _updatablesToRemove) + { + _updatablesToRemove.Remove(item); + } + + _updatablesToRemove.Clear(); } + } + finally + { + _updating = false; + } + } + + #endregion + + #region IAsyncUpdateSource - _opsToRemove.Clear(); + public void AddListener(IAsyncUpdatable updatable) + { + if (updatable == null) + { + throw new ArgumentNullException("updatable"); } - if (_updateCallbacks != null) + if (_updatables == null) { - foreach (var callback in _updateCallbacks) + _updatables = new List(); + _updatablesToRemove = new List(); + } + + _updatables.Add(updatable); + } + + public void RemoveListener(IAsyncUpdatable updatable) + { + if (_updating) + { + if (updatable != null) { - callback(); + _updatablesToRemove.Add(updatable); } } + else + { + _updatables.Remove(updatable); + } } + + #endregion } private static CoroutineRunner GetCoroutineRunner() diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index a6a933f..aacb2c5 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -914,6 +914,7 @@ public static AsyncResult FromObservable(IObservable observable) /// The number of milliseconds to wait before completing the returned operation, or (-1) to wait indefinitely. /// Thrown if the is less than -1. /// An operation that represents the time delay. + /// /// public static AsyncResult Delay(int millisecondsDelay) { @@ -932,7 +933,37 @@ public static AsyncResult Delay(int millisecondsDelay) return new AsyncResult(); } - return new DelayResult(millisecondsDelay); + return new TimerDelayResult(millisecondsDelay); + } + + /// + /// Creates an operation that completes after a time delay. This method creates a more effecient operation + /// than but requires a specialized updates source. + /// + /// The number of milliseconds to wait before completing the returned operation, or (-1) to wait indefinitely. + /// Update notifications provider. + /// Thrown if is . + /// Thrown if the is less than -1. + /// An operation that represents the time delay. + /// + public static AsyncResult Delay(int millisecondsDelay, IAsyncUpdateSource updateSource) + { + if (millisecondsDelay < Timeout.Infinite) + { + throw new ArgumentOutOfRangeException(nameof(millisecondsDelay), millisecondsDelay, Constants.ErrorValueIsLessThanZero); + } + + if (millisecondsDelay == 0) + { + return CompletedOperation; + } + + if (millisecondsDelay == Timeout.Infinite) + { + return new AsyncResult(); + } + + return new UpdatableDelayResult(millisecondsDelay, updateSource); } /// @@ -941,6 +972,7 @@ public static AsyncResult Delay(int millisecondsDelay) /// The time span to wait before completing the returned operation, or TimeSpan.FromMilliseconds(-1) to wait indefinitely. /// Thrown if the represents a negative time interval other than TimeSpan.FromMillseconds(-1). /// An operation that represents the time delay. + /// /// public static AsyncResult Delay(TimeSpan delay) { @@ -954,6 +986,28 @@ public static AsyncResult Delay(TimeSpan delay) return Delay((int)millisecondsDelay); } + /// + /// Creates an operation that completes after a specified time interval. This method creates a more effecient operation + /// than but requires a specialized updates source. + /// + /// The time span to wait before completing the returned operation, or TimeSpan.FromMilliseconds(-1) to wait indefinitely. + /// Update notifications provider. + /// Thrown if is . + /// Thrown if the represents a negative time interval other than TimeSpan.FromMillseconds(-1). + /// An operation that represents the time delay. + /// + public static AsyncResult Delay(TimeSpan delay, IAsyncUpdateSource updateSource) + { + var millisecondsDelay = (long)delay.TotalMilliseconds; + + if (millisecondsDelay > int.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(delay)); + } + + return Delay((int)millisecondsDelay, updateSource); + } + #endregion #region Retry diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncUpdatable.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncUpdatable.cs new file mode 100644 index 0000000..9fb4a34 --- /dev/null +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncUpdatable.cs @@ -0,0 +1,20 @@ +// 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 +{ + /// + /// An updatable object. + /// + /// + public interface IAsyncUpdatable + { + /// + /// Updates the object state. Called by . + /// + /// Time since last call in seconds. + void Update(float frameTime); + } +} diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncUpdateSource.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncUpdateSource.cs new file mode 100644 index 0000000..8b8bdb3 --- /dev/null +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncUpdateSource.cs @@ -0,0 +1,27 @@ +// 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 provider of update notifications. + /// + /// + public interface IAsyncUpdateSource + { + /// + /// Adds a new update listener. + /// + /// An update listener. + /// Thrown is is . + void AddListener(IAsyncUpdatable updatable); + + /// + /// Removes an existing listener. + /// + /// An update listener. Can be . + void RemoveListener(IAsyncUpdatable updatable); + } +} diff --git a/src/UnityFx.Async/Implementation/Specialized/DelayResult.cs b/src/UnityFx.Async/Implementation/Specialized/TimerDelayResult.cs similarity index 90% rename from src/UnityFx.Async/Implementation/Specialized/DelayResult.cs rename to src/UnityFx.Async/Implementation/Specialized/TimerDelayResult.cs index 5953c72..7cf5321 100644 --- a/src/UnityFx.Async/Implementation/Specialized/DelayResult.cs +++ b/src/UnityFx.Async/Implementation/Specialized/TimerDelayResult.cs @@ -6,7 +6,7 @@ namespace UnityFx.Async { - internal class DelayResult : AsyncResult + internal class TimerDelayResult : AsyncResult { #region data @@ -16,7 +16,7 @@ internal class DelayResult : AsyncResult #region interface - public DelayResult(int millisecondsDelay) + public TimerDelayResult(int millisecondsDelay) : base(AsyncOperationStatus.Running) { _timer = new Timer(TimerCompletionCallback, this, millisecondsDelay, Timeout.Infinite); diff --git a/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs b/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs new file mode 100644 index 0000000..367f761 --- /dev/null +++ b/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs @@ -0,0 +1,44 @@ +// 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 UpdatableDelayResult : AsyncResult, IAsyncUpdatable + { + #region data + + private readonly IAsyncUpdateSource _updateService; + private float _timer; + + #endregion + + #region interface + + public UpdatableDelayResult(int millisecondsDelay, IAsyncUpdateSource updateSource) + : base(AsyncOperationStatus.Running) + { + _timer = millisecondsDelay; + _updateService = updateSource; + _updateService.AddListener(this); + } + + #endregion + + #region IAsyncUpdatable + + public void Update(float frameTime) + { + _timer -= frameTime; + + if (_timer <= 0) + { + TrySetCompleted(false); + _updateService.RemoveListener(this); + } + } + + #endregion + } +} From 8cf09bef991d7e6949fb8e823018a0f067e5d255 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 5 Apr 2018 17:56:58 +0300 Subject: [PATCH 057/128] CHANGELOG update --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 004fb7b..72507c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/); this proj - Added `FromTask`/`FromObservable` helpers. - Added `ToAsync` extension method for `IObservable` interface. - Added `TryAddContinuation`/`RemoveContinuation` methods to `IAsyncOperationEvents` for non-delegate continuations. +- Added `IAsyncUpdatable` and `IAsyncUpdateSource` interfaces. +- Added `Delay` overload that uses `IAsyncUpdateSource`-based service for time management. ### Changed - Changed `ContinueWith` extension signatures to match corresponding `Task` methods. From 97e60c6cb20c6b497aa10b0dc01f6a7740510bd1 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 6 Apr 2018 11:41:24 +0300 Subject: [PATCH 058/128] Added UNITYFX_NOT_THREAD_SAFE define for single-threaded applications --- src/UnityFx.Async/Api/Core/AsyncResult.cs | 230 +++++++++++++++++- .../AsyncExtensions.Continuations.cs | 3 +- .../Api/Interfaces/IAsyncContinuation.cs | 2 +- .../Api/Interfaces/IAsyncOperationEvents.cs | 16 +- .../Continuations/ThenAllResult{T,U}.cs | 1 - .../Continuations/ThenAnyResult{T,U}.cs | 1 - 6 files changed, 235 insertions(+), 18 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index aacb2c5..6e84782 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -66,12 +66,21 @@ public class AsyncResult : IAsyncOperation, IEnumerator private readonly object _asyncState; - private EventWaitHandle _waitHandle; private AggregateException _exception; +#if UNITYFX_NOT_THREAD_SAFE + + private object _continuation; + private int _flags; + +#else + + private EventWaitHandle _waitHandle; private volatile object _continuation; private volatile int _flags; +#endif + #endregion #region interface @@ -525,6 +534,27 @@ protected virtual void OnStarted() /// protected virtual void OnCompleted() { +#if UNITYFX_NOT_THREAD_SAFE + + var continuation = _continuation; + + if (continuation != null) + { + if (continuation is IEnumerable continuationList) + { + foreach (var item in continuationList) + { + InvokeContinuation(this, item); + } + } + else + { + InvokeContinuation(this, continuation); + } + } + +#else + try { var continuation = Interlocked.Exchange(ref _continuation, _continuationCompletionSentinel); @@ -551,6 +581,8 @@ protected virtual void OnCompleted() { _waitHandle?.Set(); } + +#endif } /// @@ -568,6 +600,8 @@ protected virtual void Dispose(bool disposing) { _flags |= _flagDisposed; +#if !UNITYFX_NOT_THREAD_SAFE + if (_waitHandle != null) { #if NET35 @@ -577,6 +611,8 @@ protected virtual void Dispose(bool disposing) #endif _waitHandle = null; } + +#endif } } @@ -1358,6 +1394,28 @@ internal bool TrySetStatus(int newStatus) { Debug.Assert(newStatus < StatusRanToCompletion); +#if UNITYFX_NOT_THREAD_SAFE + + var flags = _flags; + + if ((flags & (_flagCompleted | _flagCompletionReserved)) != 0) + { + return false; + } + + var status = flags & _statusMask; + + if (status >= newStatus) + { + return false; + } + + _flags = (flags & ~_statusMask) | newStatus; + OnStatusChanged((AsyncOperationStatus)newStatus); + return true; + +#else + do { var flags = _flags; @@ -1383,6 +1441,8 @@ internal bool TrySetStatus(int newStatus) } } while (true); + +#endif } /// @@ -1400,6 +1460,22 @@ internal bool TrySetCompleted(int status, bool completedSynchronously) status |= _flagSynchronous; } +#if UNITYFX_NOT_THREAD_SAFE + + var flags = _flags; + + if ((flags & (_flagCompletionReserved | _flagCompleted)) != 0) + { + return false; + } + + _flags = (flags & ~_statusMask) | status; + OnStatusChanged((AsyncOperationStatus)status); + OnCompleted(); + return true; + +#else + do { var flags = _flags; @@ -1419,6 +1495,8 @@ internal bool TrySetCompleted(int status, bool completedSynchronously) } } while (true); + +#endif } /// @@ -1426,6 +1504,19 @@ internal bool TrySetCompleted(int status, bool completedSynchronously) /// internal bool TryReserveCompletion() { +#if UNITYFX_NOT_THREAD_SAFE + + var flags = _flags; + + if ((flags & (_flagCompletionReserved | _flagCompleted)) != 0) + { + return false; + } + + _flags = flags | _flagCompletionReserved; + return true; +#else + do { var flags = _flags; @@ -1441,6 +1532,8 @@ internal bool TryReserveCompletion() } } while (true); + +#endif } /// @@ -1461,8 +1554,12 @@ internal void SetCompleted(int status, bool completedSynchronously) newFlags |= _flagSynchronous; } - // Set completed status. After this call IsCompleted will return true. + // Set completed status. After this line IsCompleted will return true. +#if UNITYFX_NOT_THREAD_SAFE + _flags = oldFlags | newFlags; +#else Interlocked.Exchange(ref _flags, oldFlags | newFlags); +#endif // Invoke completion callbacks. OnStatusChanged((AsyncOperationStatus)status); @@ -1504,7 +1601,7 @@ internal void SetContinuationForAwait(Action continuation, SynchronizationContex { ThrowIfDisposed(); - if (!TryAddContinuation(continuation, syncContext)) + if (!TryAddContinuationInternal(continuation, syncContext)) { continuation(); } @@ -1563,7 +1660,11 @@ public event AsyncOperationCallback Completed throw new ArgumentNullException(nameof(value)); } - if (!TryAddContinuation(value, SynchronizationContext.Current)) +#if UNITYFX_NOT_THREAD_SAFE + if (!TryAddContinuationInternal(value)) +#else + if (!TryAddContinuationInternal(value, SynchronizationContext.Current)) +#endif { value(this); } @@ -1574,11 +1675,32 @@ public event AsyncOperationCallback Completed if (value != null) { - TryRemoveContinuation(value); + TryRemoveContinuationInternal(value); } } } + /// + public bool TryAddCompletionCallback(AsyncOperationCallback action) + { + ThrowIfDisposed(); + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + +#if UNITYFX_NOT_THREAD_SAFE + + return TryAddContinuationInternal(action); + +#else + + return TryAddContinuationInternal(action, SynchronizationContext.Current); + +#endif + } + /// public bool TryAddCompletionCallback(AsyncOperationCallback action, SynchronizationContext syncContext) { @@ -1589,7 +1711,7 @@ public bool TryAddCompletionCallback(AsyncOperationCallback action, Synchronizat throw new ArgumentNullException(nameof(action)); } - return TryAddContinuation(action, syncContext); + return TryAddContinuationInternal(action, syncContext); } /// @@ -1599,7 +1721,7 @@ public bool RemoveCompletionCallback(AsyncOperationCallback action) if (action != null) { - return TryRemoveContinuation(action); + return TryRemoveContinuationInternal(action); } return false; @@ -1615,7 +1737,15 @@ public bool TryAddContinuation(IAsyncContinuation continuation) throw new ArgumentNullException(nameof(continuation)); } - return TryAddContinuation(continuation, null); +#if UNITYFX_NOT_THREAD_SAFE + + return TryAddContinuationInternal(continuation); + +#else + + return TryAddContinuationInternal(continuation, null); + +#endif } /// @@ -1625,7 +1755,7 @@ public bool RemoveContinuation(IAsyncContinuation continuation) if (continuation != null) { - return TryRemoveContinuation(continuation); + return TryRemoveContinuationInternal(continuation); } return false; @@ -1640,6 +1770,12 @@ public WaitHandle AsyncWaitHandle { get { +#if UNITYFX_NOT_THREAD_SAFE + + throw new NotSupportedException(); + +#else + ThrowIfDisposed(); if (_waitHandle == null) @@ -1665,6 +1801,8 @@ public WaitHandle AsyncWaitHandle } return _waitHandle; + +#endif } } @@ -1795,22 +1933,86 @@ private AsyncResult(int flags) /// The continuation object to add. /// A instance to execute continuation on. /// Returns if the continuation was added; otherwise. - private bool TryAddContinuation(object continuation, SynchronizationContext syncContext) + private bool TryAddContinuationInternal(object continuation, SynchronizationContext syncContext) { if (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) { continuation = new AsyncContinuation(syncContext, continuation); } - return TryAddContinuation(continuation); + return TryAddContinuationInternal(continuation); + } + +#if UNITYFX_NOT_THREAD_SAFE + + /// + /// Attempts to register a continuation object. For internal use only. + /// + /// The continuation object to add. + /// Returns if the continuation was added; otherwise. + private bool TryAddContinuationInternal(object valueToAdd) + { + var oldValue = _continuation; + + // Quick return if the operation is completed. + if (oldValue != _continuationCompletionSentinel) + { + // If no continuation is stored yet, try to store it as _continuation. + if (oldValue == null) + { + _continuation = valueToAdd; + } + + // Logic for the case where we were previously storing a single continuation. + if (oldValue is IList list) + { + list.Add(valueToAdd); + } + else + { + _continuation = new List() { oldValue, valueToAdd }; + } + + return true; + } + + return false; + } + + /// + /// Attempts to remove the specified continuation. For internal use only. + /// + /// The continuation object to remove. + /// Returns if the continuation was removed; otherwise. + private bool TryRemoveContinuationInternal(object valueToRemove) + { + var value = _continuation; + + if (value != _continuationCompletionSentinel) + { + if (value is IList list) + { + list.Remove(valueToRemove); + } + else + { + _continuation = null; + } + + return true; + } + + return false; } +#else + /// /// Attempts to register a continuation object. For internal use only. /// /// The continuation object to add. /// Returns if the continuation was added; otherwise. - private bool TryAddContinuation(object valueToAdd) + private bool TryAddContinuationInternal(object valueToAdd) { // NOTE: The code below is adapted from https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs. var oldValue = _continuation; @@ -1867,7 +2069,7 @@ private bool TryAddContinuation(object valueToAdd) /// /// The continuation object to remove. /// Returns if the continuation was removed; otherwise. - private bool TryRemoveContinuation(object valueToRemove) + private bool TryRemoveContinuationInternal(object valueToRemove) { // NOTE: The code below is adapted from https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs. var value = _continuation; @@ -1917,6 +2119,8 @@ private bool TryRemoveContinuation(object valueToRemove) return false; } +#endif + /// /// Invokes the specified continuation instance. /// diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs index 03658ba..e101ef9 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs @@ -36,9 +36,10 @@ public static void AddContinuation(this IAsyncOperation op, IAsyncContinuation c /// Thrown is the operation has been disposed. /// /// + /// public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action) { - if (!op.TryAddCompletionCallback(action, SynchronizationContext.Current)) + if (!op.TryAddCompletionCallback(action)) { action(op); } diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs index 115b683..68db7a6 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs @@ -1,4 +1,4 @@ - // Copyright (c) Alexander Bogarsukov. +// Copyright (c) Alexander Bogarsukov. // Licensed under the MIT license. See the LICENSE.md file in the project root for more information. using System; diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs index 67827be..597cccd 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs @@ -29,10 +29,23 @@ public interface IAsyncOperationEvents /// /// Thrown if the delegate being registered is . /// Thrown is the operation has been disposed. + /// /// /// event AsyncOperationCallback Completed; + /// + /// Attempts to add a completion callback to be executed after the operation has finished. If the operation is already completed + /// the method does nothing and just returns . + /// + /// The callback to be executed when the operation has completed. + /// Returns if the callback was added; otherwise (the operation is completed). + /// Thrown if the is . + /// Thrown is the operation has been disposed. + /// + /// + bool TryAddCompletionCallback(AsyncOperationCallback action); + /// /// Attempts to add a completion callback to be executed after the operation has finished. If the operation is already completed /// the method does nothing and just returns . @@ -44,6 +57,7 @@ public interface IAsyncOperationEvents /// Returns if the callback was added; otherwise (the operation is completed). /// Thrown if the is . /// Thrown is the operation has been disposed. + /// /// bool TryAddCompletionCallback(AsyncOperationCallback action, SynchronizationContext syncContext); @@ -53,7 +67,7 @@ public interface IAsyncOperationEvents /// The callback to remove. Can be . /// Returns if the was removed; otherwise. /// Thrown is the operation has been disposed. - /// + /// bool RemoveCompletionCallback(AsyncOperationCallback action); /// diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenAllResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ThenAllResult{T,U}.cs index 10ec5b4..2f0b935 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ThenAllResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ThenAllResult{T,U}.cs @@ -55,7 +55,6 @@ protected override void InvokeSuccessCallback(IAsyncOperation op, bool completed { TrySetCanceled(completedSynchronously); } - } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs index 8b29379..13f60bb 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs @@ -76,7 +76,6 @@ protected override void InvokeSuccessCallback(IAsyncOperation op, bool completed { TrySetCanceled(completedSynchronously); } - } #endregion From 7ae2f91914b676d87d9d53ca8d4bb6c948a2c2bf Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 6 Apr 2018 15:33:29 +0300 Subject: [PATCH 059/128] Wait/Join fixed for UNITYFX_NOT_THREAD_SAFE compatibility --- src/UnityFx.Async/Api/Core/AsyncResult.cs | 9 ++- .../Api/Extensions/AsyncExtensions.Wait.cs | 72 +++++++++++++++++++ .../Api/Extensions/AsyncExtensions.cs | 25 +++++++ 3 files changed, 105 insertions(+), 1 deletion(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 6e84782..686b95f 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -1661,13 +1661,20 @@ public event AsyncOperationCallback Completed } #if UNITYFX_NOT_THREAD_SAFE + if (!TryAddContinuationInternal(value)) + { + value(this); + } + #else + if (!TryAddContinuationInternal(value, SynchronizationContext.Current)) -#endif { value(this); } + +#endif } remove { diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs index b607f45..63c0523 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs @@ -20,11 +20,19 @@ partial class AsyncExtensions /// public static void Wait(this IAsyncOperation op) { +#if UNITYFX_NOT_THREAD_SAFE + + SpinUntilCompleted(op); + +#else + if (!op.IsCompleted) { op.AsyncWaitHandle.WaitOne(); } +#endif + ThrowIfNonSuccess(op, true); } @@ -41,6 +49,12 @@ public static void Wait(this IAsyncOperation op) /// public static bool Wait(this IAsyncOperation op, int millisecondsTimeout) { +#if UNITYFX_NOT_THREAD_SAFE + + var result = SpinUntilCompleted(op, millisecondsTimeout); + +#else + var result = true; if (!op.IsCompleted) @@ -48,6 +62,8 @@ public static bool Wait(this IAsyncOperation op, int millisecondsTimeout) result = op.AsyncWaitHandle.WaitOne(millisecondsTimeout); } +#endif + if (result) { ThrowIfNonSuccess(op, true); @@ -69,6 +85,12 @@ public static bool Wait(this IAsyncOperation op, int millisecondsTimeout) /// public static bool Wait(this IAsyncOperation op, TimeSpan timeout) { +#if UNITYFX_NOT_THREAD_SAFE + + var result = SpinUntilCompleted(op, timeout); + +#else + var result = true; if (!op.IsCompleted) @@ -76,6 +98,8 @@ public static bool Wait(this IAsyncOperation op, TimeSpan timeout) result = op.AsyncWaitHandle.WaitOne(timeout); } +#endif + if (result) { ThrowIfNonSuccess(op, true); @@ -98,11 +122,19 @@ public static bool Wait(this IAsyncOperation op, TimeSpan timeout) /// public static void Join(this IAsyncOperation op) { +#if UNITYFX_NOT_THREAD_SAFE + + SpinUntilCompleted(op); + +#else + if (!op.IsCompleted) { op.AsyncWaitHandle.WaitOne(); } +#endif + ThrowIfNonSuccess(op, false); } @@ -119,6 +151,12 @@ public static void Join(this IAsyncOperation op) /// public static void Join(this IAsyncOperation op, int millisecondsTimeout) { +#if UNITYFX_NOT_THREAD_SAFE + + var result = SpinUntilCompleted(op, millisecondsTimeout); + +#else + var result = true; if (!op.IsCompleted) @@ -126,6 +164,8 @@ public static void Join(this IAsyncOperation op, int millisecondsTimeout) result = op.AsyncWaitHandle.WaitOne(millisecondsTimeout); } +#endif + if (result) { ThrowIfNonSuccess(op, false); @@ -149,6 +189,12 @@ public static void Join(this IAsyncOperation op, int millisecondsTimeout) /// public static void Join(this IAsyncOperation op, TimeSpan timeout) { +#if UNITYFX_NOT_THREAD_SAFE + + var result = SpinUntilCompleted(op, timeout); + +#else + var result = true; if (!op.IsCompleted) @@ -156,6 +202,8 @@ public static void Join(this IAsyncOperation op, TimeSpan timeout) result = op.AsyncWaitHandle.WaitOne(timeout); } +#endif + if (result) { ThrowIfNonSuccess(op, false); @@ -177,11 +225,19 @@ public static void Join(this IAsyncOperation op, TimeSpan timeout) /// public static TResult Join(this IAsyncOperation op) { +#if UNITYFX_NOT_THREAD_SAFE + + SpinUntilCompleted(op); + +#else + if (!op.IsCompleted) { op.AsyncWaitHandle.WaitOne(); } +#endif + ThrowIfNonSuccess(op, false); return op.Result; } @@ -200,6 +256,12 @@ public static TResult Join(this IAsyncOperation op) /// public static TResult Join(this IAsyncOperation op, int millisecondsTimeout) { +#if UNITYFX_NOT_THREAD_SAFE + + var result = SpinUntilCompleted(op, millisecondsTimeout); + +#else + var result = true; if (!op.IsCompleted) @@ -207,6 +269,8 @@ public static TResult Join(this IAsyncOperation op, int millis result = op.AsyncWaitHandle.WaitOne(millisecondsTimeout); } +#endif + if (result) { ThrowIfNonSuccess(op, false); @@ -233,6 +297,12 @@ public static TResult Join(this IAsyncOperation op, int millis /// public static TResult Join(this IAsyncOperation op, TimeSpan timeout) { +#if UNITYFX_NOT_THREAD_SAFE + + var result = SpinUntilCompleted(op, timeout); + +#else + var result = true; if (!op.IsCompleted) @@ -240,6 +310,8 @@ public static TResult Join(this IAsyncOperation op, TimeSpan t result = op.AsyncWaitHandle.WaitOne(timeout); } +#endif + if (result) { ThrowIfNonSuccess(op, false); diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs index c4933b2..98c3f05 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs @@ -19,6 +19,7 @@ public static partial class AsyncExtensions /// /// Spins until the operation has completed. /// + /// The operation to wait for. public static void SpinUntilCompleted(this IAsyncResult op) { #if NET35 @@ -40,6 +41,30 @@ public static void SpinUntilCompleted(this IAsyncResult op) #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) + { + throw new NotImplementedException(); + } + + /// + /// 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) + { + throw new NotImplementedException(); + } + /// /// Throws exception if the operation has failed or canceled. /// From 174df8528fd42b0d45063774d5ca015066770bb8 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 6 Apr 2018 15:40:18 +0300 Subject: [PATCH 060/128] Implemented UNITYFX_NOT_THREAD_SAFE for task extensions and async/await --- .../Api/Extensions/AsyncExtensions.Tasks.cs | 18 +++++++++++++++++- .../Continuations/AsyncContinuation.cs | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs index c0758b0..7c2187f 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs @@ -154,7 +154,15 @@ public ConfiguredAsyncAwaitable(IAsyncOperation op, bool continueOnCapturedCo /// public static AsyncAwaiter GetAwaiter(this IAsyncOperation op) { +#if UNITYFX_NOT_THREAD_SAFE + return new AsyncAwaiter(op, false); + +#else + + return new AsyncAwaiter(op, true); + +#endif } /// @@ -164,7 +172,15 @@ public static AsyncAwaiter GetAwaiter(this IAsyncOperation op) /// public static AsyncAwaiter GetAwaiter(this IAsyncOperation op) { +#if UNITYFX_NOT_THREAD_SAFE + return new AsyncAwaiter(op, false); + +#else + + return new AsyncAwaiter(op, true); + +#endif } /// @@ -216,7 +232,7 @@ public static Task ToTask(this IAsyncOperation op) } else { - var result = new TaskCompletionSource(); + var result = new TaskCompletionSource(); if (!op.TryAddCompletionCallback(asyncOp => AsyncContinuation.InvokeTaskContinuation(asyncOp, result), null)) { diff --git a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs index 753e219..fdbffee 100644 --- a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs +++ b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs @@ -68,7 +68,7 @@ internal static void InvokeDelegate(IAsyncOperation op, object continuation) #if UNITYFX_SUPPORT_TAP - internal static void InvokeTaskContinuation(IAsyncOperation op, TaskCompletionSource tcs) + internal static void InvokeTaskContinuation(IAsyncOperation op, TaskCompletionSource tcs) { var status = op.Status; From 4e11063599a2eaeebd2b4d3ff4c1b7db4340cf6f Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 6 Apr 2018 15:52:08 +0300 Subject: [PATCH 061/128] Added Finally tests --- src/UnityFx.Async.Tests/Tests/CatchTests.cs | 84 +++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/src/UnityFx.Async.Tests/Tests/CatchTests.cs b/src/UnityFx.Async.Tests/Tests/CatchTests.cs index e20ceac..2b5710d 100644 --- a/src/UnityFx.Async.Tests/Tests/CatchTests.cs +++ b/src/UnityFx.Async.Tests/Tests/CatchTests.cs @@ -10,5 +10,89 @@ namespace UnityFx.Async { public class CatchTests { + [Fact] + public async Task Catch_DelegateIsNotCalledOnSuccess() + { + // Arrange + var called = false; + var op = AsyncResult.Delay(1).Catch(e => called = true); + + // Act + await op; + + // Assert + Assert.False(called); + } + + [Fact] + public async Task Catch_DelegateIsCalledOnFailure() + { + // Arrange + Exception exception = null; + var op0 = AsyncResult.FromException(new Exception()); + var op = op0.Catch(e => exception = e); + + // Act + await op; + + // Assert + Assert.Equal(op0.Exception.InnerException, exception); + } + + [Fact] + public async Task Catch_DelegateIsCalledOnCancel() + { + // Arrange + var called = false; + var op = AsyncResult.FromCanceled().Catch(e => called = true); + + // Act + await op; + + // Assert + Assert.True(called); + } + + [Fact] + public async Task Catch_ExceptionIsFiltered() + { + // Arrange + var called = false; + var op = AsyncResult.FromCanceled().Catch(e => called = true); + + // Act + await op; + + // Assert + Assert.True(called); + } + + [Fact] + public async Task Catch_ExceptionIsFiltered_2() + { + // Arrange + var called = false; + var op = AsyncResult.FromCanceled().Catch(e => called = true); + + // Act + await op; + + // Assert + Assert.True(called); + } + + [Fact] + public async Task Catch_ExceptionIsFiltered_3() + { + // Arrange + var called = false; + var op = AsyncResult.FromCanceled().Catch(e => called = true); + + // Act + await op; + + // Assert + Assert.False(called); + } } } From 8e91000f38bfd6515d747e411b605f5e9709b529 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 6 Apr 2018 15:55:00 +0300 Subject: [PATCH 062/128] Finally tests added --- src/UnityFx.Async.Tests/Tests/FinallyTests.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/UnityFx.Async.Tests/Tests/FinallyTests.cs b/src/UnityFx.Async.Tests/Tests/FinallyTests.cs index ce49617..387c33c 100644 --- a/src/UnityFx.Async.Tests/Tests/FinallyTests.cs +++ b/src/UnityFx.Async.Tests/Tests/FinallyTests.cs @@ -10,5 +10,46 @@ namespace UnityFx.Async { public class FinallyTests { + [Fact] + public async Task Finally_DelegateIsCalledOnSuccess() + { + // Arrange + var called = false; + var op = AsyncResult.Delay(1).Finally(() => called = true); + + // Act + await op; + + // Assert + Assert.True(called); + } + + [Fact] + public async Task Finally_DelegateIsCalledOnFailure() + { + // Arrange + var called = false; + var op = AsyncResult.FromException(new Exception()).Finally(() => called = true); + + // Act + await op; + + // Assert + Assert.True(called); + } + + [Fact] + public async Task Finally_DelegateIsCalledOnCancel() + { + // Arrange + var called = false; + var op = AsyncResult.FromCanceled().Finally(() => called = true); + + // Act + await op; + + // Assert + Assert.True(called); + } } } From 3626b7de3fd43c3fde57bfd794ad6bb77340fec4 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 6 Apr 2018 16:12:26 +0300 Subject: [PATCH 063/128] Added Then tests --- src/UnityFx.Async.Tests/Tests/ThenTests.cs | 53 ++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/UnityFx.Async.Tests/Tests/ThenTests.cs b/src/UnityFx.Async.Tests/Tests/ThenTests.cs index eaaabf9..901bcd0 100644 --- a/src/UnityFx.Async.Tests/Tests/ThenTests.cs +++ b/src/UnityFx.Async.Tests/Tests/ThenTests.cs @@ -10,5 +10,58 @@ namespace UnityFx.Async { public class ThenTests { + [Fact] + public async Task Then_DelegateIsCalledOnSuccess() + { + // Arrange + var called = false; + var op = AsyncResult.Delay(1).Then(() => called = true); + + // Act + await op; + + // Assert + Assert.True(called); + } + + [Fact] + public async Task Then_DelegateIsNotCalledOnFailure() + { + // Arrange + var called = false; + var op = AsyncResult.FromException(new Exception()).Then(() => called = true); + + // Act + try + { + await op; + } + catch + { + } + + // Assert + Assert.False(called); + } + + [Fact] + public async Task Then_DelegateIsNotCalledOnCancel() + { + // Arrange + var called = false; + var op = AsyncResult.FromCanceled().Then(() => called = true); + + // Act + try + { + await op; + } + catch + { + } + + // Assert + Assert.False(called); + } } } From e68b9a977fad6120ff924ede73aa7c0c99f726f8 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 6 Apr 2018 17:37:23 +0300 Subject: [PATCH 064/128] Added Unwrap tests --- src/UnityFx.Async.Tests/Tests/UnwrapTests.cs | 94 +++++++++++++++++++ .../Extensions/AsyncExtensions.Promises.cs | 4 +- .../Continuations/UnwrapResult{T}.cs | 8 +- 3 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 src/UnityFx.Async.Tests/Tests/UnwrapTests.cs diff --git a/src/UnityFx.Async.Tests/Tests/UnwrapTests.cs b/src/UnityFx.Async.Tests/Tests/UnwrapTests.cs new file mode 100644 index 0000000..e86f387 --- /dev/null +++ b/src/UnityFx.Async.Tests/Tests/UnwrapTests.cs @@ -0,0 +1,94 @@ +// 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 UnwrapTests + { + [Fact] + public async Task Unwrap_SucceedsIfBothOperationsSucceed() + { + // Arrange + var op1 = AsyncResult.Delay(1); + var op2 = AsyncResult.FromResult(op1); + + // Act + var op = op2.Unwrap(); + await op; + + // Assert + Assert.True(op.IsCompletedSuccessfully); + Assert.True(op1.IsCompleted); + Assert.True(op2.IsCompleted); + } + + [Fact] + public async Task Unwrap_FailsIfInnerOperationFails() + { + // Arrange + var expectedException = new Exception(); + var actualException = default(Exception); + var op1 = AsyncResult.FromException(expectedException); + var op2 = AsyncResult.FromResult(op1); + var op = op2.Unwrap(); + + // Act + try + { + await op; + } + catch (Exception e) + { + actualException = e; + } + + // Assert + Assert.True(op.IsFaulted); + Assert.Equal(expectedException, actualException); + } + + [Fact] + public async Task Unwrap_FailsIfOuterOperationFails() + { + // Arrange + var actualException = default(Exception); + var op1 = AsyncResult.FromCanceled(); + var op = op1.Unwrap(); + + // Act + try + { + await op; + } + catch (OperationCanceledException e) + { + actualException = e; + } + + // Assert + Assert.True(op.IsCanceled); + Assert.NotNull(actualException); + } + + [Fact] + public async Task Unwrap_ReturnsInnerResult() + { + // Arrange + var op1 = AsyncResult.FromResult(1); + var op2 = AsyncResult.FromResult(op1); + + // Act + var op = op2.Unwrap(); + await op; + + // Assert + Assert.True(op.IsCompletedSuccessfully); + Assert.Equal(1, op.Result); + } + } +} diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs index 0fb8768..c67db9f 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs @@ -350,14 +350,14 @@ public static IAsyncOperation ThenAny(this IAsyncOperation< /// An operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the callback has completed. - public static IAsyncOperation Rebind(this IAsyncOperation op, Func successCallback) + public static IAsyncOperation Rebind(this IAsyncOperation op, Func successCallback) { if (successCallback == null) { throw new ArgumentNullException(nameof(successCallback)); } - return new RebindResult(op, successCallback); + return new RebindResult(op, successCallback); } /// diff --git a/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs index d7f74b1..cb301e9 100644 --- a/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs @@ -37,16 +37,18 @@ public void Invoke(IAsyncOperation op) { if (_state == State.WaitingForOuterOperation) { + _state = State.WaitingForInnerOperation; + if (op.IsCompletedSuccessfully) { switch (op) { case IAsyncOperation> innerOp1: - ProcessInnerOperation(innerOp1, false); + ProcessInnerOperation(innerOp1.Result, false); break; case IAsyncOperation innerOp2: - ProcessInnerOperation(innerOp2, false); + ProcessInnerOperation(innerOp2.Result, false); break; default: @@ -58,8 +60,6 @@ public void Invoke(IAsyncOperation op) { TrySetException(op.Exception, false); } - - _state = State.WaitingForInnerOperation; } else if (_state == State.WaitingForInnerOperation) { From 74c51260ef1f48394d98038054aeb0a223658f7e Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 7 Apr 2018 21:39:57 +0300 Subject: [PATCH 065/128] Minor project structure changes --- src/UnityFx.Async/Api/{Exceptions => Net35}/AggregateException.cs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/UnityFx.Async/Api/{Exceptions => Net35}/AggregateException.cs (100%) diff --git a/src/UnityFx.Async/Api/Exceptions/AggregateException.cs b/src/UnityFx.Async/Api/Net35/AggregateException.cs similarity index 100% rename from src/UnityFx.Async/Api/Exceptions/AggregateException.cs rename to src/UnityFx.Async/Api/Net35/AggregateException.cs From 9b483a32b6ad23d7afd8c8fe1d36c225ef686d29 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 7 Apr 2018 21:43:02 +0300 Subject: [PATCH 066/128] Minor continuation changes --- .../Continuations/CatchResult{T,TException}.cs | 1 + .../Continuations/ContinuationResult{T}.cs | 14 +++++++++----- .../Continuations/ContinueWithResult{T,U}.cs | 2 +- .../Continuations/ContinueWithResult{T}.cs | 2 +- .../Continuations/FinallyResult{T}.cs | 1 + .../Continuations/RebindResult{T,U}.cs | 1 + .../Continuations/ThenResult{T,U}.cs | 1 + 7 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs b/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs index c4c85f7..cc88d46 100644 --- a/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs @@ -16,6 +16,7 @@ internal class CatchResult : ContinuationResult, IAsyncContinu #region interface public CatchResult(IAsyncOperation op, Action errorCallback) + : base(op) { _errorCallback = errorCallback; diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs index 3cc6645..c6b6ea9 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs @@ -2,6 +2,7 @@ // 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 @@ -13,29 +14,34 @@ internal abstract class ContinuationResult : AsyncResult private static SendOrPostCallback _postCallback; private readonly SynchronizationContext _syncContext; - private IAsyncOperation _op; + private readonly IAsyncOperation _op; #endregion #region interface - protected ContinuationResult() + protected ContinuationResult(IAsyncOperation op) : base(AsyncOperationStatus.Running) { _syncContext = SynchronizationContext.Current; + _op = op; } - protected ContinuationResult(bool captureSynchronizationContext) + protected ContinuationResult(IAsyncOperation op, bool captureSynchronizationContext) : base(AsyncOperationStatus.Running) { if (captureSynchronizationContext) { _syncContext = SynchronizationContext.Current; } + + _op = op; } protected void InvokeOnSyncContext(IAsyncOperation op, bool completedSynchronously) { + Debug.Assert(op == _op); + if (completedSynchronously || _syncContext == null || _syncContext == SynchronizationContext.Current) { try @@ -49,8 +55,6 @@ protected void InvokeOnSyncContext(IAsyncOperation op, bool completedSynchronous } else { - _op = op; - if (_postCallback == null) { _postCallback = args => diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs index dfa24a8..43f184b 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs @@ -18,7 +18,7 @@ internal class ContinueWithResult : ContinuationResult, IAsyncContinuat #region interface internal ContinueWithResult(IAsyncOperation op, AsyncContinuationOptions options, object continuation, object userState) - : base((options & AsyncContinuationOptions.ExecuteSynchronously) == 0) + : base(op, (options & AsyncContinuationOptions.ExecuteSynchronously) == 0) { _options = options; _continuation = continuation; diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs index 3723eb9..aa02938 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs @@ -18,7 +18,7 @@ internal class ContinueWithResult : ContinuationResult, IAsyncContinuation #region interface internal ContinueWithResult(IAsyncOperation op, AsyncContinuationOptions options, object continuation, object userState) - : base((options & AsyncContinuationOptions.ExecuteSynchronously) == 0) + : base(op, (options & AsyncContinuationOptions.ExecuteSynchronously) == 0) { _options = options; _continuation = continuation; diff --git a/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs index c969dfc..1b2da31 100644 --- a/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs @@ -16,6 +16,7 @@ internal class FinallyResult : ContinuationResult, IAsyncContinuation #region interface public FinallyResult(IAsyncOperation op, Action action) + : base(op) { _continuation = action; diff --git a/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs index c8fe490..9302572 100644 --- a/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs @@ -17,6 +17,7 @@ internal class RebindResult : ContinuationResult, IAsyncContinuation #region interface public RebindResult(IAsyncOperation op, object action) + : base(op) { _continuation = action; diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs index b58ed70..74ffd3e 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs @@ -17,6 +17,7 @@ internal class ThenResult : ContinuationResult, IAsyncContinuation #region interface public ThenResult(IAsyncOperation op, object successCallback, Action errorCallback) + : base(op) { _successCallback = successCallback; _errorCallback = errorCallback; From 3f0117e27c49744d51b6eb2e229ff55451f5e7b4 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 7 Apr 2018 22:55:18 +0300 Subject: [PATCH 067/128] Added new overloads for ContinueWith --- .../Extensions/AsyncExtensions.Promises.cs | 38 +++++++++++++++++++ .../Continuations/FinallyResult{T}.cs | 25 ++++++++++-- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs index c67db9f..61a416e 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs @@ -416,6 +416,44 @@ public static IAsyncOperation Catch(this IAsyncOperation op, Action< #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, Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return new FinallyResult(op, action); + } + + /// + /// 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) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return new FinallyResult(op, action); + } + + #endregion + #region Finally /// diff --git a/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs index 1b2da31..38d9ada 100644 --- a/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs @@ -9,13 +9,13 @@ internal class FinallyResult : ContinuationResult, IAsyncContinuation { #region data - private readonly Action _continuation; + private readonly object _continuation; #endregion #region interface - public FinallyResult(IAsyncOperation op, Action action) + public FinallyResult(IAsyncOperation op, object action) : base(op) { _continuation = action; @@ -33,8 +33,25 @@ public FinallyResult(IAsyncOperation op, Action action) protected override void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously) { - _continuation(); - TrySetCompleted(completedSynchronously); + switch (_continuation) + { + case Action a: + a.Invoke(); + TrySetCompleted(completedSynchronously); + break; + + case Func> f1: + f1().AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); + break; + + case Func f2: + f2().AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); + break; + + default: + TrySetCanceled(completedSynchronously); + break; + } } #endregion From 4c22d0c32312cda15f1093d74b6c816b3f4cd259 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 7 Apr 2018 23:01:17 +0300 Subject: [PATCH 068/128] Added tests for ThenAll/ThenAny --- src/UnityFx.Async.Tests/Tests/ThenAllTests.cs | 16 ++++++++++++++++ src/UnityFx.Async.Tests/Tests/ThenAnyTests.cs | 15 +++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/src/UnityFx.Async.Tests/Tests/ThenAllTests.cs b/src/UnityFx.Async.Tests/Tests/ThenAllTests.cs index 0c83a19..77a4429 100644 --- a/src/UnityFx.Async.Tests/Tests/ThenAllTests.cs +++ b/src/UnityFx.Async.Tests/Tests/ThenAllTests.cs @@ -10,5 +10,21 @@ namespace UnityFx.Async { public class ThenAllTests { + [Fact] + public async Task ThenAll_CompletesWhenAllOperationsComplete() + { + // Arrange + var op1 = AsyncResult.Delay(1); + var op2 = AsyncResult.Delay(2); + var op3 = AsyncResult.Delay(3); + + // Act + await op1.ThenAll(() => new IAsyncOperation[] { op2, op3 }); + + // Assert + Assert.True(op1.IsCompleted); + Assert.True(op2.IsCompleted); + Assert.True(op3.IsCompleted); + } } } diff --git a/src/UnityFx.Async.Tests/Tests/ThenAnyTests.cs b/src/UnityFx.Async.Tests/Tests/ThenAnyTests.cs index 00fc71d..070fed2 100644 --- a/src/UnityFx.Async.Tests/Tests/ThenAnyTests.cs +++ b/src/UnityFx.Async.Tests/Tests/ThenAnyTests.cs @@ -10,5 +10,20 @@ namespace UnityFx.Async { public class ThenAnyTests { + [Fact] + public async Task ThenAll_CompletesWhenAllOperationsComplete() + { + // Arrange + var op1 = AsyncResult.Delay(1); + var op2 = AsyncResult.Delay(2); + var op3 = new AsyncResult(); + + // Act + await op1.ThenAny(() => new IAsyncOperation[] { op2, op3 }); + + // Assert + Assert.True(op1.IsCompleted); + Assert.True(op2.IsCompleted); + } } } From 3552bfb98e94429feea2c81240b109b60e8a530a Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 7 Apr 2018 23:44:32 +0300 Subject: [PATCH 069/128] Catch filtering fix --- src/UnityFx.Async.Tests/Tests/CatchTests.cs | 11 ++++++++++- .../Continuations/CatchResult{T,TException}.cs | 6 +++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/UnityFx.Async.Tests/Tests/CatchTests.cs b/src/UnityFx.Async.Tests/Tests/CatchTests.cs index 2b5710d..f5d7897 100644 --- a/src/UnityFx.Async.Tests/Tests/CatchTests.cs +++ b/src/UnityFx.Async.Tests/Tests/CatchTests.cs @@ -86,13 +86,22 @@ public async Task Catch_ExceptionIsFiltered_3() { // Arrange var called = false; + var exceptionThrown = false; var op = AsyncResult.FromCanceled().Catch(e => called = true); // Act - await op; + try + { + await op; + } + catch (Exception e) + { + exceptionThrown = true; + } // Assert Assert.False(called); + Assert.True(exceptionThrown); } } } diff --git a/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs b/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs index cc88d46..44239c3 100644 --- a/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs @@ -52,10 +52,14 @@ public void Invoke(IAsyncOperation op) private void InvokeInternal(IAsyncOperation op, bool completedSynchronously) { - if (op.IsCompletedSuccessfully || !(op.Exception.InnerException is TException)) + if (op.IsCompletedSuccessfully) { TrySetCompleted(completedSynchronously); } + else if (!(op.Exception.InnerException is TException)) + { + TrySetException(op.Exception, completedSynchronously); + } else { InvokeOnSyncContext(op, completedSynchronously); From ab6c3168216a989459e5f2cfa88ce1a98c5c0abf Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sun, 8 Apr 2018 00:13:15 +0300 Subject: [PATCH 070/128] README update --- README.md | 59 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 0f1f663..aa555af 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,8 @@ private IEnumerator DownloadTextInternal(IAsyncCompletionSource op, stri ### Waiting for an operation to complete The simpliest way to get notified of an operation completion is registering a completion handler to be invoked when the operation succeeds (the JS promise-like way): ```csharp -DownloadTextAsync("http://www.google.com").Then(text => Debug.Log(text)); +DownloadTextAsync("http://www.google.com") + .Then(text => Debug.Log(text)); ``` The above code downloads content of Google's front page and prints it to Unity console. To make this example closer to real life applications let's add simple error handling code to it: ```csharp @@ -134,7 +135,7 @@ catch (Exception e) Debug.LogException(e); } ``` -Or, you can just block current thread while waiting (don't do that from UI thread!). Note usage of `using` with operation instance: +Or, you can just block current thread while waiting (don't do that from UI thread!): ```csharp try { @@ -151,12 +152,13 @@ catch (Exception e) ``` ### Chaining asynchronous operations -Multiple asynchronous operations can be chained one after other using `Then`: +Multiple asynchronous operations can be chained one after other using `Then` / `ContinueWith` / `Catch` / `Finally`: ```csharp DownloadTextAsync("http://www.google.com") .Then(text => ExtractFirstParagraph(text)) .Then(firstParagraph => Debug.Log(firstParagraph)) - .Catch(e => Debug.LogException(e)); + .Catch(e => Debug.LogException(e)) + .Finally(() => Debug.Log("Done")); ``` Alternatively, with .NET 4.6: ```csharp @@ -166,24 +168,53 @@ try var firstParagraph = await ExtractFirstParagraph(text); Debug.Log(firstParagraph); } -catch (OperationCanceledException) -{ - Debug.LogWarning("The operation was canceled."); -} catch (Exception e) { Debug.LogException(e); } +finally +{ + Debug.Log("Done"); +} ``` -### Error handling -TODO +The chain of processing ends as soon as an exception occurs. In this case when an error occurs the `Catch` handler would be called (if present). -### Completed asynchronous operations -TODO +`Then` continuations get executed only if previous operation in the chain completed successfully. Otherwise they are skipped. Also note that `Then` expects the return value to be another operation. `Rebind` is a special kind of continuation for transforming operation result to a different type. -### Interfaces -TODO +`ContinueWith` delegates get called in any case. The list of `ContinueWith` overloads closely matches [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) interface. + +### Synchronization context capturing +The default behaviour of all library methods is to capture current synchronization context and try to schedule all continuations on it. If there is not synchronization context attached to current thread continuations are executed on a thread that initiated an operation completion. The same behaviour applies to `async` / `await` implementation unless explicitly overriden with `ConfigureAwait`. +```csharp +// thread1 +await DownloadTextAsync("http://www.google.com"); +// Back on thread1 (if SynchronizationContext.Current is set) +await DownloadTextAsync("http://www.yahoo.com").ConfigureAwait(false); +// Most likely some other thread +``` + +### Completion callbacks +All operations defined in the library support addition of completion callbacks that are executed when the operation completes: +```csharp +var op = DownloadTextAsync("http://www.google.com"); +op.AddCompletionCallback(o => Debug.Log("Done")); +// or +op.Completed += o => Debug.Log("Done"); +``` +Unlike `ContinueWith`-like stuff completion callbacks cannot be chained and do not handle exceptions. That's why they should be used with care. + +### Disposing of operations +All operations implement [IDisposable](https://docs.microsoft.com/en-us/dotnet/api/system.idisposable) interface. So strictly speaking users should call `Dispose` when an operation is not in use. That said library implementation only requires this if `AsyncWaitHandle` was accessed. This behaviour closely matches [Tasks](https://blogs.msdn.microsoft.com/pfxteam/2012/03/25/do-i-need-to-dispose-of-tasks/). + +### Completed asynchronous operations +There are a number of helper methods for creating completed operations: +```csharp +var op1 = AsyncResult.CompletedOperation; +var op2 = AsyncResult.FromResult(10); +var op3 = AsyncResult.FromException(new Exception()); +var op4 = AsyncResult.FromCanceled(); +``` ## Comparison to .NET Tasks The comparison table below shows how *UnityFx.Async* entities relate to [Tasks](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task): From 97993cb1b3d6b1a8e32829f989c13f55c545b404 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sun, 8 Apr 2018 14:38:41 +0300 Subject: [PATCH 071/128] README update --- README.md | 156 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 132 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index aa555af..a92f967 100644 --- a/README.md +++ b/README.md @@ -34,14 +34,57 @@ 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) from the editor. ## Understanding the concepts -The below listed topics are just a quict summary of the problems and the proposed solutions. For more details on the topic please see this [excellent article](http://www.what-could-possibly-go-wrong.com/promises-for-game-development/). -### Why callbacks are bad +The below listed topics 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. +### Callback hell +Getting notified of an asynchronous operation completion via callbacks is the most common (as well as low-level) approach. It is very simple and obvious at first glance: +```csharp +InitiateSomeAsyncOperation( + result => + { + // Success handler + }, + e => + { + // Error handler + }); +``` +Now let's chain several operations: +```csharp +InitiateSomeAsyncOperation( + result => + { + InitiateAsyncOperation2(result, + result2 => + { + InitiateAsyncOperation3(result2, + result3 => + { + // ... + }, + e => + { + // Error handler 3 + }); + }, + e => + { + // Error handler 2 + }); + }, + e => + { + // Error handler + }); +``` +Doesn't look that simple now, right? And that's just the async method calls without actual result processing and error handling. Production code would have `try` / `catch` blocks in each handler and much more result processing code. The code complexity (and maintainability problems as a result) produced by extensive callback usage is exactly what is called a [callback hell](http://callbackhell.com/). + +### Unity coroutines - another way to shoot yourself in the foot TODO -### Why coroutines are bad +### Promises to the rescue TODO -### Promises to the Rescue +### Asynchronous programming with async and await TODO ## Using the library @@ -53,13 +96,13 @@ Create an operation instance like this: ```csharp var op = new AsyncCompletionSource(); ``` -The type of the operation should reflect its result type. In this case we have created a special kind of operations - a completion source, that incapsulated both producer and consumer operation interfaces (consumer side is represented via `IAsyncOperation` / `IAsyncOperation` interfaces and producer side is `IAsyncCompletionSource` / `IAsyncCompletionSource`, `AsyncCompletionSource` implements both of the interfaces). +The type of the operation should reflect its result type. In this case we have created a special kind of operation - a completion source that incapsulated both producer and consumer interfaces (consumer side is represented via `IAsyncOperation` / `IAsyncOperation` interfaces and producer side is `IAsyncCompletionSource` / `IAsyncCompletionSource`, `AsyncCompletionSource` implements both of the interfaces). -Upon completion of the asynchronous operation notify its users: +Upon completion of an asynchronous operation transition it to one of the final states (`RanToCompletion`, `Faulted` or `Canceled`): ```csharp op.SetResult(resultValue); ``` -Alternatively, if the operation has failed: +Or, if the operation has failed: ```csharp op.SetException(ex); ``` @@ -119,7 +162,7 @@ else if (op.IsCanceled) Debug.LogWarning("The operation was canceled."); } ``` -With Unity 2017+ and .NET 4.6 it can be used just like a [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task). The await continuation is scheduled on the captured [SynchronizationContext](https://docs.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext) (if any): +With Unity 2017+ and .NET 4.6 it can be used just like a [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task). The await continuation is scheduled on a captured [SynchronizationContext](https://docs.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext) (if any): ```csharp try { @@ -152,7 +195,7 @@ catch (Exception e) ``` ### Chaining asynchronous operations -Multiple asynchronous operations can be chained one after other using `Then` / `ContinueWith` / `Catch` / `Finally`: +Multiple asynchronous operations can be chained one after other using `Then` / `Rebind` / `ContinueWith` / `Catch` / `Finally`: ```csharp DownloadTextAsync("http://www.google.com") .Then(text => ExtractFirstParagraph(text)) @@ -160,7 +203,24 @@ DownloadTextAsync("http://www.google.com") .Catch(e => Debug.LogException(e)) .Finally(() => Debug.Log("Done")); ``` -Alternatively, with .NET 4.6: +The chain of processing ends as soon as an exception occurs. In this case when an error occurs the `Catch` handler would be called. + +`Then` continuations get executed only if previous operation in the chain completed successfully. Otherwise they are skipped. Also note that `Then` expects the handler return value to be another operation. + +`Rebind` is a special kind of continuation for transforming operation result to a different type: +```csharp +DownloadTextAsync("http://www.google.com") + .Then(text => ExtractFirstUrl(text)) + .Rebind(url => new Url(url)); +``` +`ContinueWith` and `Finally` delegates get called independently of the antecedent operation result. `ContinueWith` also define overloads accepting `AsyncContinuationOptions` argument that allows to customize its behaviour: +```csharp +DownloadTextAsync("http://www.google.com") + .ContinueWith(op => Debug.Log("1")) + .ContinueWith(op => Debug.Log("2"), AsyncContinuationOptions.NotOnCanceled) + .ContinueWith(op => Debug.Log("3"), AsyncContinuationOptions.OnlyOnFaulted); +``` +That said with .NET 4.6 the recommented approach is using `async` / `await`: ```csharp try { @@ -178,34 +238,42 @@ finally } ``` -The chain of processing ends as soon as an exception occurs. In this case when an error occurs the `Catch` handler would be called (if present). - -`Then` continuations get executed only if previous operation in the chain completed successfully. Otherwise they are skipped. Also note that `Then` expects the return value to be another operation. `Rebind` is a special kind of continuation for transforming operation result to a different type. - -`ContinueWith` delegates get called in any case. The list of `ContinueWith` overloads closely matches [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) interface. - ### Synchronization context capturing -The default behaviour of all library methods is to capture current synchronization context and try to schedule all continuations on it. If there is not synchronization context attached to current thread continuations are executed on a thread that initiated an operation completion. The same behaviour applies to `async` / `await` implementation unless explicitly overriden with `ConfigureAwait`. +The default behaviour of all library methods is to capture current [SynchronizationContext](https://docs.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext) and try to schedule continuations on it. If there is no synchronization context attached to current thread, continuations are executed on a thread that initiated an operation completion. The same behaviour applies to `async` / `await` implementation unless explicitly overriden with `ConfigureAwait`: ```csharp // thread1 await DownloadTextAsync("http://www.google.com"); -// Back on thread1 (if SynchronizationContext.Current is set) +// Back on thread1 await DownloadTextAsync("http://www.yahoo.com").ConfigureAwait(false); // Most likely some other thread ``` ### Completion callbacks -All operations defined in the library support addition of completion callbacks that are executed when the operation completes: +All operations defined in the library support adding of completion callbacks that are executed when an operation completes: +```csharp +var op = DownloadTextAsync("http://www.google.com"); +op.Completed += o => Debug.Log("1"); +op.AddCompletionCallback(o => Debug.Log("2")); +``` +Unlike `ContinueWith`-like stuff completion callbacks cannot be chained and do not handle exceptions automatically. Throwing an exception from a completion callback results in unspecified behavior. + +There is also a low-level continuation interface (`IAsyncContinuation`): ```csharp +class MyContinuation : IAsyncContinuation +{ + public void Invoke(IAsyncOperation op) => Debug.Log("Done"); +} + +// ... + var op = DownloadTextAsync("http://www.google.com"); -op.AddCompletionCallback(o => Debug.Log("Done")); -// or -op.Completed += o => Debug.Log("Done"); +op.AddContinuation(new MyContinuation()); ``` -Unlike `ContinueWith`-like stuff completion callbacks cannot be chained and do not handle exceptions. That's why they should be used with care. ### Disposing of operations -All operations implement [IDisposable](https://docs.microsoft.com/en-us/dotnet/api/system.idisposable) interface. So strictly speaking users should call `Dispose` when an operation is not in use. That said library implementation only requires this if `AsyncWaitHandle` was accessed. This behaviour closely matches [Tasks](https://blogs.msdn.microsoft.com/pfxteam/2012/03/25/do-i-need-to-dispose-of-tasks/). +All operations implement [IDisposable](https://docs.microsoft.com/en-us/dotnet/api/system.idisposable) interface. So strictly speaking users should call `Dispose` when an operation is not in use. That said library implementation only requires this if `AsyncWaitHandle` was accessed (just like [tasks](https://blogs.msdn.microsoft.com/pfxteam/2012/03/25/do-i-need-to-dispose-of-tasks/)). + +Please note that `Dispose` implementation is NOT thread-safe and it can only be called after an operation has completed (the same restrictions apply to [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task)). ### Completed asynchronous operations There are a number of helper methods for creating completed operations: @@ -216,6 +284,46 @@ var op3 = AsyncResult.FromException(new Exception()); var op4 = AsyncResult.FromCanceled(); ``` +### Creating own asynchronous operations +Most common way of creating own asynchronous operation is instantiating `AsyncCompletionSource` instance and call `SetResult` / `SetException` / `SetCanceled` when done. Still there are cases when more control is required. For this purpose the library provides two public extendable implementations for asynchronous operations: +* `AsyncResult`: an operation without a result value. +* `AsyncResult`: an asynchronous operation with a generic result value. + +The sample code below demostrates creating a delay operation: +```csharp +public class TimerDelayResult : AsyncResult +{ + private readonly Timer _timer; + + public TimerDelayResult(int millisecondsDelay) + : base(AsyncOperationStatus.Running) + { + _timer = new Timer(TimerCompletionCallback, this, millisecondsDelay, Timeout.Infinite); + } + + protected override void OnCompleted() + { + _timer.Dispose(); + base.OnCompleted(); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _timer.Dispose(); + } + + base.Dispose(disposing); + } + + private static void TimerCompletionCallback(object state) + { + (state as TimerDelayResult).TrySetCompleted(false); + } +} +``` + ## Comparison to .NET Tasks The comparison table below shows how *UnityFx.Async* entities relate to [Tasks](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task): From dca59dda489520a02b1d7d9a7fecb2a956ce9102 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sun, 8 Apr 2018 16:04:13 +0300 Subject: [PATCH 072/128] README update --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a92f967..b6139c2 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,10 @@ Unity Asset Store | [![Asynchronous operations for Unity](https://img.shields.io Library is designed as a lightweight [Unity3d](https://unity3d.com)-compatible [Tasks](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) alternative (not a replacement though). Main design goals are: - Minimum object size and number of allocations. -- Extensibility. The library operations are designed to be inherited (if needed). +- Extensibility. The library operations are designed to be inherited. - Thread-safe. The library classes can be safely used from different threads (unless explicitly stated otherwise). - [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task)-like interface and behaviour. In many cases library classes can be used much like corresponding [TPL](https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl) entities. -- [Unity3d](https://unity3d.com) compatibility. This includes possibility to yield any operations in coroutines and net35-compilance. +- [Unity3d](https://unity3d.com) compatibility. This includes possibility to yield operations in coroutines and net35-compilance. ## Getting Started ### Prerequisites @@ -351,6 +351,12 @@ The tables below contains comparison of performance to several other popular fra 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 Unity coroutines | ️️✔️ | - | - +Supports `async` / `await` | ✔️ | - | ✔️ +Supports `promise`-like continuations | ✔️ | ✔️ | - Operation data size for 32-bit systems (in bytes) | 28+ | 36+ | 40+ Number of allocations per continuation (`ContinueWith`/`Then`) | 1+ | 5+ | 2+ From 8d10a425c1c13f38c4011ec5fbc99a177badb077 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sun, 8 Apr 2018 22:43:41 +0300 Subject: [PATCH 073/128] README update --- README.md | 31 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index b6139c2..69ac5af 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,20 @@ Library is designed as a lightweight [Unity3d](https://unity3d.com)-compatible [ - [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task)-like interface and behaviour. In many cases library classes can be used much like corresponding [TPL](https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-parallel-library-tpl) entities. - [Unity3d](https://unity3d.com) compatibility. This includes possibility to yield operations in coroutines and net35-compilance. +The table below summarizes differences berween *UnityFx.Async* and other popular asynchronous operation frameworks: +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 continuations | ✔️ | ✔️ | ✔️ +Supports Unity coroutines | ️️✔️ | - | - +Supports `async` / `await` | ✔️ | - | ✔️ +Supports `promise`-like continuations | ✔️ | ✔️ | - +Supports child operations | - | - | ✔️ +Minimum operation data size for 32-bit systems (in bytes) | 28+ | 36+ | 40+ +Minimjm number of allocations per continuation | 1+ | 5+ | 2+ + ## Getting Started ### Prerequisites You may need the following software installed in order to build/use the library: @@ -273,7 +287,7 @@ op.AddContinuation(new MyContinuation()); ### Disposing of operations All operations implement [IDisposable](https://docs.microsoft.com/en-us/dotnet/api/system.idisposable) interface. So strictly speaking users should call `Dispose` when an operation is not in use. That said library implementation only requires this if `AsyncWaitHandle` was accessed (just like [tasks](https://blogs.msdn.microsoft.com/pfxteam/2012/03/25/do-i-need-to-dispose-of-tasks/)). -Please note that `Dispose` implementation is NOT thread-safe and it can only be called after an operation has completed (the same restrictions apply to [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task)). +Please note that `Dispose` implementation is NOT thread-safe and can only be called after an operation has completed (the same restrictions apply to [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task)). ### Completed asynchronous operations There are a number of helper methods for creating completed operations: @@ -345,21 +359,6 @@ Please note that the library is NOT a replacement for [Tasks](https://docs.micro - Memory usage is a concern ([Tasks](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) tend to do quite a lot of allocations). - An extendable [IAsyncResult](https://docs.microsoft.com/en-us/dotnet/api/system.iasyncresult) implementation is needed. -## Performance - -The tables below contains comparison of performance to several other popular frameworks (NOTE: this section needs performing more precise tests, the results below might not be accurate enough): - -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 Unity coroutines | ️️✔️ | - | - -Supports `async` / `await` | ✔️ | - | ✔️ -Supports `promise`-like continuations | ✔️ | ✔️ | - -Operation data size for 32-bit systems (in bytes) | 28+ | 36+ | 40+ -Number of allocations per continuation (`ContinueWith`/`Then`) | 1+ | 5+ | 2+ - ## Future work * Progress reporting (via [IProgress](https://docs.microsoft.com/en-us/dotnet/api/system.iprogress-1)). * Cancellation support (via [CancellationToken](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken)). From 9df57006a2ad62f5dbbfad28d06b8b12738027cd Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sun, 8 Apr 2018 22:45:36 +0300 Subject: [PATCH 074/128] tt --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 69ac5af..4036a00 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Library is designed as a lightweight [Unity3d](https://unity3d.com)-compatible [ The table below summarizes differences berween *UnityFx.Async* and other popular asynchronous operation frameworks: 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 | ✔️ | ✔️ | -️️ From e2f69145031bae379f1a13cba01025bb1ec4d0b5 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sun, 8 Apr 2018 22:46:16 +0300 Subject: [PATCH 075/128] README update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4036a00..11fa229 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Library is designed as a lightweight [Unity3d](https://unity3d.com)-compatible [ The table below summarizes differences berween *UnityFx.Async* and other popular asynchronous operation frameworks: 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 | ✔️ | ✔️ | -️️ From 46740e62b91c0e588d5aed32d69188f7651bd1aa Mon Sep 17 00:00:00 2001 From: Alexander Date: Sun, 8 Apr 2018 22:52:42 +0300 Subject: [PATCH 076/128] README update --- README.md | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 11fa229..13e5abd 100644 --- a/README.md +++ b/README.md @@ -19,18 +19,19 @@ Library is designed as a lightweight [Unity3d](https://unity3d.com)-compatible [ - [Unity3d](https://unity3d.com) compatibility. This includes possibility to yield operations in coroutines and net35-compilance. The table below summarizes differences berween *UnityFx.Async* and other popular asynchronous operation frameworks: -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 continuations | ✔️ | ✔️ | ✔️ -Supports Unity coroutines | ️️✔️ | - | - -Supports `async` / `await` | ✔️ | - | ✔️ -Supports `promise`-like continuations | ✔️ | ✔️ | - -Supports child operations | - | - | ✔️ -Minimum operation data size for 32-bit systems (in bytes) | 28+ | 36+ | 40+ -Minimjm number of allocations per continuation | 1+ | 5+ | 2+ + +| 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 continuations | ✔️ | ✔️ | ✔️ | +| Supports Unity coroutines | ️️✔️ | - | - | +| Supports `async` / `await` | ✔️ | - | ✔️ | +| Supports `promise`-like continuations | ✔️ | ✔️ | - | +| Supports child operations | - | - | ✔️ | +| Minimum operation data size for 32-bit systems (in bytes) | 28+ | 36+ | 40+ | +| Minimjm number of allocations per continuation | 1+ | 5+ | 2+ | ## Getting Started ### Prerequisites From ecef0aafea77770e41e790e7257c8fb32656cc7e Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sun, 8 Apr 2018 23:07:32 +0300 Subject: [PATCH 077/128] README update --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 13e5abd..1123e17 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ InitiateSomeAsyncOperation( Doesn't look that simple now, right? And that's just the async method calls without actual result processing and error handling. Production code would have `try` / `catch` blocks in each handler and much more result processing code. The code complexity (and maintainability problems as a result) produced by extensive callback usage is exactly what is called a [callback hell](http://callbackhell.com/). ### Unity coroutines - another way to shoot yourself in the foot -TODO +Unity coroutines are another popular approach to prorgamming asynchronous operations. ### Promises to the rescue TODO @@ -299,6 +299,19 @@ var op3 = AsyncResult.FromException(new Exception()); var op4 = AsyncResult.FromCanceled(); ``` +### Convertions +Library defines convertion methods between `IAsyncOperation` and `Task`, `IObservable`, `UnityWebRequest`, `AsyncOperation`, `WWW`: +```csharp +var task = op.ToTask(); +var observable = op.ToObservable(); + +var op1 = task.ToAsync(); +var op2 = observable.ToAsync(); +var op3 = unityWebRequest.ToAsync(); +var op4 = unityAsyncOperation.ToAsync(); +var op5 = unityWWW.ToAsync(); +``` + ### Creating own asynchronous operations Most common way of creating own asynchronous operation is instantiating `AsyncCompletionSource` instance and call `SetResult` / `SetException` / `SetCanceled` when done. Still there are cases when more control is required. For this purpose the library provides two public extendable implementations for asynchronous operations: * `AsyncResult`: an operation without a result value. From 40ae9f5304d45cff0a0052ca98ad45ce5cb5ecd9 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sun, 8 Apr 2018 23:26:43 +0300 Subject: [PATCH 078/128] README update --- README.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1123e17..a308565 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,12 @@ InitiateSomeAsyncOperation( Doesn't look that simple now, right? And that's just the async method calls without actual result processing and error handling. Production code would have `try` / `catch` blocks in each handler and much more result processing code. The code complexity (and maintainability problems as a result) produced by extensive callback usage is exactly what is called a [callback hell](http://callbackhell.com/). ### Unity coroutines - another way to shoot yourself in the foot -Unity coroutines are another popular approach to prorgamming asynchronous operations. +Coroutines are another popular approach to programming asynchronous operations available for Unity users by default. While it allows convenient way of operation chaining there are quite a lot of drawbacks that make it not suited well for large applications: +* Coroutines cannot return result values (since the return type must be `IEnumerator`). +* Coroutines can't handle exceptions, because `yield return` statements cannot be surrounded with a `try`-`catch` construction. This makes error handling a pain. +* Coroutine require a `MonoBehaviour` to run. +* There is no way to wait for a coroutine other than yield. +* There is no way to get coroutine state information. ### Promises to the rescue TODO @@ -411,5 +416,6 @@ Please see the [![license](https://img.shields.io/github/license/Arvtesh/UnityFx ## Acknowledgments Working on this project is a great experience. Please see below list of sources of my inspiration (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. +* [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. * Everyone who ever commented or left any feedback on the project. It's always very helpful. From 55e41be8cc1c8dba6ba276c4f7e5c488daac77e9 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Tue, 10 Apr 2018 12:22:19 +0300 Subject: [PATCH 079/128] README update --- README.md | 52 +++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a308565..34554ee 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ Library is designed as a lightweight [Unity3d](https://unity3d.com)-compatible [ The table below summarizes differences berween *UnityFx.Async* and other popular asynchronous operation frameworks: -| 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) | +| 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 | ✔️ | - | ✔️ | @@ -31,7 +31,7 @@ The table below summarizes differences berween *UnityFx.Async* and other popular | Supports `promise`-like continuations | ✔️ | ✔️ | - | | Supports child operations | - | - | ✔️ | | Minimum operation data size for 32-bit systems (in bytes) | 28+ | 36+ | 40+ | -| Minimjm number of allocations per continuation | 1+ | 5+ | 2+ | +| Minimum number of allocations per continuation | 1+ | 5+ | 2+ | ## Getting Started ### Prerequisites @@ -94,18 +94,60 @@ InitiateSomeAsyncOperation( Doesn't look that simple now, right? And that's just the async method calls without actual result processing and error handling. Production code would have `try` / `catch` blocks in each handler and much more result processing code. The code complexity (and maintainability problems as a result) produced by extensive callback usage is exactly what is called a [callback hell](http://callbackhell.com/). ### Unity coroutines - another way to shoot yourself in the foot -Coroutines are another popular approach to programming asynchronous operations available for Unity users by default. While it allows convenient way of operation chaining there are quite a lot of drawbacks that make it not suited well for large applications: +Coroutines are another popular approach of programming asynchronous operations available for Unity users by default. While it allows convenient way of operation chaining there are quite a lot of drawbacks that make it not suited well for large applications: * Coroutines cannot return result values (since the return type must be `IEnumerator`). * Coroutines can't handle exceptions, because `yield return` statements cannot be surrounded with a `try`-`catch` construction. This makes error handling a pain. * Coroutine require a `MonoBehaviour` to run. * There is no way to wait for a coroutine other than yield. * There is no way to get coroutine state information. +That said, here is the previous example rewrited using coroutines: +```csharp +var result = new MyResultType(); +yield return InitiateSomeAsyncOperation(result); + +var result2 = new MyResultType2(); +yield return InitiateAsyncOperation2(result, result2); + +var result3 = new MyResultType3(); +yield return InitiateAsyncOperation3(result2, result3); + +/// ... +/// No way to handle exceptions here +``` +As you can see while coroutines allow more streamlined coding, we have to wrap result values into custom classes and there is no easy way of error handling. + ### Promises to the rescue -TODO +Promises are a design pattern to structure asynchronous code as a sequence of chained (not nested!) operations. This concept was introduces for JS and has even become a [standard](https://promisesaplus.com/) since then. At low level a promise is an object containing a state (Running, Resolved or Rejected), a result value and (optionally) success/error callbacks. At high level the point of promises is to give us functional composition and error handling is the async world. + +Let's rewrite the last callback hell sample using promises: +```csharp +InitiateSomeAsyncOperation() + .Then(result => InitiateAsyncOperation2(result)) + .Then(result2 => InitiateAsyncOperation3(result2)) + .Then(result3 => /* ... */) + .Catch(e => /* Shared error handler */); +``` +This does exaclty the same job as the callbacks sample, but it's much more readable. + +That said promises are still not an ideal solution (at least for C#). They still require quite a lot of filler code at rely heavily on delegate usage. ### Asynchronous programming with async and await -TODO +C# 5.0/.NET 4.5 introduced a new appoach to asynchronous programming. By using `async` and `await` one can write an asynchronous methods almost as synchronous methods. The following example shows implementation of the callback hell method with this technique: +```csharp +try +{ + var result = await InitiateSomeAsyncOperation(); + var result2 = await InitiateAsyncOperation2(result); + var result3 = await InitiateAsyncOperation3(result2); + // ... +} +catch (Exception e) +{ + // Error handling code +} +``` +In fact the only notable difference from synchronous implementation is usage of the mentioned `async` and `await` keywords. It's worth mentioning that there is lots of hidden work done by both the C# compliter and asynchronous operation to allow this. ## Using the library Reference the DLL and import the namespace: From c442a8f6ad5441016734333cd02635a25cd6d0e6 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Tue, 10 Apr 2018 12:37:07 +0300 Subject: [PATCH 080/128] README update --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 34554ee..6aa250a 100644 --- a/README.md +++ b/README.md @@ -46,10 +46,10 @@ git clone https://github.com/Arvtesh/UnityFx.Async.git git submodule -q update --init ``` ### Getting binaries -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) from the editor. +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. ## Understanding the concepts -The below listed topics 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. +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. ### Callback hell Getting notified of an asynchronous operation completion via callbacks is the most common (as well as low-level) approach. It is very simple and obvious at first glance: ```csharp @@ -94,7 +94,7 @@ InitiateSomeAsyncOperation( Doesn't look that simple now, right? And that's just the async method calls without actual result processing and error handling. Production code would have `try` / `catch` blocks in each handler and much more result processing code. The code complexity (and maintainability problems as a result) produced by extensive callback usage is exactly what is called a [callback hell](http://callbackhell.com/). ### Unity coroutines - another way to shoot yourself in the foot -Coroutines are another popular approach of programming asynchronous operations available for Unity users by default. While it allows convenient way of operation chaining there are quite a lot of drawbacks that make it not suited well for large applications: +Coroutines are another popular approach of programming asynchronous operations available for Unity users by default. While it allows convenient way of operation chaining there are quite a lot of drawbacks that make it not suited well for large projects: * Coroutines cannot return result values (since the return type must be `IEnumerator`). * Coroutines can't handle exceptions, because `yield return` statements cannot be surrounded with a `try`-`catch` construction. This makes error handling a pain. * Coroutine require a `MonoBehaviour` to run. @@ -115,7 +115,7 @@ yield return InitiateAsyncOperation3(result2, result3); /// ... /// No way to handle exceptions here ``` -As you can see while coroutines allow more streamlined coding, we have to wrap result values into custom classes and there is no easy way of error handling. +As you can see we had to wrap result values into custom classes (which resulted in quire unobvious code) and no error handling can be done at this level (have to rely on the methods been called). ### Promises to the rescue Promises are a design pattern to structure asynchronous code as a sequence of chained (not nested!) operations. This concept was introduces for JS and has even become a [standard](https://promisesaplus.com/) since then. At low level a promise is an object containing a state (Running, Resolved or Rejected), a result value and (optionally) success/error callbacks. At high level the point of promises is to give us functional composition and error handling is the async world. @@ -130,7 +130,7 @@ InitiateSomeAsyncOperation() ``` This does exaclty the same job as the callbacks sample, but it's much more readable. -That said promises are still not an ideal solution (at least for C#). They still require quite a lot of filler code at rely heavily on delegate usage. +That said promises are still not an ideal solution (at least for C#). They require quite a lot of filler code and rely heavily on delegate usage. ### Asynchronous programming with async and await C# 5.0/.NET 4.5 introduced a new appoach to asynchronous programming. By using `async` and `await` one can write an asynchronous methods almost as synchronous methods. The following example shows implementation of the callback hell method with this technique: @@ -421,6 +421,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. ## Future work +* Implementation of noalloc completion callbacks. * Progress reporting (via [IProgress](https://docs.microsoft.com/en-us/dotnet/api/system.iprogress-1)). * Cancellation support (via [CancellationToken](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken)). From 2e008088b8523d26b0f39594d7ae3553686d3b9f Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Tue, 10 Apr 2018 13:07:05 +0300 Subject: [PATCH 081/128] Comment changes --- .../AsyncExtensions.Continuations.cs | 18 ++++ .../Extensions/AsyncExtensions.Promises.cs | 83 +++++++++++++------ 2 files changed, 74 insertions(+), 27 deletions(-) diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs index e101ef9..6618308 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs @@ -84,6 +84,7 @@ public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperation /// 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); @@ -97,6 +98,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, ActionOptions 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) @@ -115,6 +117,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, ActionA 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); @@ -129,6 +132,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, ActionOptions 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) @@ -146,6 +150,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, ActionAn 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); @@ -159,6 +164,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperatio /// 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) @@ -177,6 +183,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperatio /// 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); @@ -191,6 +198,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperatio /// 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) @@ -208,6 +216,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperatio /// 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); @@ -221,6 +230,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperationOptions 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) @@ -239,6 +249,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperationA 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); @@ -253,6 +264,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperationOptions 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) @@ -270,6 +282,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperationAn 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); @@ -283,6 +296,7 @@ public static IAsyncOperation ContinueWith(this /// 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) @@ -301,6 +315,7 @@ public static IAsyncOperation ContinueWith(this /// 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); @@ -315,6 +330,7 @@ public static IAsyncOperation ContinueWith(this /// 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) @@ -334,6 +350,7 @@ public static IAsyncOperation ContinueWith(this /// /// The source operation. /// The unwrapped operation. + /// public static IAsyncOperation Unwrap(this IAsyncOperation op) { return new UnwrapResult(op); @@ -344,6 +361,7 @@ public static IAsyncOperation Unwrap(this IAsyncOperation op) /// /// The source operation. /// The unwrapped operation. + /// public static IAsyncOperation Unwrap(this IAsyncOperation> op) { return new UnwrapResult(op); diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs index 61a416e..4fdbb55 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs @@ -11,11 +11,12 @@ partial class AsyncExtensions #region Then /// - /// Schedules a callback to be executed after the operation has succeeded. + /// Schedules a callback to be executed after the operation has been resolved. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the callback has completed. + /// /// public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback) { @@ -28,11 +29,12 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba } /// - /// Schedules a callback to be executed after the operation has succeeded. + /// Schedules a callback to be executed after the operation has been resolved. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the callback has completed. + /// /// public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback) { @@ -45,11 +47,13 @@ public static IAsyncOperation Then(this IAsyncOperation op, Ac } /// - /// Schedules a callback to be executed after the operation has succeeded. + /// Schedules a callback to be executed after the operation has been resolved. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the operation returned by has completed. + /// + /// /// public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback) { @@ -62,11 +66,13 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func - /// Schedules a callback to be executed after the operation has succeeded. + /// Schedules a callback to be executed after the operation has been resolved. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the operation returned by has completed. + /// + /// /// public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback) { @@ -79,11 +85,13 @@ public static IAsyncOperation Then(this IAsyncOperation op, Fu } /// - /// Schedules a callback to be executed after the operation has succeeded. + /// Schedules a callback to be executed after the operation has been resolved. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the operation returned by has completed. + /// + /// /// public static IAsyncOperation Then(this IAsyncOperation op, Func> successCallback) { @@ -96,11 +104,14 @@ public static IAsyncOperation Then(this IAsyncOperation op, Fu } /// - /// Schedules a callback to be executed after the operation has succeeded. + /// Schedules a callback to be executed after the operation has been resolved. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the operation returned by has completed. + /// + /// + /// /// public static IAsyncOperation Then(this IAsyncOperation op, Func> successCallback) { @@ -113,12 +124,13 @@ public static IAsyncOperation Then(this IAsyncO } /// - /// Schedules a callbacks to be executed after the operation has completed. + /// Schedules a callbacks to be executed after the operation has been resolved. /// /// An operation to be continued. /// The callback to be executed when the operation has succeeded. /// The callback to be executed when the operation has faulted/was canceled. /// Returns a continuation operation that completes after both source operation and the callback has completed. + /// /// public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback, Action errorCallback) { @@ -136,12 +148,13 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba } /// - /// Schedules a callbacks to be executed after the operation has completed. + /// Schedules a callbacks to be executed after the operation has been resolved. /// /// An operation to be continued. /// The callback to be executed when the operation has succeeded. /// The callback to be executed when the operation has faulted/was canceled. /// Returns a continuation operation that completes after both source operation and the callback has completed. + /// /// public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback, Action errorCallback) { @@ -159,12 +172,13 @@ public static IAsyncOperation Then(this IAsyncOperation op, Ac } /// - /// Adds a completion callback to be executed after the operation has succeeded. + /// Adds a completion callback to be executed after the operation has been resolved. /// /// An operation to be continued. /// The callback to be executed when the operation has succeeded. /// The callback to be executed when the operation has faulted/was canceled. /// Returns a continuation operation that completes after both source operation and the operation returned by has completed. + /// /// public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback, Action errorCallback) { @@ -182,12 +196,13 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func - /// Adds a completion callback to be executed after the operation has succeeded. + /// Adds a completion callback to be executed after the operation has been resolved. /// /// An operation to be continued. /// The callback to be executed when the operation has succeeded. /// The callback to be executed when the operation has faulted/was canceled. /// Returns a continuation operation that completes after both source operation and the operation returned by has completed. + /// /// public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback, Action errorCallback) { @@ -209,11 +224,12 @@ public static IAsyncOperation Then(this IAsyncOperation op, Fu #region ThenAll /// - /// Schedules a callback to be executed after the operation has succeeded. + /// Schedules a callback to be executed after the operation has been resolved. The resulting operation will complete after all of the operations in the callback return value have completed. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the callback has completed. + /// public static IAsyncOperation ThenAll(this IAsyncOperation op, Func> successCallback) { if (successCallback == null) @@ -225,11 +241,12 @@ public static IAsyncOperation ThenAll(this IAsyncOperation op, Func - /// Schedules a callback to be executed after the operation has succeeded. + /// Schedules a callback to be executed after the operation has been resolved. The resulting operation will complete after all of the operations in the callback return value have completed. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the callback has completed. + /// public static IAsyncOperation ThenAll(this IAsyncOperation op, Func> successCallback) { if (successCallback == null) @@ -241,11 +258,12 @@ public static IAsyncOperation ThenAll(this IAsyncOperation op, Func - /// Schedules a callback to be executed after the operation has succeeded. + /// Schedules a callback to be executed after the operation has been resolved. The resulting operation will complete after all of the operations in the callback return value have completed. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the callback has completed. + /// public static IAsyncOperation ThenAll(this IAsyncOperation op, Func>> successCallback) { if (successCallback == null) @@ -257,11 +275,12 @@ public static IAsyncOperation ThenAll(this IAsyncOperation op, Func - /// Schedules a callback to be executed after the operation has succeeded. + /// Schedules a callback to be executed after the operation has been resolved. The resulting operation will complete after all of the specified objects in an array have completed. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the callback has completed. + /// public static IAsyncOperation ThenAll(this IAsyncOperation op, Func>> successCallback) { if (successCallback == null) @@ -277,11 +296,12 @@ public static IAsyncOperation ThenAll(this IAsyncOperation op, Fun #region ThenAny /// - /// Schedules a callback to be executed after the operation has succeeded. + /// Schedules a callback to be executed after the operation has been resolved. The resulting operation will complete after any of the operations in the callback return value have completed. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the callback has completed. + /// public static IAsyncOperation ThenAny(this IAsyncOperation op, Func> successCallback) { if (successCallback == null) @@ -293,27 +313,29 @@ public static IAsyncOperation ThenAny(this IAsyncOperation op, Func - /// Schedules a callback to be executed after the operation has succeeded. + /// Schedules a callback to be executed after the operation has been resolved. The resulting operation will complete after any of the operations in the callback return value have completed. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the callback has completed. - public static IAsyncOperation ThenAny(this IAsyncOperation op, Func> successCallback) + /// + public static IAsyncOperation ThenAny(this IAsyncOperation op, Func> successCallback) { if (successCallback == null) { throw new ArgumentNullException(nameof(successCallback)); } - return new ThenAnyResult(op, successCallback, null); + return new ThenAnyResult(op, successCallback, null); } /// - /// Schedules a callback to be executed after the operation has succeeded. + /// Schedules a callback to be executed after the operation has been resolved. The resulting operation will complete after any of the operations in the callback return value have completed. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the callback has completed. + /// public static IAsyncOperation ThenAny(this IAsyncOperation op, Func>> successCallback) { if (successCallback == null) @@ -325,11 +347,12 @@ public static IAsyncOperation ThenAny(this IAsyncOperation op, } /// - /// Schedules a callback to be executed after the operation has succeeded. + /// Schedules a callback to be executed after the operation has been resolved. The resulting operation will complete after any of the operations in the callback return value have completed. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the callback has completed. + /// public static IAsyncOperation ThenAny(this IAsyncOperation op, Func>> successCallback) { if (successCallback == null) @@ -345,11 +368,12 @@ public static IAsyncOperation ThenAny(this IAsyncOperation< #region Rebind /// - /// Schedules a callback to be executed after the operation has succeeded. + /// Schedules a callback to be executed after the operation has been resolved. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the callback has completed. + /// public static IAsyncOperation Rebind(this IAsyncOperation op, Func successCallback) { if (successCallback == null) @@ -361,11 +385,12 @@ public static IAsyncOperation Rebind(this IAsyncOperation op, } /// - /// Schedules a callback to be executed after the operation has succeeded. + /// Schedules a callback to be executed after the operation has been resolved. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the callback has completed. + /// public static IAsyncOperation Rebind(this IAsyncOperation op, Func successCallback) { if (successCallback == null) @@ -381,11 +406,12 @@ public static IAsyncOperation Rebind(this IAsyn #region Catch /// - /// Adds a completion callback to be executed after the operation has faulted or was canceled. + /// Schedules a callback to be executed after the operation has been rejected. /// /// An operation to be continued. /// The callback to be executed when the operation has faulted/was canceled. /// Returns a continuation operation that completes after both source operation and the callback has completed. + /// /// public static IAsyncOperation Catch(this IAsyncOperation op, Action errorCallback) { @@ -398,11 +424,12 @@ public static IAsyncOperation Catch(this IAsyncOperation op, Action e } /// - /// Adds a completion callback to be executed after the operation has faulted or was canceled. + /// Schedules a callback to be executed after the operation has been rejected. /// /// An operation to be continued. /// The callback to be executed when the operation has faulted/was canceled. /// Returns a continuation operation that completes after both source operation and the callback has completed. + /// /// public static IAsyncOperation Catch(this IAsyncOperation op, Action errorCallback) where TException : Exception { @@ -419,12 +446,13 @@ public static IAsyncOperation Catch(this IAsyncOperation op, Action< #region ContinueWith /// - /// Creates a continuation that executes when the target completes. + /// Schedules a callback to be executed after the operation has completed. /// /// 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) { if (action == null) @@ -436,12 +464,13 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Func - /// Creates a continuation that executes when the target completes. + /// Schedules a callback to be executed after the operation has completed. /// /// 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) { if (action == null) @@ -457,7 +486,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperatio #region Finally /// - /// Adds a completion callback to be executed after the operation has completed. + /// Schedules a callback to be executed after the operation has completed. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. From 4383572b94dab61c15e1b78f684c11b2a7e01fc6 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 13 Apr 2018 16:20:23 +0300 Subject: [PATCH 082/128] Added await support for Unity built-in operations --- .../UnityFx.Async/Scripts/UnityExtensions.cs | 154 ++++++++++++++++++ src/UnityFx.Async.Tests/Tests/CatchTests.cs | 2 +- 2 files changed, 155 insertions(+), 1 deletion(-) diff --git a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/UnityExtensions.cs b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/UnityExtensions.cs index 1d2ac0b..d4044f8 100644 --- a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/UnityExtensions.cs +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/UnityExtensions.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using UnityEngine; #if UNITY_5_4_OR_NEWER || UNITY_2017 || UNITY_2018 using UnityEngine.Networking; @@ -106,6 +107,63 @@ public static IAsyncOperation ToAsync(this AssetBundleRequest op) where T } } +#if NET_4_6 || NETFX_CORE + + /// + /// Provides an object that waits for the completion of an . This type and its members are intended for compiler use only. + /// + public struct AsyncOperationAwaiter : INotifyCompletion + { + private readonly AsyncOperation _op; + + /// + /// Initializes a new instance of the struct. + /// + public AsyncOperationAwaiter(AsyncOperation op) + { + _op = op; + } + + /// + /// Gets a value indicating whether the underlying operation is completed. + /// + /// The operation completion flag. + public bool IsCompleted => _op.isDone; + + /// + /// Returns the source result value. + /// + public void GetResult() + { + } + + /// + public void OnCompleted(Action continuation) + { +#if UNITY_2017_2_OR_NEWER + + // Starting with Unity 2017.2 there is AsyncOperation.completed event + op.completed += o => continuation(); + +#else + + AsyncUtility.AddCompletionCallback(_op, () => continuation()); + +#endif + } + } + + /// + /// Returns the operation awaiter. This method is intended for compiler rather than use directly in code. + /// + /// The operation to await. + public static AsyncOperationAwaiter GetAwaiter(this AsyncOperation op) + { + return new AsyncOperationAwaiter(op); + } + +#endif + #endregion #region UnityWebRequest @@ -185,6 +243,54 @@ public static WebRequestResult ToAsyncString(this UnityWebRequest reques return WebRequestResult.FromUnityWebRequest(request); } +#if NET_4_6 || NETFX_CORE + + /// + /// Provides an object that waits for the completion of an . This type and its members are intended for compiler use only. + /// + public struct UnityWebRequestAwaiter : INotifyCompletion + { + private readonly UnityWebRequest _op; + + /// + /// Initializes a new instance of the struct. + /// + public UnityWebRequestAwaiter(UnityWebRequest op) + { + _op = op; + } + + /// + /// Gets a value indicating whether the underlying operation is completed. + /// + /// The operation completion flag. + public bool IsCompleted => _op.isDone; + + /// + /// Returns the source result value. + /// + public void GetResult() + { + } + + /// + public void OnCompleted(Action continuation) + { + AsyncUtility.AddCompletionCallback(_op, () => continuation()); + } + } + + /// + /// Returns the operation awaiter. This method is intended for compiler rather than use directly in code. + /// + /// The operation to await. + public static UnityWebRequestAwaiter GetAwaiter(this UnityWebRequest op) + { + return new UnityWebRequestAwaiter(op); + } + +#endif + #endif #endregion @@ -263,6 +369,54 @@ public static WwwResult ToAsyncString(this WWW request) return WwwResult.FromWWW(request); } +#if NET_4_6 || NETFX_CORE + + /// + /// Provides an object that waits for the completion of an . This type and its members are intended for compiler use only. + /// + public struct WwwAwaiter : INotifyCompletion + { + private readonly WWW _op; + + /// + /// Initializes a new instance of the struct. + /// + public WwwAwaiter(WWW op) + { + _op = op; + } + + /// + /// Gets a value indicating whether the underlying operation is completed. + /// + /// The operation completion flag. + public bool IsCompleted => _op.isDone; + + /// + /// Returns the source result value. + /// + public void GetResult() + { + } + + /// + public void OnCompleted(Action continuation) + { + AsyncUtility.AddCompletionCallback(_op, () => continuation()); + } + } + + /// + /// Returns the operation awaiter. This method is intended for compiler rather than use directly in code. + /// + /// The operation to await. + public static WwwAwaiter GetAwaiter(this WWW op) + { + return new WwwAwaiter(op); + } + +#endif + #endregion #region implementation diff --git a/src/UnityFx.Async.Tests/Tests/CatchTests.cs b/src/UnityFx.Async.Tests/Tests/CatchTests.cs index f5d7897..cb1ad05 100644 --- a/src/UnityFx.Async.Tests/Tests/CatchTests.cs +++ b/src/UnityFx.Async.Tests/Tests/CatchTests.cs @@ -94,7 +94,7 @@ public async Task Catch_ExceptionIsFiltered_3() { await op; } - catch (Exception e) + catch (Exception) { exceptionThrown = true; } From 5af25742b3d31d9c41125ccffbe06cec49bd8f49 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 13 Apr 2018 16:20:34 +0300 Subject: [PATCH 083/128] README update --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6aa250a..af79ddc 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Unity Asset Store | [![Asynchronous operations for Unity](https://img.shields.io ## Synopsis -*UnityFx.Async* is a set of of classes and interfaces that extend [Unity3d](https://unity3d.com) asynchronous operations and can be used very much like [Task-based Asynchronous Pattern (TAP)](https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming) in .NET or [Promises](https://developers.google.com/web/fundamentals/primers/promises) in Javascript. The library at its core defines a container ([AsyncResult](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncResult.html)) for state and result value of an 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). +*UnityFx.Async* is a set of classes and interfaces that extend [Unity3d](https://unity3d.com) asynchronous operations and can be used very much like [Task-based Asynchronous Pattern (TAP)](https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/task-based-asynchronous-programming) in .NET or [Promises](https://developers.google.com/web/fundamentals/primers/promises) in Javascript. The library at its core defines a container ([AsyncResult](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncResult.html)) for state and result value of an 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). Library is designed as a lightweight [Unity3d](https://unity3d.com)-compatible [Tasks](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) alternative (not a replacement though). Main design goals are: - Minimum object size and number of allocations. @@ -97,7 +97,7 @@ Doesn't look that simple now, right? And that's just the async method calls with Coroutines are another popular approach of programming asynchronous operations available for Unity users by default. While it allows convenient way of operation chaining there are quite a lot of drawbacks that make it not suited well for large projects: * Coroutines cannot return result values (since the return type must be `IEnumerator`). * Coroutines can't handle exceptions, because `yield return` statements cannot be surrounded with a `try`-`catch` construction. This makes error handling a pain. -* Coroutine require a `MonoBehaviour` to run. +* Coroutine requires a `MonoBehaviour` to run. * There is no way to wait for a coroutine other than yield. * There is no way to get coroutine state information. @@ -133,7 +133,7 @@ This does exaclty the same job as the callbacks sample, but it's much more reada That said promises are still not an ideal solution (at least for C#). They require quite a lot of filler code and rely heavily on delegate usage. ### Asynchronous programming with async and await -C# 5.0/.NET 4.5 introduced a new appoach to asynchronous programming. By using `async` and `await` one can write an asynchronous methods almost as synchronous methods. The following example shows implementation of the callback hell method with this technique: +C# 5.0/.NET 4.5 introduced a new appoach to asynchronous programming. By using `async` and `await` one can write asynchronous methods almost as synchronous methods. The following example shows implementation of the callback hell method with this technique: ```csharp try { @@ -147,7 +147,7 @@ catch (Exception e) // Error handling code } ``` -In fact the only notable difference from synchronous implementation is usage of the mentioned `async` and `await` keywords. It's worth mentioning that there is lots of hidden work done by both the C# compliter and asynchronous operation to allow this. +In fact the only notable difference from synchronous implementation is usage of the mentioned `async` and `await` keywords. It's worth mentioning that a lot of hidden work is done by both the C# compliter and asynchronous operation to allow this. ## Using the library Reference the DLL and import the namespace: @@ -224,7 +224,7 @@ else if (op.IsCanceled) Debug.LogWarning("The operation was canceled."); } ``` -With Unity 2017+ and .NET 4.6 it can be used just like a [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task). The await continuation is scheduled on a captured [SynchronizationContext](https://docs.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext) (if any): +With Unity 2017+ and .NET 4.6 it can be used just like a [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task). An await continuation is scheduled on a captured [SynchronizationContext](https://docs.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext) (if any): ```csharp try { @@ -267,7 +267,7 @@ DownloadTextAsync("http://www.google.com") ``` The chain of processing ends as soon as an exception occurs. In this case when an error occurs the `Catch` handler would be called. -`Then` continuations get executed only if previous operation in the chain completed successfully. Otherwise they are skipped. Also note that `Then` expects the handler return value to be another operation. +`Then` continuations get executed only if previous operation in the chain completed successfully. Otherwise, they are skipped. Note that `Then` expects the handler return value to be another operation. `Rebind` is a special kind of continuation for transforming operation result to a different type: ```csharp From 04311c9f67cc938ed4275d0183c35e4cc3361122 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 13 Apr 2018 20:09:46 +0300 Subject: [PATCH 084/128] Unity awaiters fixes --- .../Assets/UnityFx.Async/Scripts/UnityExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/UnityExtensions.cs b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/UnityExtensions.cs index d4044f8..6f6da99 100644 --- a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/UnityExtensions.cs +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/UnityExtensions.cs @@ -147,7 +147,7 @@ public void OnCompleted(Action continuation) #else - AsyncUtility.AddCompletionCallback(_op, () => continuation()); + AsyncUtility.AddCompletionCallback(_op, continuation); #endif } @@ -276,7 +276,7 @@ public void GetResult() /// public void OnCompleted(Action continuation) { - AsyncUtility.AddCompletionCallback(_op, () => continuation()); + AsyncUtility.AddCompletionCallback(_op, continuation); } } @@ -402,7 +402,7 @@ public void GetResult() /// public void OnCompleted(Action continuation) { - AsyncUtility.AddCompletionCallback(_op, () => continuation()); + AsyncUtility.AddCompletionCallback(_op, continuation); } } From dd16f68117fdf831e4515ffe7fc2f611ced52233 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 16 Apr 2018 13:17:07 +0300 Subject: [PATCH 085/128] Added cancellation request infractructure --- .../Scripts/WebRequestResult{T}.cs | 20 ++-- .../Api/Core/AsyncCompletionSource.cs | 10 ++ .../Core/AsyncCompletionSource{TResult}.cs | 10 ++ src/UnityFx.Async/Api/Core/AsyncResult.cs | 98 ++++++++++++++++++- 4 files changed, 124 insertions(+), 14 deletions(-) diff --git a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/WebRequestResult{T}.cs b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/WebRequestResult{T}.cs index 8b1fc1e..bb98a8d 100644 --- a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/WebRequestResult{T}.cs +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/WebRequestResult{T}.cs @@ -60,17 +60,6 @@ public WebRequestResult(UnityWebRequest request) _request = request; } - /// - /// Attempts to calcel the web request. - /// - public void Cancel() - { - if (TrySetCanceled(false)) - { - _request.Abort(); - } - } - /// /// Initializes the operation result value. Called when the underlying has completed withou errors. /// @@ -152,6 +141,15 @@ protected override void OnStarted() #endif } + /// + protected override void OnCancel() + { + if (TrySetCanceled(false)) + { + _request.Abort(); + } + } + /// protected override void Dispose(bool disposing) { diff --git a/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs b/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs index ea966f7..d286b14 100644 --- a/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs +++ b/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs @@ -290,6 +290,16 @@ public void SetCompleted(bool completedSynchronously) #endregion + #region AsyncResult + + /// + protected override void OnCancel() + { + base.TrySetCanceled(false); + } + + #endregion + #region IAsyncCompletionSource /// diff --git a/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs b/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs index 87d6cb1..8a5e87f 100644 --- a/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs @@ -294,6 +294,16 @@ public void SetResult(TResult result, bool completedSynchronously) #endregion + #region AsyncResult + + /// + protected override void OnCancel() + { + base.TrySetCanceled(false); + } + + #endregion + #region IAsyncCompletionSource /// diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 686b95f..2a35404 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -52,9 +52,10 @@ public class AsyncResult : IAsyncOperation, IEnumerator { #region data - private const int _flagCompletionReserved = 0x00100000; - private const int _flagCompleted = 0x00200000; - private const int _flagSynchronous = 0x00400000; + private const int _flagCompletionReserved = 0x00010000; + private const int _flagCompleted = 0x00020000; + private const int _flagSynchronous = 0x00040000; + private const int _flagCancellationRequested = 0x00100000; private const int _flagCompletedSynchronously = _flagCompleted | _flagCompletionReserved | _flagSynchronous; private const int _flagDisposed = 0x01000000; private const int _flagDoNotDispose = 0x10000000; @@ -91,6 +92,12 @@ public class AsyncResult : IAsyncOperation, IEnumerator /// The disposed flag. protected bool IsDisposed => (_flags & _flagDisposed) != 0; + /// + /// Gets a value indicating whether the operation cancellation was requested. + /// + /// The cancellation request flag. + protected bool IsCancellationRequested => (_flags & _flagCancellationRequested) != 0; + /// /// Initializes a new instance of the class. /// @@ -225,6 +232,7 @@ public void Start() /// Attempts to transitions the operation into the state. /// /// Thrown is the operation is disposed. + /// Returns if the operation status was changed to ; otherwise. /// /// /// @@ -233,6 +241,43 @@ public bool TryStart() return TrySetRunning(); } + /// + /// Requests the operation cancellation (if possible). + /// + /// Thrown if the transition has failed. + /// Thrown is the operation is disposed. + /// + /// + /// + public void Cancel() + { + if (!TryCancel()) + { + throw new InvalidOperationException(); + } + } + + /// + /// Attempts to request the operation cancellation. Calling this method multiple times or when the operation is completed results in a failure. + /// + /// Thrown is the operation is disposed. + /// Returns if the cancellation request was made; otherwise. + /// + /// + /// + public bool TryCancel() + { + ThrowIfDisposed(); + + if (TrySetFlag(_flagCancellationRequested)) + { + OnCancel(); + return true; + } + + return false; + } + /// /// Attempts to transition the operation into the state. /// @@ -523,6 +568,16 @@ protected virtual void OnStarted() { } + /// + /// Called when the operation cancellation has been requested. Default implementation throws . + /// + /// + /// + protected virtual void OnCancel() + { + throw new NotSupportedException(); + } + /// /// Called when the operation is completed. Default implementation invokes completion handlers registered. /// @@ -1533,6 +1588,43 @@ internal bool TryReserveCompletion() } while (true); +#endif + } + + /// + /// Attempts to add a new flag value. + /// + internal bool TrySetFlag(int newFlag) + { +#if UNITYFX_NOT_THREAD_SAFE + + var flags = _flags; + + if ((flags & (newFlag | _flagCompletionReserved | _flagCompleted)) != 0) + { + return false; + } + + _flags = flags | newFlag; + return true; +#else + + do + { + var flags = _flags; + + if ((flags & (newFlag | _flagCompletionReserved | _flagCompleted)) != 0) + { + return false; + } + + if (Interlocked.CompareExchange(ref _flags, flags | newFlag, flags) == flags) + { + return true; + } + } + while (true); + #endif } From c2bb5003392f604609489fa8dc5e2c09c4b18acf Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 16 Apr 2018 14:52:05 +0300 Subject: [PATCH 086/128] README update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index af79ddc..c104a39 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ The table below summarizes differences berween *UnityFx.Async* and other popular | Supports Unity coroutines | ️️✔️ | - | - | | Supports `async` / `await` | ✔️ | - | ✔️ | | Supports `promise`-like continuations | ✔️ | ✔️ | - | +| Supports cancellation | ✔️ | -️ | ✔️ | | Supports child operations | - | - | ✔️ | | Minimum operation data size for 32-bit systems (in bytes) | 28+ | 36+ | 40+ | | Minimum number of allocations per continuation | 1+ | 5+ | 2+ | @@ -423,7 +424,6 @@ Please note that the library is NOT a replacement for [Tasks](https://docs.micro ## Future work * Implementation of noalloc completion callbacks. * Progress reporting (via [IProgress](https://docs.microsoft.com/en-us/dotnet/api/system.iprogress-1)). -* Cancellation support (via [CancellationToken](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken)). ## 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 the like 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 for the best of human kind. From b8ad65c5a61e59e2fc0ecd8ad324c461cfb317fb Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 16 Apr 2018 18:50:32 +0300 Subject: [PATCH 087/128] Added IAsyncCancellable interface --- src/UnityFx.Async/Api/Core/AsyncResult.cs | 55 ++++++------------- .../Extensions/AsyncExtensions.Promises.cs | 39 +++++++++++++ .../Api/Interfaces/IAsyncCancellable.cs | 20 +++++++ .../Continuations/ContinuationResult{T}.cs | 12 ++++ .../Continuations/ThenResult{T,U}.cs | 37 +++++++++++-- .../Specialized/WhenAllResult{T}.cs | 5 -- .../Specialized/WhenAnyResult{T}.cs | 5 -- 7 files changed, 119 insertions(+), 54 deletions(-) create mode 100644 src/UnityFx.Async/Api/Interfaces/IAsyncCancellable.cs diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 2a35404..9dfe480 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -48,7 +48,7 @@ namespace UnityFx.Async /// /// [DebuggerDisplay("{DebuggerDisplay,nq}")] - public class AsyncResult : IAsyncOperation, IEnumerator + public class AsyncResult : IAsyncOperation, IAsyncCancellable, IEnumerator { #region data @@ -241,43 +241,6 @@ public bool TryStart() return TrySetRunning(); } - /// - /// Requests the operation cancellation (if possible). - /// - /// Thrown if the transition has failed. - /// Thrown is the operation is disposed. - /// - /// - /// - public void Cancel() - { - if (!TryCancel()) - { - throw new InvalidOperationException(); - } - } - - /// - /// Attempts to request the operation cancellation. Calling this method multiple times or when the operation is completed results in a failure. - /// - /// Thrown is the operation is disposed. - /// Returns if the cancellation request was made; otherwise. - /// - /// - /// - public bool TryCancel() - { - ThrowIfDisposed(); - - if (TrySetFlag(_flagCancellationRequested)) - { - OnCancel(); - return true; - } - - return false; - } - /// /// Attempts to transition the operation into the state. /// @@ -572,7 +535,6 @@ protected virtual void OnStarted() /// Called when the operation cancellation has been requested. Default implementation throws . /// /// - /// protected virtual void OnCancel() { throw new NotSupportedException(); @@ -1916,6 +1878,21 @@ public WaitHandle AsyncWaitHandle #endregion + #region IAsyncCancellable + + /// + public void Cancel() + { + ThrowIfDisposed(); + + if (TrySetFlag(_flagCancellationRequested)) + { + OnCancel(); + } + } + + #endregion + #region IEnumerator /// diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs index 4fdbb55..7351a4e 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Threading; namespace UnityFx.Async { @@ -443,6 +444,44 @@ public static IAsyncOperation Catch(this IAsyncOperation op, Action< #endregion + #region WithCancellation + +#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 oepration. + /// Returns the source operation. + public static IAsyncOperation WithCancellation(this IAsyncOperation op, CancellationToken cancellationToken) + { + if (cancellationToken.CanBeCanceled && !op.IsCompleted) + { + if (op is IAsyncCancellable c) + { + if (cancellationToken.IsCancellationRequested) + { + c.Cancel(); + } + else + { + cancellationToken.Register(c.Cancel); + } + } + else + { + throw new NotSupportedException(); + } + } + + return op; + } + +#endif + + #endregion + #region ContinueWith /// diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncCancellable.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncCancellable.cs new file mode 100644 index 0000000..8526219 --- /dev/null +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncCancellable.cs @@ -0,0 +1,20 @@ +// 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 cancellable operation. + /// + /// + public interface IAsyncCancellable + { + /// + /// Attempts to cancel the operation. + /// + /// Thrown is the operation is disposed. + void Cancel(); + } +} diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs index c6b6ea9..40a1049 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs @@ -79,5 +79,17 @@ protected void InvokeOnSyncContext(IAsyncOperation op, bool completedSynchronous protected abstract void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously); #endregion + + #region AsyncResult + + protected override void OnCancel() + { + if (_op is IAsyncCancellable c) + { + c.Cancel(); + } + } + + #endregion } } diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs index 74ffd3e..56823ba 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs @@ -12,6 +12,8 @@ internal class ThenResult : ContinuationResult, IAsyncContinuation private readonly object _successCallback; private readonly Action _errorCallback; + private IAsyncOperation _continuation; + #endregion #region interface @@ -44,19 +46,23 @@ protected virtual void InvokeSuccessCallback(IAsyncOperation op, bool completedS break; case Func> f3: - f3().AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); + _continuation = f3(); + _continuation.AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); break; case Func f1: - f1().AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); + _continuation = f1(); + _continuation.AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); break; case Func> f4: - f4((op as IAsyncOperation).Result).AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); + _continuation = f4((op as IAsyncOperation).Result); + _continuation.AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); break; case Func f2: - f2((op as IAsyncOperation).Result).AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); + _continuation = f2((op as IAsyncOperation).Result); + _continuation.AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); break; default: @@ -73,7 +79,14 @@ protected sealed override void InvokeUnsafe(IAsyncOperation op, bool completedSy { if (op.IsCompletedSuccessfully) { - InvokeSuccessCallback(op, completedSynchronously, _successCallback); + if (IsCancellationRequested) + { + TrySetCanceled(completedSynchronously); + } + else + { + InvokeSuccessCallback(op, completedSynchronously, _successCallback); + } } else { @@ -83,6 +96,20 @@ protected sealed override void InvokeUnsafe(IAsyncOperation op, bool completedSy #endregion + #region AsyncResult + + protected override void OnCancel() + { + base.OnCancel(); + + if (_continuation is IAsyncCancellable c) + { + c.Cancel(); + } + } + + #endregion + #region IAsyncContinuation public void Invoke(IAsyncOperation op) diff --git a/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs index 2fe80ed..6332300 100644 --- a/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs @@ -38,11 +38,6 @@ public WhenAllResult(IAsyncOperation[] ops) _completedSynchronously = false; } - public void Cancel() - { - TrySetCanceled(false); - } - #endregion #region IAsyncContinuation diff --git a/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs index ae0db5a..adb22c0 100644 --- a/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs @@ -30,11 +30,6 @@ public WhenAnyResult(T[] ops) } } - public void Cancel() - { - TrySetCanceled(false); - } - #endregion #region IAsyncContinuation From 05079ea6e51b957072b81204cd9a7e42e2923769 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 16 Apr 2018 18:52:49 +0300 Subject: [PATCH 088/128] CHANGELOG update --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 72507c1..14cab48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/); this proj - Added `TryAddContinuation`/`RemoveContinuation` methods to `IAsyncOperationEvents` for non-delegate continuations. - Added `IAsyncUpdatable` and `IAsyncUpdateSource` interfaces. - Added `Delay` overload that uses `IAsyncUpdateSource`-based service for time management. +- Added cancellation support (`IAsyncCancellable` interface). ### Changed - Changed `ContinueWith` extension signatures to match corresponding `Task` methods. From 2408622e2c0bd3dcc03bdb3d67c9d3ca30e933ae Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 16 Apr 2018 21:18:21 +0300 Subject: [PATCH 089/128] Removed async ContinueWith overloads --- .../Extensions/AsyncExtensions.Promises.cs | 40 ------------------- 1 file changed, 40 deletions(-) diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs index 7351a4e..0efe87b 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs @@ -482,46 +482,6 @@ public static IAsyncOperation WithCancellation(this IAsyncOperation op, Cancella #endregion - #region ContinueWith - - /// - /// Schedules a callback to be executed after the operation has completed. - /// - /// 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) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - return new FinallyResult(op, action); - } - - /// - /// Schedules a callback to be executed after the operation has completed. - /// - /// 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) - { - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - return new FinallyResult(op, action); - } - - #endregion - #region Finally /// From 24cacebd55932cb90ac6bea6752afdeb8c49d355 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 16 Apr 2018 21:18:38 +0300 Subject: [PATCH 090/128] Many cancellation-related fixes --- .../Continuations/CatchResult{T,TException}.cs | 4 ++-- .../Continuations/ContinueWithResult{T,U}.cs | 4 ++-- .../Continuations/ContinueWithResult{T}.cs | 4 ++-- .../Continuations/FinallyResult{T}.cs | 4 ++-- .../Continuations/RebindResult{T,U}.cs | 4 ++-- .../Continuations/ThenAllResult{T,U}.cs | 14 ++++++++++++-- .../Continuations/ThenAnyResult{T,U}.cs | 14 ++++++++++++-- .../Continuations/ThenResult{T,U}.cs | 2 +- .../Continuations/UnwrapResult{T}.cs | 2 +- 9 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs b/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs index 44239c3..8340d47 100644 --- a/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs @@ -5,7 +5,7 @@ namespace UnityFx.Async { - internal class CatchResult : ContinuationResult, IAsyncContinuation where TException : Exception + internal sealed class CatchResult : ContinuationResult, IAsyncContinuation where TException : Exception { #region data @@ -29,7 +29,7 @@ public CatchResult(IAsyncOperation op, Action errorCallback) #endregion - #region PromiseResult + #region ContinuationResult protected override void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously) { diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs index 43f184b..88b11a7 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs @@ -5,7 +5,7 @@ namespace UnityFx.Async { - internal class ContinueWithResult : ContinuationResult, IAsyncContinuation + internal sealed class ContinueWithResult : ContinuationResult, IAsyncContinuation { #region data @@ -33,7 +33,7 @@ internal ContinueWithResult(IAsyncOperation op, AsyncContinuationOptions options #endregion - #region PromiseResult + #region ContinuationResult protected override void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously) { diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs index aa02938..1b90e7e 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs @@ -5,7 +5,7 @@ namespace UnityFx.Async { - internal class ContinueWithResult : ContinuationResult, IAsyncContinuation + internal sealed class ContinueWithResult : ContinuationResult, IAsyncContinuation { #region data @@ -33,7 +33,7 @@ internal ContinueWithResult(IAsyncOperation op, AsyncContinuationOptions options #endregion - #region PromiseResult + #region ContinuationResult protected override void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously) { diff --git a/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs index 38d9ada..b3bdb42 100644 --- a/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs @@ -5,7 +5,7 @@ namespace UnityFx.Async { - internal class FinallyResult : ContinuationResult, IAsyncContinuation + internal sealed class FinallyResult : ContinuationResult, IAsyncContinuation { #region data @@ -29,7 +29,7 @@ public FinallyResult(IAsyncOperation op, object action) #endregion - #region PromiseResult + #region ContinuationResult protected override void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously) { diff --git a/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs index 9302572..af617f7 100644 --- a/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs @@ -6,7 +6,7 @@ namespace UnityFx.Async { - internal class RebindResult : ContinuationResult, IAsyncContinuation + internal sealed class RebindResult : ContinuationResult, IAsyncContinuation { #region data @@ -30,7 +30,7 @@ public RebindResult(IAsyncOperation op, object action) #endregion - #region PromiseResult + #region ContinuationResult protected override void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously) { diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenAllResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ThenAllResult{T,U}.cs index 2f0b935..b2b4fd6 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ThenAllResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ThenAllResult{T,U}.cs @@ -7,11 +7,11 @@ namespace UnityFx.Async { - internal class ThenAllResult : ThenResult + internal sealed class ThenAllResult : ThenResult { #region data - private IAsyncOperation _op2; + private WhenAllResult _op2; #endregion @@ -59,6 +59,16 @@ protected override void InvokeSuccessCallback(IAsyncOperation op, bool completed #endregion + #region AsyncResult + + protected override void OnCancel() + { + base.OnCancel(); + _op2?.Cancel(); + } + + #endregion + #region implementation #endregion } diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs index 13f60bb..ca472a9 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs @@ -7,11 +7,11 @@ namespace UnityFx.Async { - internal class ThenAnyResult : ThenResult + internal sealed class ThenAnyResult : ThenResult { #region data - private IAsyncOperation _op2; + private WhenAnyResult _op2; #endregion @@ -80,6 +80,16 @@ protected override void InvokeSuccessCallback(IAsyncOperation op, bool completed #endregion + #region AsyncResult + + protected override void OnCancel() + { + base.OnCancel(); + _op2?.Cancel(); + } + + #endregion + #region implementation #endregion } diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs index 56823ba..df6b6c8 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs @@ -73,7 +73,7 @@ protected virtual void InvokeSuccessCallback(IAsyncOperation op, bool completedS #endregion - #region PromiseResult + #region ContinuationResult protected sealed override void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously) { diff --git a/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs index cb301e9..c2dfe73 100644 --- a/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs @@ -6,7 +6,7 @@ namespace UnityFx.Async { - internal class UnwrapResult : AsyncResult, IAsyncContinuation + internal sealed class UnwrapResult : AsyncResult, IAsyncContinuation { #region data From 229966d28b0386293bb9800025f5d4b16fd3e443 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 16 Apr 2018 21:20:22 +0300 Subject: [PATCH 091/128] Minor project structure adjustments --- src/UnityFx.Async/Implementation/{ => Common}/AssemblyInfo.cs | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/UnityFx.Async/Implementation/{ => Common}/AssemblyInfo.cs (100%) diff --git a/src/UnityFx.Async/Implementation/AssemblyInfo.cs b/src/UnityFx.Async/Implementation/Common/AssemblyInfo.cs similarity index 100% rename from src/UnityFx.Async/Implementation/AssemblyInfo.cs rename to src/UnityFx.Async/Implementation/Common/AssemblyInfo.cs From f9730a4094bb6958c5b4b4aa2bb1cc3b1de376af Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 16 Apr 2018 21:50:34 +0300 Subject: [PATCH 092/128] Added cancellation support to specialized operations --- .../Specialized/RetryResult{T}.cs | 21 ++++++++++++++++- .../Specialized/TimerDelayResult.cs | 23 +++++++++---------- .../Specialized/UpdatableDelayResult.cs | 16 ++++++++++++- .../Specialized/WhenAllResult{T}.cs | 15 ++++++++++++ .../Specialized/WhenAnyResult{T}.cs | 15 ++++++++++++ 5 files changed, 76 insertions(+), 14 deletions(-) diff --git a/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs index c4b44fb..6cc8e8b 100644 --- a/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs @@ -39,6 +39,14 @@ internal RetryResult(object opFactory, int millisecondsRetryDelay, int maxRetryC #region AsyncResult + protected override void OnCancel() + { + if (_op is IAsyncCancellable c) + { + c.Cancel(); + } + } + protected override void OnCompleted() { base.OnCompleted(); @@ -65,6 +73,10 @@ public void Invoke(IAsyncOperation op) { SetResult(false); } + else if (IsCancellationRequested) + { + TrySetCanceled(false); + } else if (_millisecondsRetryDelay > 0) { if (_timerCallback == null) @@ -131,7 +143,14 @@ private void OnTimer(object args) { if (!IsCompleted) { - Retry(false); + if (IsCancellationRequested) + { + TrySetCanceled(false); + } + else + { + Retry(false); + } } } diff --git a/src/UnityFx.Async/Implementation/Specialized/TimerDelayResult.cs b/src/UnityFx.Async/Implementation/Specialized/TimerDelayResult.cs index 7cf5321..176bafd 100644 --- a/src/UnityFx.Async/Implementation/Specialized/TimerDelayResult.cs +++ b/src/UnityFx.Async/Implementation/Specialized/TimerDelayResult.cs @@ -19,13 +19,22 @@ internal class TimerDelayResult : AsyncResult public TimerDelayResult(int millisecondsDelay) : base(AsyncOperationStatus.Running) { - _timer = new Timer(TimerCompletionCallback, this, millisecondsDelay, Timeout.Infinite); + _timer = new Timer( + state => (state as AsyncResult).TrySetCompleted(false), + this, + millisecondsDelay, + Timeout.Infinite); } #endregion #region AsyncResult + protected override void OnCancel() + { + TrySetCanceled(false); + } + protected override void OnCompleted() { _timer.Dispose(); @@ -36,22 +45,12 @@ protected override void Dispose(bool disposing) { if (disposing) { - _timer?.Dispose(); + _timer.Dispose(); } base.Dispose(disposing); } #endregion - - #region implementation - - private static void TimerCompletionCallback(object state) - { - var asyncResult = state as AsyncResult; - asyncResult.TrySetCompleted(false); - } - - #endregion } } diff --git a/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs b/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs index 367f761..1b647e9 100644 --- a/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs +++ b/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs @@ -26,6 +26,21 @@ public UpdatableDelayResult(int millisecondsDelay, IAsyncUpdateSource updateSour #endregion + #region AsyncResult + + protected override void OnCancel() + { + TrySetCanceled(false); + } + + protected override void OnCompleted() + { + _updateService.RemoveListener(this); + base.OnCompleted(); + } + + #endregion + #region IAsyncUpdatable public void Update(float frameTime) @@ -35,7 +50,6 @@ public void Update(float frameTime) if (_timer <= 0) { TrySetCompleted(false); - _updateService.RemoveListener(this); } } diff --git a/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs index 6332300..06595c3 100644 --- a/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs @@ -40,6 +40,21 @@ public WhenAllResult(IAsyncOperation[] ops) #endregion + #region AsyncResult + + protected override void OnCancel() + { + foreach (var op in _ops) + { + if (op is IAsyncCancellable c) + { + c.Cancel(); + } + } + } + + #endregion + #region IAsyncContinuation public void Invoke(IAsyncOperation asyncOp) diff --git a/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs index adb22c0..e1ce406 100644 --- a/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs @@ -32,6 +32,21 @@ public WhenAnyResult(T[] ops) #endregion + #region AsyncResult + + protected override void OnCancel() + { + foreach (var op in _ops) + { + if (op is IAsyncCancellable c) + { + c.Cancel(); + } + } + } + + #endregion + #region IAsyncContinuation public void Invoke(IAsyncOperation op) From 3d3e5f3557bb77604ad7caa4e8aa821dcc29b99c Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 16 Apr 2018 22:26:34 +0300 Subject: [PATCH 093/128] Added Then/WithCancellation tests --- src/UnityFx.Async.Tests/Tests/ThenAllTests.cs | 27 ++++++++++++++++++ src/UnityFx.Async.Tests/Tests/ThenAnyTests.cs | 28 ++++++++++++++++++- src/UnityFx.Async.Tests/Tests/ThenTests.cs | 24 ++++++++++++++++ .../Continuations/ThenAnyResult{T,U}.cs | 6 +++- 4 files changed, 83 insertions(+), 2 deletions(-) diff --git a/src/UnityFx.Async.Tests/Tests/ThenAllTests.cs b/src/UnityFx.Async.Tests/Tests/ThenAllTests.cs index 77a4429..bb6f42b 100644 --- a/src/UnityFx.Async.Tests/Tests/ThenAllTests.cs +++ b/src/UnityFx.Async.Tests/Tests/ThenAllTests.cs @@ -26,5 +26,32 @@ public async Task ThenAll_CompletesWhenAllOperationsComplete() Assert.True(op2.IsCompleted); Assert.True(op3.IsCompleted); } + + [Fact] + public async Task ThenAll_CompletesWhenCancelled() + { + // Arrange + var cs = new CancellationTokenSource(); + var op1 = AsyncResult.CompletedOperation; + var op2 = new AsyncCompletionSource(); + var op3 = new AsyncCompletionSource(); + var op = op1.ThenAll(() => new IAsyncOperation[] { op2, op3 }).WithCancellation(cs.Token); + + cs.Cancel(); + + // Act + try + { + await op; + } + catch (OperationCanceledException) + { + } + + // Assert + Assert.True(op.IsCanceled); + Assert.True(op2.IsCanceled); + Assert.True(op3.IsCanceled); + } } } diff --git a/src/UnityFx.Async.Tests/Tests/ThenAnyTests.cs b/src/UnityFx.Async.Tests/Tests/ThenAnyTests.cs index 070fed2..cf9e2c4 100644 --- a/src/UnityFx.Async.Tests/Tests/ThenAnyTests.cs +++ b/src/UnityFx.Async.Tests/Tests/ThenAnyTests.cs @@ -11,7 +11,7 @@ namespace UnityFx.Async public class ThenAnyTests { [Fact] - public async Task ThenAll_CompletesWhenAllOperationsComplete() + public async Task ThenAny_CompletesWhenAllOperationsComplete() { // Arrange var op1 = AsyncResult.Delay(1); @@ -25,5 +25,31 @@ public async Task ThenAll_CompletesWhenAllOperationsComplete() Assert.True(op1.IsCompleted); Assert.True(op2.IsCompleted); } + + [Fact] + public async Task ThenAny_CompletesWhenCanceled() + { + // Arrange + var cs = new CancellationTokenSource(); + cs.Cancel(); + + var op1 = new AsyncCompletionSource(); + var op2 = new AsyncCompletionSource(); + var op = AsyncResult.CompletedOperation.ThenAny(() => new IAsyncOperation[] { op2, op1 }).WithCancellation(cs.Token); + + // Act + try + { + await op; + } + catch (OperationCanceledException) + { + } + + // Assert + Assert.True(op.IsCanceled); + Assert.True(op1.IsCanceled); + Assert.True(op2.IsCanceled); + } } } diff --git a/src/UnityFx.Async.Tests/Tests/ThenTests.cs b/src/UnityFx.Async.Tests/Tests/ThenTests.cs index 901bcd0..08670b6 100644 --- a/src/UnityFx.Async.Tests/Tests/ThenTests.cs +++ b/src/UnityFx.Async.Tests/Tests/ThenTests.cs @@ -63,5 +63,29 @@ public async Task Then_DelegateIsNotCalledOnCancel() // Assert Assert.False(called); } + + [Fact] + public async Task Then_CompletesWhenCanceled() + { + // Arrange + var cs = new CancellationTokenSource(); + cs.Cancel(); + + var op2 = new AsyncCompletionSource(); + var op = AsyncResult.CompletedOperation.Then(() => op2).WithCancellation(cs.Token); + + // Act + try + { + await op; + } + catch + { + } + + // Assert + Assert.True(op.IsCanceled); + Assert.True(op2.IsCanceled); + } } } diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs index ca472a9..dbb3601 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs @@ -52,7 +52,11 @@ protected override void InvokeSuccessCallback(IAsyncOperation op, bool completed _op2.AddCompletionCallback( op2 => { - if (op2.IsCompletedSuccessfully) + if (IsCancellationRequested) + { + TrySetCanceled(false); + } + else if (op2.IsCompletedSuccessfully) { var op3 = (op2 as IAsyncOperation).Result; From f012f290e2630eb44658634038ebe7c77e2db7a0 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 18 Apr 2018 16:31:10 +0300 Subject: [PATCH 094/128] Cancellation-related changes --- src/UnityFx.Async/Api/Core/AsyncResult.cs | 23 +++++- .../Extensions/AsyncExtensions.Promises.cs | 80 +++++-------------- .../Api/Extensions/AsyncExtensions.cs | 46 +++++++++++ .../Api/Interfaces/IAsyncCancellable.cs | 3 +- 4 files changed, 89 insertions(+), 63 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 9dfe480..3d174c3 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -64,6 +64,7 @@ public class AsyncResult : IAsyncOperation, IAsyncCancellable, IEnumerator private static readonly object _continuationCompletionSentinel = new object(); private static AsyncResult _completedOperation; + private static AsyncResult _canceledOperation; private readonly object _asyncState; @@ -657,6 +658,26 @@ public static AsyncResult CompletedOperation } } + /// + /// Gets an operation that's already been canceled. + /// + /// + /// Note that call have no effect on operations returned with the property. May not always return the same instance. + /// + /// Completed instance. + public static AsyncResult CanceledOperation + { + get + { + if (_canceledOperation == null) + { + _canceledOperation = new AsyncResult(_flagDoNotDispose | _flagCompletedSynchronously | StatusCanceled); + } + + return _canceledOperation; + } + } + #region From* /// @@ -1883,8 +1904,6 @@ public WaitHandle AsyncWaitHandle /// public void Cancel() { - ThrowIfDisposed(); - if (TrySetFlag(_flagCancellationRequested)) { OnCancel(); diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs index 0efe87b..41ad68a 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs @@ -12,7 +12,7 @@ partial class AsyncExtensions #region Then /// - /// Schedules a callback to be executed after the operation has been resolved. + /// Schedules a callback to be executed after the promise has been resolved. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. @@ -30,7 +30,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba } /// - /// Schedules a callback to be executed after the operation has been resolved. + /// Schedules a callback to be executed after the promise has been resolved. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. @@ -48,7 +48,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Ac } /// - /// Schedules a callback to be executed after the operation has been resolved. + /// Schedules a callback to be executed after the promise has been resolved. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. @@ -67,7 +67,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func - /// Schedules a callback to be executed after the operation has been resolved. + /// Schedules a callback to be executed after the promise has been resolved. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. @@ -86,7 +86,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Fu } /// - /// Schedules a callback to be executed after the operation has been resolved. + /// Schedules a callback to be executed after the promise has been resolved. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. @@ -105,7 +105,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Fu } /// - /// Schedules a callback to be executed after the operation has been resolved. + /// Schedules a callback to be executed after the promise has been resolved. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. @@ -173,7 +173,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Ac } /// - /// Adds a completion callback to be executed after the operation has been resolved. + /// Schedules a callbacks to be executed after the operation has been resolved. /// /// An operation to be continued. /// The callback to be executed when the operation has succeeded. @@ -197,7 +197,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Func - /// Adds a completion callback to be executed after the operation has been resolved. + /// Schedules a callbacks to be executed after the operation has been resolved. /// /// An operation to be continued. /// The callback to be executed when the operation has succeeded. @@ -225,7 +225,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Fu #region ThenAll /// - /// Schedules a callback to be executed after the operation has been resolved. The resulting operation will complete after all of the operations in the callback return value have completed. + /// Schedules a callback to be executed after the promise has been resolved. The resulting operation will complete after all of the operations in the callback return value have completed. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. @@ -242,7 +242,7 @@ public static IAsyncOperation ThenAll(this IAsyncOperation op, Func - /// Schedules a callback to be executed after the operation has been resolved. The resulting operation will complete after all of the operations in the callback return value have completed. + /// Schedules a callback to be executed after the promise has been resolved. The resulting operation will complete after all of the operations in the callback return value have completed. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. @@ -259,7 +259,7 @@ public static IAsyncOperation ThenAll(this IAsyncOperation op, Func - /// Schedules a callback to be executed after the operation has been resolved. The resulting operation will complete after all of the operations in the callback return value have completed. + /// Schedules a callback to be executed after the promise has been resolved. The resulting operation will complete after all of the operations in the callback return value have completed. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. @@ -276,7 +276,7 @@ public static IAsyncOperation ThenAll(this IAsyncOperation op, Func - /// Schedules a callback to be executed after the operation has been resolved. The resulting operation will complete after all of the specified objects in an array have completed. + /// Schedules a callback to be executed after the promise has been resolved. The resulting operation will complete after all of the specified objects in an array have completed. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. @@ -297,7 +297,7 @@ public static IAsyncOperation ThenAll(this IAsyncOperation op, Fun #region ThenAny /// - /// Schedules a callback to be executed after the operation has been resolved. The resulting operation will complete after any of the operations in the callback return value have completed. + /// Schedules a callback to be executed after the promise has been resolved. The resulting operation will complete after any of the operations in the callback return value have completed. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. @@ -314,7 +314,7 @@ public static IAsyncOperation ThenAny(this IAsyncOperation op, Func - /// Schedules a callback to be executed after the operation has been resolved. The resulting operation will complete after any of the operations in the callback return value have completed. + /// Schedules a callback to be executed after the promise has been resolved. The resulting operation will complete after any of the operations in the callback return value have completed. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. @@ -331,7 +331,7 @@ public static IAsyncOperation ThenAny(this IAsyncOperation op, } /// - /// Schedules a callback to be executed after the operation has been resolved. The resulting operation will complete after any of the operations in the callback return value have completed. + /// Schedules a callback to be executed after the promise has been resolved. The resulting operation will complete after any of the operations in the callback return value have completed. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. @@ -348,7 +348,7 @@ public static IAsyncOperation ThenAny(this IAsyncOperation op, } /// - /// Schedules a callback to be executed after the operation has been resolved. The resulting operation will complete after any of the operations in the callback return value have completed. + /// Schedules a callback to be executed after the promise has been resolved. The resulting operation will complete after any of the operations in the callback return value have completed. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. @@ -369,7 +369,7 @@ public static IAsyncOperation ThenAny(this IAsyncOperation< #region Rebind /// - /// Schedules a callback to be executed after the operation has been resolved. + /// Schedules a callback to be executed after the promise has been resolved. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. @@ -386,7 +386,7 @@ public static IAsyncOperation Rebind(this IAsyncOperation op, } /// - /// Schedules a callback to be executed after the operation has been resolved. + /// Schedules a callback to be executed after the promise has been resolved. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. @@ -407,7 +407,7 @@ public static IAsyncOperation Rebind(this IAsyn #region Catch /// - /// Schedules a callback to be executed after the operation has been rejected. + /// Schedules a callback to be executed after the promise has been rejected. /// /// An operation to be continued. /// The callback to be executed when the operation has faulted/was canceled. @@ -425,7 +425,7 @@ public static IAsyncOperation Catch(this IAsyncOperation op, Action e } /// - /// Schedules a callback to be executed after the operation has been rejected. + /// Schedules a callback to be executed after the promise has been rejected. /// /// An operation to be continued. /// The callback to be executed when the operation has faulted/was canceled. @@ -444,48 +444,10 @@ public static IAsyncOperation Catch(this IAsyncOperation op, Action< #endregion - #region WithCancellation - -#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 oepration. - /// Returns the source operation. - public static IAsyncOperation WithCancellation(this IAsyncOperation op, CancellationToken cancellationToken) - { - if (cancellationToken.CanBeCanceled && !op.IsCompleted) - { - if (op is IAsyncCancellable c) - { - if (cancellationToken.IsCancellationRequested) - { - c.Cancel(); - } - else - { - cancellationToken.Register(c.Cancel); - } - } - else - { - throw new NotSupportedException(); - } - } - - return op; - } - -#endif - - #endregion - #region Finally /// - /// Schedules a callback to be executed after the operation has completed. + /// Schedules a callback to be executed after the promise has completed. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs index 98c3f05..f0ea3bb 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs @@ -14,6 +14,12 @@ namespace UnityFx.Async [EditorBrowsable(EditorBrowsableState.Advanced)] public static partial class AsyncExtensions { + #region data + + private static Action _cancelHandler; + + #endregion + #region Common /// @@ -104,6 +110,46 @@ internal static void ThrowIfNonSuccess(IAsyncOperation op, bool throwAggregate) } } +#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 (op is IAsyncCancellable c) + { + if (cancellationToken.IsCancellationRequested) + { + c.Cancel(); + } + else + { + if (_cancelHandler == null) + { + _cancelHandler = args => (args as IAsyncCancellable).Cancel(); + } + + cancellationToken.Register(_cancelHandler, op, false); + } + } + else + { + throw new NotSupportedException(); + } + } + + return op; + } + +#endif + #endregion #region IAsyncCompletionSource diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncCancellable.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncCancellable.cs index 8526219..28674ef 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncCancellable.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncCancellable.cs @@ -12,9 +12,8 @@ namespace UnityFx.Async public interface IAsyncCancellable { /// - /// Attempts to cancel the operation. + /// Attempts to cancel the operation. When this method returns the operation can still be uncompleted. /// - /// Thrown is the operation is disposed. void Cancel(); } } From 21ae62be30f14fbd8b2b9b8a416d66a72373adc1 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 18 Apr 2018 17:08:29 +0300 Subject: [PATCH 095/128] README update --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index c104a39..4b30296 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,9 @@ This does exaclty the same job as the callbacks sample, but it's much more reada That said promises are still not an ideal solution (at least for C#). They require quite a lot of filler code and rely heavily on delegate usage. +### Observables and reactive programming +Observable event streams as defined in [reactive programming](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754) provide a convenient way of managing push-based event notifications (opposed to pull-based nature of `IEnumerable`). One of the core differences is multiple resulting values for observables versus single promise result. While observables may represent an asynchronous operation it is not always the case (and it is generally not recommended to use them in this way). That is why the concept is ot of the scope covered by this document. + ### Asynchronous programming with async and await C# 5.0/.NET 4.5 introduced a new appoach to asynchronous programming. By using `async` and `await` one can write asynchronous methods almost as synchronous methods. The following example shows implementation of the callback hell method with this technique: ```csharp @@ -301,6 +304,15 @@ finally } ``` +### Cancellation +All library operatinos can be cancelled using `AsyncResult.Cancel` method or with `WithCancellation` extension: +```csharp +DownloadTextAsync("http://www.google.com") + .Then(text => ExtractFirstParagraph(text)) + .WithCancellation(cancellationToken); +``` +If the [token](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken) passed to `WithCancellation` the target operation is cancelled (and that means cancelling all of the chain operations as well) as soon as possible. The cancellation might not be instant (depends on specific operation implementation). Also please note that not all operations might support cancellation. + ### Synchronization context capturing The default behaviour of all library methods is to capture current [SynchronizationContext](https://docs.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext) and try to schedule continuations on it. If there is no synchronization context attached to current thread, continuations are executed on a thread that initiated an operation completion. The same behaviour applies to `async` / `await` implementation unless explicitly overriden with `ConfigureAwait`: ```csharp @@ -439,6 +451,7 @@ Please see the links below for extended information on the product: - [Task-based Asynchronous Pattern (TAP)](https://docs.microsoft.com/en-us/dotnet/standard/asynchronous-programming-patterns/task-based-asynchronous-pattern-tap). - [Asynchronous programming with async and await (C#)](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/). - [.NET Task reference source](https://referencesource.microsoft.com/#mscorlib/System/threading/Tasks/Task.cs). +- [Introduction to Reactive Programming](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754). - [Promise pattern](https://en.wikipedia.org/wiki/Futures_and_promises). - [Promises for Game Development](http://www.what-could-possibly-go-wrong.com/promises-for-game-development/). - [Promises/A+ Spec](https://promisesaplus.com/). From 5321cd6ea7cfad6ff624beb3ee01282b4f09172e Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 18 Apr 2018 17:12:25 +0300 Subject: [PATCH 096/128] README typo fixes --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4b30296..f780be5 100644 --- a/README.md +++ b/README.md @@ -134,7 +134,7 @@ This does exaclty the same job as the callbacks sample, but it's much more reada That said promises are still not an ideal solution (at least for C#). They require quite a lot of filler code and rely heavily on delegate usage. ### Observables and reactive programming -Observable event streams as defined in [reactive programming](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754) provide a convenient way of managing push-based event notifications (opposed to pull-based nature of `IEnumerable`). One of the core differences is multiple resulting values for observables versus single promise result. While observables may represent an asynchronous operation it is not always the case (and it is generally not recommended to use them in this way). That is why the concept is ot of the scope covered by this document. +Observable event streams as defined in [reactive programming](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754) provide a convenient way of managing push-based event notifications (opposed to pull-based nature of `IEnumerable`). One of the core differences is multiple result values for observables versus single promise result. While observables may represent an asynchronous operation it is not always the case (and it is generally not recommended to use them in this way). That is why the concept is ot of the scope covered by this document. ### Asynchronous programming with async and await C# 5.0/.NET 4.5 introduced a new appoach to asynchronous programming. By using `async` and `await` one can write asynchronous methods almost as synchronous methods. The following example shows implementation of the callback hell method with this technique: @@ -305,13 +305,13 @@ finally ``` ### Cancellation -All library operatinos can be cancelled using `AsyncResult.Cancel` method or with `WithCancellation` extension: +All library operations can be cancelled using `AsyncResult.Cancel` method or with `WithCancellation` extension: ```csharp DownloadTextAsync("http://www.google.com") .Then(text => ExtractFirstParagraph(text)) .WithCancellation(cancellationToken); ``` -If the [token](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken) passed to `WithCancellation` the target operation is cancelled (and that means cancelling all of the chain operations as well) as soon as possible. The cancellation might not be instant (depends on specific operation implementation). Also please note that not all operations might support cancellation. +If the [token](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken) passed to `WithCancellation` is cancelled the target operation is cancelled as well (and that means cancelling all of the chain operations) as soon as possible. The cancellation might not be instant (depends on specific operation implementation). Also please note that not all operations might support cancellation. ### Synchronization context capturing The default behaviour of all library methods is to capture current [SynchronizationContext](https://docs.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext) and try to schedule continuations on it. If there is no synchronization context attached to current thread, continuations are executed on a thread that initiated an operation completion. The same behaviour applies to `async` / `await` implementation unless explicitly overriden with `ConfigureAwait`: From 4eb0d2af7649f7a97f254f1160d8044d4f07337f Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 18 Apr 2018 20:54:19 +0300 Subject: [PATCH 097/128] Added IProgress interface for net35 --- src/UnityFx.Async/Api/Net35/IProgress{T}.cs | 24 +++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/UnityFx.Async/Api/Net35/IProgress{T}.cs diff --git a/src/UnityFx.Async/Api/Net35/IProgress{T}.cs b/src/UnityFx.Async/Api/Net35/IProgress{T}.cs new file mode 100644 index 0000000..433a561 --- /dev/null +++ b/src/UnityFx.Async/Api/Net35/IProgress{T}.cs @@ -0,0 +1,24 @@ +// 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 +{ +#if NET35 + + /// + /// Defines a provider for progress updates. + /// + /// The type of progress update value. + public interface IProgress + { + /// + /// Reports a progress update. + /// + /// The value of the updated progress. + void Report(T value); + } + +#endif +} From f8c9ad86c5cdfbcde6a9228b7bf780aa18d286cc Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 18 Apr 2018 21:43:18 +0300 Subject: [PATCH 098/128] Added updatable implementations for Retry --- src/UnityFx.Async/Api/Core/AsyncResult.cs | 275 +++++++++++++++++- .../Specialized/RetryResult{T}.cs | 97 +++--- .../Specialized/TimerDelayResult.cs | 2 +- .../Specialized/TimerRetryResult{T}.cs | 63 ++++ .../Specialized/UpdatableDelayResult.cs | 2 +- .../Specialized/UpdatableRetryResult{T}.cs | 60 ++++ 6 files changed, 428 insertions(+), 71 deletions(-) create mode 100644 src/UnityFx.Async/Implementation/Specialized/TimerRetryResult{T}.cs create mode 100644 src/UnityFx.Async/Implementation/Specialized/UpdatableRetryResult{T}.cs diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 3d174c3..a7413d4 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -1004,10 +1004,13 @@ public static AsyncResult Delay(int millisecondsDelay) if (millisecondsDelay == Timeout.Infinite) { - return new AsyncResult(); + // NOTE: Cannot return AsyncResult instance because its Cancel implementation throws NotSupportedException. + return new AsyncCompletionSource(AsyncOperationStatus.Running); } - return new TimerDelayResult(millisecondsDelay); + var result = new TimerDelayResult(millisecondsDelay); + result.Start(); + return result; } /// @@ -1022,6 +1025,11 @@ public static AsyncResult Delay(int millisecondsDelay) /// public static AsyncResult Delay(int millisecondsDelay, IAsyncUpdateSource updateSource) { + if (updateSource == null) + { + throw new ArgumentNullException(nameof(updateSource)); + } + if (millisecondsDelay < Timeout.Infinite) { throw new ArgumentOutOfRangeException(nameof(millisecondsDelay), millisecondsDelay, Constants.ErrorValueIsLessThanZero); @@ -1034,10 +1042,13 @@ public static AsyncResult Delay(int millisecondsDelay, IAsyncUpdateSource update if (millisecondsDelay == Timeout.Infinite) { - return new AsyncResult(); + // NOTE: Cannot return AsyncResult instance because its Cancel implementation throws NotSupportedException. + return new AsyncCompletionSource(AsyncOperationStatus.Running); } - return new UpdatableDelayResult(millisecondsDelay, updateSource); + var result = new UpdatableDelayResult(millisecondsDelay, updateSource); + result.Start(); + return result; } /// @@ -1086,6 +1097,35 @@ public static AsyncResult Delay(TimeSpan delay, IAsyncUpdateSource updateSource) #region Retry + /// + /// Creates an operation that completes when the source operation is completed successfully. + /// + /// A delegate that initiates the source operation. + /// The number of milliseconds to wait after a failed try before starting a new operation. + /// Thrown if the is . + /// Thrown if is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func opFactory, int millisecondsRetryDelay) + { + return Retry(opFactory, millisecondsRetryDelay, 0); + } + + /// + /// Creates an operation that completes when the source operation is completed successfully. + /// + /// A delegate that initiates the source operation. + /// The number of milliseconds to wait after a failed try before starting a new operation. + /// Update notifications provider. + /// Thrown if the is . + /// Thrown if is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func opFactory, int millisecondsRetryDelay, IAsyncUpdateSource updateSource) + { + return Retry(opFactory, millisecondsRetryDelay, 0, updateSource); + } + /// /// Creates an operation that completes when the source operation is completed successfully or maximum number of retries exceeded. /// @@ -1096,7 +1136,7 @@ public static AsyncResult Delay(TimeSpan delay, IAsyncUpdateSource updateSource) /// Thrown if or is less than zero. /// An operation that represents the retry process. /// - public static AsyncResult Retry(Func opFactory, int millisecondsRetryDelay, int maxRetryCount = 0) + public static AsyncResult Retry(Func opFactory, int millisecondsRetryDelay, int maxRetryCount) { if (opFactory == null) { @@ -1113,7 +1153,76 @@ public static AsyncResult Retry(Func opFactory, int millisecond throw new ArgumentOutOfRangeException(nameof(maxRetryCount), maxRetryCount, Constants.ErrorValueIsLessThanZero); } - return new RetryResult(opFactory, millisecondsRetryDelay, maxRetryCount); + var result = new TimerRetryResult(opFactory, millisecondsRetryDelay, maxRetryCount); + result.Start(); + return result; + } + + /// + /// Creates an operation that completes when the source operation is completed successfully or maximum number of retries exceeded. + /// + /// A delegate that initiates the source operation. + /// The number of milliseconds to wait after a failed try before starting a new operation. + /// Maximum number of retries. Zero means no limits. + /// Update notifications provider. + /// Thrown if the or is . + /// Thrown if or is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func opFactory, int millisecondsRetryDelay, int maxRetryCount, IAsyncUpdateSource updateSource) + { + if (opFactory == null) + { + throw new ArgumentNullException(nameof(opFactory)); + } + + if (updateSource == null) + { + throw new ArgumentNullException(nameof(updateSource)); + } + + if (millisecondsRetryDelay < 0) + { + throw new ArgumentOutOfRangeException(nameof(millisecondsRetryDelay), millisecondsRetryDelay, Constants.ErrorValueIsLessThanZero); + } + + if (maxRetryCount < 0) + { + throw new ArgumentOutOfRangeException(nameof(maxRetryCount), maxRetryCount, Constants.ErrorValueIsLessThanZero); + } + + var result = new UpdatableRetryResult(opFactory, millisecondsRetryDelay, maxRetryCount, updateSource); + result.Start(); + return result; + } + + /// + /// Creates an operation that completes when the source operation is completed successfully. + /// + /// A delegate that initiates the source operation. + /// The time to wait after a failed try before starting a new operation. + /// Thrown if the is . + /// Thrown if is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func opFactory, TimeSpan retryDelay) + { + return Retry(opFactory, retryDelay, 0); + } + + /// + /// Creates an operation that completes when the source operation is completed successfully. + /// + /// A delegate that initiates the source operation. + /// The time to wait after a failed try before starting a new operation. + /// Update notifications provider. + /// Thrown if the or is . + /// Thrown if is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func opFactory, TimeSpan retryDelay, IAsyncUpdateSource updateSource) + { + return Retry(opFactory, retryDelay, 0, updateSource); } /// @@ -1126,7 +1235,7 @@ public static AsyncResult Retry(Func opFactory, int millisecond /// Thrown if or is less than zero. /// An operation that represents the retry process. /// - public static AsyncResult Retry(Func opFactory, TimeSpan retryDelay, int maxRetryCount = 0) + public static AsyncResult Retry(Func opFactory, TimeSpan retryDelay, int maxRetryCount) { var millisecondsDelay = (long)retryDelay.TotalMilliseconds; @@ -1138,6 +1247,58 @@ public static AsyncResult Retry(Func opFactory, TimeSpan retryD return Retry(opFactory, (int)millisecondsDelay, maxRetryCount); } + /// + /// Creates an operation that completes when the source operation is completed successfully or maximum number of retries exceeded. + /// + /// A delegate that initiates the source operation. + /// The time to wait after a failed try before starting a new operation. + /// Maximum number of retries. Zero means no limits. + /// Update notifications provider. + /// Thrown if the or is . + /// Thrown if or is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func opFactory, TimeSpan retryDelay, int maxRetryCount, IAsyncUpdateSource updateSource) + { + var millisecondsDelay = (long)retryDelay.TotalMilliseconds; + + if (millisecondsDelay > int.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(retryDelay)); + } + + return Retry(opFactory, (int)millisecondsDelay, maxRetryCount, updateSource); + } + + /// + /// Creates an operation that completes when the source operation is completed successfully. + /// + /// A delegate that initiates the source operation. + /// The number of milliseconds to wait after a failed try before starting a new operation. + /// Thrown if the is . + /// Thrown if is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func> opFactory, int millisecondsRetryDelay) + { + return Retry(opFactory, millisecondsRetryDelay, 0); + } + + /// + /// Creates an operation that completes when the source operation is completed successfully. + /// + /// A delegate that initiates the source operation. + /// The number of milliseconds to wait after a failed try before starting a new operation. + /// Update notifications provider. + /// Thrown if the or is . + /// Thrown if is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func> opFactory, int millisecondsRetryDelay, IAsyncUpdateSource updateSource) + { + return Retry(opFactory, millisecondsRetryDelay, 0, updateSource); + } + /// /// Creates an operation that completes when the source operation is completed successfully or maximum number of retries exceeded. /// @@ -1148,7 +1309,7 @@ public static AsyncResult Retry(Func opFactory, TimeSpan retryD /// Thrown if or is less than zero. /// An operation that represents the retry process. /// - public static AsyncResult Retry(Func> opFactory, int millisecondsRetryDelay, int maxRetryCount = 0) + public static AsyncResult Retry(Func> opFactory, int millisecondsRetryDelay, int maxRetryCount) { if (opFactory == null) { @@ -1165,7 +1326,76 @@ public static AsyncResult Retry(Func> throw new ArgumentOutOfRangeException(nameof(maxRetryCount), maxRetryCount, Constants.ErrorValueIsLessThanZero); } - return new RetryResult(opFactory, millisecondsRetryDelay, maxRetryCount); + var result = new TimerRetryResult(opFactory, millisecondsRetryDelay, maxRetryCount); + result.Start(); + return result; + } + + /// + /// Creates an operation that completes when the source operation is completed successfully or maximum number of retries exceeded. + /// + /// A delegate that initiates the source operation. + /// The number of milliseconds to wait after a failed try before starting a new operation. + /// Maximum number of retries. Zero means no limits. + /// Update notifications provider. + /// Thrown if the or is . + /// Thrown if or is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func> opFactory, int millisecondsRetryDelay, int maxRetryCount, IAsyncUpdateSource updateSource) + { + if (opFactory == null) + { + throw new ArgumentNullException(nameof(opFactory)); + } + + if (updateSource == null) + { + throw new ArgumentNullException(nameof(updateSource)); + } + + if (millisecondsRetryDelay < 0) + { + throw new ArgumentOutOfRangeException(nameof(millisecondsRetryDelay), millisecondsRetryDelay, Constants.ErrorValueIsLessThanZero); + } + + if (maxRetryCount < 0) + { + throw new ArgumentOutOfRangeException(nameof(maxRetryCount), maxRetryCount, Constants.ErrorValueIsLessThanZero); + } + + var result = new UpdatableRetryResult(opFactory, millisecondsRetryDelay, maxRetryCount, updateSource); + result.Start(); + return result; + } + + /// + /// Creates an operation that completes when the source operation is completed successfully. + /// + /// A delegate that initiates the source operation. + /// The time to wait after a failed try before starting a new operation. + /// Thrown if the is . + /// Thrown if is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func> opFactory, TimeSpan retryDelay) + { + return Retry(opFactory, retryDelay, 0); + } + + /// + /// Creates an operation that completes when the source operation is completed successfully. + /// + /// A delegate that initiates the source operation. + /// The time to wait after a failed try before starting a new operation. + /// Update notifications provider. + /// Thrown if the or is . + /// Thrown if is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func> opFactory, TimeSpan retryDelay, IAsyncUpdateSource updateSource) + { + return Retry(opFactory, retryDelay, 0, updateSource); } /// @@ -1178,7 +1408,7 @@ public static AsyncResult Retry(Func> /// Thrown if or is less than zero. /// An operation that represents the retry process. /// - public static AsyncResult Retry(Func> opFactory, TimeSpan retryDelay, int maxRetryCount = 0) + public static AsyncResult Retry(Func> opFactory, TimeSpan retryDelay, int maxRetryCount) { var millisecondsDelay = (long)retryDelay.TotalMilliseconds; @@ -1190,6 +1420,29 @@ public static AsyncResult Retry(Func> return Retry(opFactory, (int)millisecondsDelay, maxRetryCount); } + /// + /// Creates an operation that completes when the source operation is completed successfully or maximum number of retries exceeded. + /// + /// A delegate that initiates the source operation. + /// The time to wait after a failed try before starting a new operation. + /// Maximum number of retries. Zero means no limits. + /// Update notifications provider. + /// Thrown if the or is . + /// Thrown if or is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func> opFactory, TimeSpan retryDelay, int maxRetryCount, IAsyncUpdateSource updateSource) + { + var millisecondsDelay = (long)retryDelay.TotalMilliseconds; + + if (millisecondsDelay > int.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(retryDelay)); + } + + return Retry(opFactory, (int)millisecondsDelay, maxRetryCount, updateSource); + } + #endregion #region WhenAll @@ -1904,6 +2157,8 @@ public WaitHandle AsyncWaitHandle /// public void Cancel() { + ThrowIfDisposed(); + if (TrySetFlag(_flagCancellationRequested)) { OnCancel(); diff --git a/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs index 6cc8e8b..0d0664d 100644 --- a/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs @@ -7,7 +7,7 @@ namespace UnityFx.Async { - internal class RetryResult : AsyncResult, IAsyncContinuation + internal abstract class RetryResult : AsyncResult, IAsyncContinuation { #region data @@ -15,8 +15,6 @@ internal class RetryResult : AsyncResult, IAsyncContinuation private readonly int _millisecondsRetryDelay; private readonly int _maxRetryCount; - private Timer _timer; - private TimerCallback _timerCallback; private IAsyncOperation _op; private int _numberOfRetriesLeft; @@ -24,37 +22,45 @@ internal class RetryResult : AsyncResult, IAsyncContinuation #region interface - internal RetryResult(object opFactory, int millisecondsRetryDelay, int maxRetryCount) - : base(AsyncOperationStatus.Running) + protected RetryResult(object opFactory, int millisecondsRetryDelay, int maxRetryCount) { _opFactory = opFactory; _millisecondsRetryDelay = millisecondsRetryDelay; _maxRetryCount = maxRetryCount; _numberOfRetriesLeft = maxRetryCount; + } - StartOperation(true); + protected void EndWait() + { + if (!IsCompleted) + { + if (IsCancellationRequested) + { + TrySetCanceled(false); + } + else + { + Retry(); + } + } } + protected abstract void BeginWait(int millisecondsDelay); + #endregion #region AsyncResult - protected override void OnCancel() + protected override void OnStarted() { - if (_op is IAsyncCancellable c) - { - c.Cancel(); - } + StartOperation(); } - protected override void OnCompleted() + protected override void OnCancel() { - base.OnCompleted(); - - if (_timer != null) + if (_op is IAsyncCancellable c) { - _timer.Dispose(); - _timer = null; + c.Cancel(); } } @@ -71,7 +77,7 @@ public void Invoke(IAsyncOperation op) { if (_op.IsCompletedSuccessfully) { - SetResult(false); + SetResult(); } else if (IsCancellationRequested) { @@ -79,23 +85,11 @@ public void Invoke(IAsyncOperation op) } else if (_millisecondsRetryDelay > 0) { - if (_timerCallback == null) - { - _timerCallback = OnTimer; - } - - if (_timer == null) - { - _timer = new Timer(_timerCallback, null, _millisecondsRetryDelay, Timeout.Infinite); - } - else - { - _timer.Change(_millisecondsRetryDelay, Timeout.Infinite); - } + BeginWait(_millisecondsRetryDelay); } else { - Retry(false); + Retry(); } } } @@ -104,7 +98,7 @@ public void Invoke(IAsyncOperation op) #region implementation - private void StartOperation(bool calledFromConstructor) + private void StartOperation() { try { @@ -125,71 +119,56 @@ private void StartOperation(bool calledFromConstructor) { if (_op.IsCompletedSuccessfully) { - SetResult(calledFromConstructor); + SetResult(); } else { - Retry(calledFromConstructor); + Retry(); } } } catch (Exception e) { - TrySetException(e, calledFromConstructor); - } - } - - private void OnTimer(object args) - { - if (!IsCompleted) - { - if (IsCancellationRequested) - { - TrySetCanceled(false); - } - else - { - Retry(false); - } + TrySetException(e, false); } } - private void Retry(bool calledFromConstructor) + private void Retry() { Debug.Assert(_op != null); Debug.Assert(!_op.IsCompletedSuccessfully); if (_maxRetryCount == 0 || --_numberOfRetriesLeft > 0) { - StartOperation(calledFromConstructor); + StartOperation(); } else if (_op.IsFaulted) { - TrySetException(_op.Exception, calledFromConstructor); + TrySetException(_op.Exception, false); } else if (_op.IsCanceled) { - TrySetCanceled(calledFromConstructor); + TrySetCanceled(false); } else { // NOTE: should not get here. - TrySetException(new Exception("Maximum number of retries exceeded."), calledFromConstructor); + TrySetException(new Exception("Maximum number of retries exceeded."), false); } } - private void SetResult(bool completedSynchronously) + private void SetResult() { Debug.Assert(_op != null); Debug.Assert(_op.IsCompletedSuccessfully); if (_op is IAsyncOperation rop) { - TrySetResult(rop.Result, completedSynchronously); + TrySetResult(rop.Result, false); } else { - TrySetCompleted(completedSynchronously); + TrySetCompleted(false); } } diff --git a/src/UnityFx.Async/Implementation/Specialized/TimerDelayResult.cs b/src/UnityFx.Async/Implementation/Specialized/TimerDelayResult.cs index 176bafd..07c34ee 100644 --- a/src/UnityFx.Async/Implementation/Specialized/TimerDelayResult.cs +++ b/src/UnityFx.Async/Implementation/Specialized/TimerDelayResult.cs @@ -6,7 +6,7 @@ namespace UnityFx.Async { - internal class TimerDelayResult : AsyncResult + internal sealed class TimerDelayResult : AsyncResult { #region data diff --git a/src/UnityFx.Async/Implementation/Specialized/TimerRetryResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/TimerRetryResult{T}.cs new file mode 100644 index 0000000..2eb4b70 --- /dev/null +++ b/src/UnityFx.Async/Implementation/Specialized/TimerRetryResult{T}.cs @@ -0,0 +1,63 @@ +// 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 +{ + internal sealed class TimerRetryResult : RetryResult + { + #region data + + private Timer _timer; + private TimerCallback _timerCallback; + + #endregion + + #region interface + + internal TimerRetryResult(object opFactory, int millisecondsRetryDelay, int maxRetryCount) + : base(opFactory, millisecondsRetryDelay, maxRetryCount) + { + } + + #endregion + + #region RetryResult + + protected override void BeginWait(int millisecondsDelay) + { + if (_timerCallback == null) + { + _timerCallback = args => (args as TimerRetryResult).EndWait(); + } + + if (_timer == null) + { + _timer = new Timer(_timerCallback, this, millisecondsDelay, Timeout.Infinite); + } + else + { + _timer.Change(millisecondsDelay, Timeout.Infinite); + } + } + + #endregion + + #region AsyncResult + + protected override void OnCompleted() + { + base.OnCompleted(); + + if (_timer != null) + { + _timer.Dispose(); + _timer = null; + } + } + + #endregion + } +} diff --git a/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs b/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs index 1b647e9..a8af6f0 100644 --- a/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs +++ b/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs @@ -5,7 +5,7 @@ namespace UnityFx.Async { - internal class UpdatableDelayResult : AsyncResult, IAsyncUpdatable + internal sealed class UpdatableDelayResult : AsyncResult, IAsyncUpdatable { #region data diff --git a/src/UnityFx.Async/Implementation/Specialized/UpdatableRetryResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/UpdatableRetryResult{T}.cs new file mode 100644 index 0000000..ec692ab --- /dev/null +++ b/src/UnityFx.Async/Implementation/Specialized/UpdatableRetryResult{T}.cs @@ -0,0 +1,60 @@ +// 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 UpdatableRetryResult : RetryResult, IAsyncUpdatable + { + #region data + + private readonly IAsyncUpdateSource _updateService; + private float _timer; + + #endregion + + #region interface + + internal UpdatableRetryResult(object opFactory, int millisecondsRetryDelay, int maxRetryCount, IAsyncUpdateSource updateSource) + : base(opFactory, millisecondsRetryDelay, maxRetryCount) + { + _updateService = updateSource; + } + + protected override void BeginWait(int millisecondsDelay) + { + _timer = millisecondsDelay / 1000f; + _updateService.AddListener(this); + } + + #endregion + + #region AsyncResult + + protected override void OnCompleted() + { + _updateService.RemoveListener(this); + base.OnCompleted(); + } + + #endregion + + #region IAsyncUpdatable + + public void Update(float frameTime) + { + _timer -= frameTime; + + if (_timer <= 0) + { + _updateService.RemoveListener(this); + EndWait(); + } + } + + #endregion + } +} From 615810be11b992bf166afa7b333f823692d88df1 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 19 Apr 2018 11:44:35 +0300 Subject: [PATCH 099/128] Fixed initial status of the Delay operation --- src/UnityFx.Async/Implementation/Specialized/TimerDelayResult.cs | 1 - .../Implementation/Specialized/UpdatableDelayResult.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/src/UnityFx.Async/Implementation/Specialized/TimerDelayResult.cs b/src/UnityFx.Async/Implementation/Specialized/TimerDelayResult.cs index 07c34ee..314423c 100644 --- a/src/UnityFx.Async/Implementation/Specialized/TimerDelayResult.cs +++ b/src/UnityFx.Async/Implementation/Specialized/TimerDelayResult.cs @@ -17,7 +17,6 @@ internal sealed class TimerDelayResult : AsyncResult #region interface public TimerDelayResult(int millisecondsDelay) - : base(AsyncOperationStatus.Running) { _timer = new Timer( state => (state as AsyncResult).TrySetCompleted(false), diff --git a/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs b/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs index a8af6f0..38c0e83 100644 --- a/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs +++ b/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs @@ -17,7 +17,6 @@ internal sealed class UpdatableDelayResult : AsyncResult, IAsyncUpdatable #region interface public UpdatableDelayResult(int millisecondsDelay, IAsyncUpdateSource updateSource) - : base(AsyncOperationStatus.Running) { _timer = millisecondsDelay; _updateService = updateSource; From d368683d318a86f833d9ff5a4a3011d2b27ed38e Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 19 Apr 2018 11:45:00 +0300 Subject: [PATCH 100/128] Added cancellable overloads for Wait and SpinUntilCompleted extensions --- .../Api/Extensions/AsyncExtensions.Wait.cs | 155 ++++++++++++++++++ .../Api/Extensions/AsyncExtensions.cs | 54 ++++++ 2 files changed, 209 insertions(+) diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs index 63c0523..4bc4566 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs @@ -108,6 +108,161 @@ public static bool Wait(this IAsyncOperation op, TimeSpan timeout) return result; } +#if !NET35 + + /// + /// Waits for the to complete execution. 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 if the operation was canceled or faulted. + /// Thrown is the operation is disposed. + /// The cancellationToken was canceled. + /// + public static void Wait(this IAsyncOperation op, CancellationToken cancellationToken) + { +#if UNITYFX_NOT_THREAD_SAFE + + SpinUntilCompleted(op, cancellationToken); + +#else + + 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(); + } + } + +#endif + + ThrowIfNonSuccess(op, true); + } + + /// + /// Waits for the to complete execution within a specified number of milliseconds. 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. + /// Thrown if the operation was canceled or faulted. + /// Thrown is the operation is disposed. + /// + public static bool Wait(this IAsyncOperation op, int millisecondsTimeout, CancellationToken cancellationToken) + { +#if UNITYFX_NOT_THREAD_SAFE + + var result = SpinUntilCompleted(op, millisecondsTimeout, cancellationToken); + +#else + + 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); + } + } + +#endif + + if (result) + { + ThrowIfNonSuccess(op, true); + } + + return result; + } + + /// + /// Waits for the to complete execution within a specified time interval. 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 . + /// Thrown if the operation was canceled or faulted. + /// Thrown is the operation is disposed. + /// + public static bool Wait(this IAsyncOperation op, TimeSpan timeout, CancellationToken cancellationToken) + { +#if UNITYFX_NOT_THREAD_SAFE + + var result = SpinUntilCompleted(op, timeout, cancellationToken); + +#else + + 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); + } + } + +#endif + + if (result) + { + ThrowIfNonSuccess(op, true); + } + + return result; + } + +#endif + #endregion #region Join diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs index f0ea3bb..a2d2829 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs @@ -112,6 +112,60 @@ internal static void ThrowIfNonSuccess(IAsyncOperation op, bool throwAggregate) #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 cancellationToken was canceled. + /// + public static void SpinUntilCompleted(this IAsyncResult op, CancellationToken cancellationToken) + { + var sw = new SpinWait(); + + if (cancellationToken.CanBeCanceled) + { + while (!op.IsCompleted) + { + cancellationToken.ThrowIfCancellationRequested(); + sw.SpinOnce(); + } + } + else + { + while (!op.IsCompleted) + { + 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. + /// Returns if the operation was completed within the specified time interfval; otherwise. + public static bool SpinUntilCompleted(this IAsyncResult op, int millisecondsTimeout, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + /// + /// 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 . + /// Returns if the operation was completed within the specified time interfval; otherwise. + public static bool SpinUntilCompleted(this IAsyncResult op, TimeSpan timeout, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + /// /// Registers a that can be used to cancel the specified operation. /// From 6f76fe506a6bb87b951e84952f0dc95f5d5510c1 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 19 Apr 2018 12:11:34 +0300 Subject: [PATCH 101/128] Splitted AsyncResult implementation into two files --- .../Api/Core/AsyncResult.Helpers.cs | 1057 +++++++++++++++++ src/UnityFx.Async/Api/Core/AsyncResult.cs | 1041 +--------------- 2 files changed, 1058 insertions(+), 1040 deletions(-) create mode 100644 src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs new file mode 100644 index 0000000..da25d63 --- /dev/null +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs @@ -0,0 +1,1057 @@ +// 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.Diagnostics; +using System.Threading; +#if UNITYFX_SUPPORT_TAP +using System.Threading.Tasks; +#endif + +namespace UnityFx.Async +{ + partial class AsyncResult + { + #region data + + private static AsyncResult _completedOperation; + private static AsyncResult _canceledOperation; + + #endregion + + #region interface + + /// + /// Gets an operation that's already been completed successfully. + /// + /// + /// Note that call have no effect on operations returned with the property. May not always return the same instance. + /// + /// Completed instance. + public static AsyncResult CompletedOperation + { + get + { + if (_completedOperation == null) + { + _completedOperation = new AsyncResult(_flagDoNotDispose | _flagCompletedSynchronously | StatusRanToCompletion); + } + + return _completedOperation; + } + } + + /// + /// Gets an operation that's already been canceled. + /// + /// + /// Note that call have no effect on operations returned with the property. May not always return the same instance. + /// + /// Completed instance. + public static AsyncResult CanceledOperation + { + get + { + if (_canceledOperation == null) + { + _canceledOperation = new AsyncResult(_flagDoNotDispose | _flagCompletedSynchronously | StatusCanceled); + } + + return _canceledOperation; + } + } + + #region From* + + /// + /// Creates a that is canceled. + /// + /// A canceled operation. + /// + /// + /// + /// + public static AsyncResult FromCanceled() + { + return new AsyncResult(AsyncOperationStatus.Canceled); + } + + /// + /// Creates a that is canceled. + /// + /// User-defined data returned by . + /// A canceled operation. + /// + /// + /// + /// + public static AsyncResult FromCanceled(object asyncState) + { + return new AsyncResult(AsyncOperationStatus.Canceled, asyncState); + } + + /// + /// Creates a that is canceled. + /// + /// A canceled operation. + /// + /// + /// + /// + public static AsyncResult FromCanceled() + { + return new AsyncResult(AsyncOperationStatus.Canceled); + } + + /// + /// Creates a that is canceled. + /// + /// User-defined data returned by . + /// A canceled operation. + /// + /// + /// + /// + public static AsyncResult FromCanceled(object asyncState) + { + return new AsyncResult(AsyncOperationStatus.Canceled, asyncState); + } + + /// + /// Creates a that has completed with a specified exception. + /// + /// The exception to complete the operation with. + /// A faulted operation. + /// + /// + /// + /// + public static AsyncResult FromException(Exception exception) + { + return new AsyncResult(exception, null); + } + + /// + /// Creates a that has completed with a specified exception. + /// + /// The exception to complete the operation with. + /// User-defined data returned by . + /// A faulted operation. + /// + /// + /// + /// + public static AsyncResult FromException(Exception exception, object asyncState) + { + return new AsyncResult(exception, asyncState); + } + + /// + /// Creates a that has completed with specified exceptions. + /// + /// Exceptions to complete the operation with. + /// A faulted operation. + /// + /// + /// + /// + public static AsyncResult FromExceptions(IEnumerable exceptions) + { + return new AsyncResult(exceptions, null); + } + + /// + /// Creates a that has completed with specified exceptions. + /// + /// Exceptions to complete the operation with. + /// User-defined data returned by . + /// A faulted operation. + /// + /// + /// + /// + public static AsyncResult FromExceptions(IEnumerable exceptions, object asyncState) + { + return new AsyncResult(exceptions, asyncState); + } + + /// + /// Creates a that has completed with a specified exception. + /// + /// The exception to complete the operation with. + /// A faulted operation. + /// + /// + /// + /// + public static AsyncResult FromException(Exception exception) + { + return new AsyncResult(exception, null); + } + + /// + /// Creates a that has completed with a specified exception. + /// + /// The exception to complete the operation with. + /// User-defined data returned by . + /// A faulted operation. + /// + /// + /// + /// + public static AsyncResult FromException(Exception exception, object asyncState) + { + return new AsyncResult(exception, asyncState); + } + + /// + /// Creates a that has completed with specified exceptions. + /// + /// Exceptions to complete the operation with. + /// A faulted operation. + /// + /// + /// + /// + public static AsyncResult FromExceptions(IEnumerable exceptions) + { + return new AsyncResult(exceptions, null); + } + + /// + /// Creates a that has completed with specified exceptions. + /// + /// Exceptions to complete the operation with. + /// User-defined data returned by . + /// A faulted operation. + /// + /// + /// + /// + public static AsyncResult FromExceptions(IEnumerable exceptions, object asyncState) + { + return new AsyncResult(exceptions, asyncState); + } + + /// + /// Creates a that has completed with a specified result. + /// + /// The result value with which to complete the operation. + /// A completed operation with the specified result value. + /// + /// + /// + /// + public static AsyncResult FromResult(T result) + { + return new AsyncResult(result, null); + } + + /// + /// Creates a that has completed with a specified result. + /// + /// The result value with which to complete the operation. + /// User-defined data returned by . + /// A completed operation with the specified result value. + /// + /// + /// + /// + public static AsyncResult FromResult(T result, object asyncState) + { + return new AsyncResult(result, asyncState); + } + +#if UNITYFX_SUPPORT_TAP + + /// + /// Creates an instance that completes when the specified completes. + /// + /// The source instance. + /// Thrown if the reference is . + /// An that represents the source . + /// + public static AsyncResult FromTask(Task task) + { + if (task == null) + { + throw new ArgumentNullException(nameof(task)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + task.ContinueWith( + t => + { + if (t.IsFaulted) + { + result.SetException(t.Exception); + } + else if (t.IsCanceled) + { + result.SetCanceled(); + } + else + { + result.SetCompleted(); + } + }, + TaskContinuationOptions.ExecuteSynchronously); + + return result; + } + + /// + /// Creates an instance that completes when the specified completes. + /// + /// The source instance. + /// Thrown if the reference is . + /// An that represents the source . + /// + public static AsyncResult FromTask(Task task) + { + if (task == null) + { + throw new ArgumentNullException(nameof(task)); + } + + var result = new AsyncCompletionSource(AsyncOperationStatus.Running); + + task.ContinueWith( + t => + { + if (t.IsFaulted) + { + result.SetException(t.Exception); + } + else if (t.IsCanceled) + { + result.SetCanceled(); + } + else + { + result.SetResult(t.Result); + } + }, + TaskContinuationOptions.ExecuteSynchronously); + + return result; + } + +#endif + +#if !NET35 + + /// + /// Creates a instance that can be used to track the source observable. + /// + /// Type of the operation result. + /// The source observable. + /// Thrown if the reference is . + /// Returns an instance that can be used to track the observable. + public static AsyncResult FromObservable(IObservable observable) + { + if (observable == null) + { + throw new ArgumentNullException(nameof(observable)); + } + + return new AsyncObservableResult(observable); + } + +#endif + + #endregion + + #region Delay + + /// + /// Creates an operation that completes after a time delay. + /// + /// The number of milliseconds to wait before completing the returned operation, or (-1) to wait indefinitely. + /// Thrown if the is less than -1. + /// An operation that represents the time delay. + /// + /// + public static AsyncResult Delay(int millisecondsDelay) + { + if (millisecondsDelay < Timeout.Infinite) + { + throw new ArgumentOutOfRangeException(nameof(millisecondsDelay), millisecondsDelay, Constants.ErrorValueIsLessThanZero); + } + + if (millisecondsDelay == 0) + { + return CompletedOperation; + } + + if (millisecondsDelay == Timeout.Infinite) + { + // NOTE: Cannot return AsyncResult instance because its Cancel implementation throws NotSupportedException. + return new AsyncCompletionSource(AsyncOperationStatus.Running); + } + + var result = new TimerDelayResult(millisecondsDelay); + result.Start(); + return result; + } + + /// + /// Creates an operation that completes after a time delay. This method creates a more effecient operation + /// than but requires a specialized updates source. + /// + /// The number of milliseconds to wait before completing the returned operation, or (-1) to wait indefinitely. + /// Update notifications provider. + /// Thrown if is . + /// Thrown if the is less than -1. + /// An operation that represents the time delay. + /// + public static AsyncResult Delay(int millisecondsDelay, IAsyncUpdateSource updateSource) + { + if (updateSource == null) + { + throw new ArgumentNullException(nameof(updateSource)); + } + + if (millisecondsDelay < Timeout.Infinite) + { + throw new ArgumentOutOfRangeException(nameof(millisecondsDelay), millisecondsDelay, Constants.ErrorValueIsLessThanZero); + } + + if (millisecondsDelay == 0) + { + return CompletedOperation; + } + + if (millisecondsDelay == Timeout.Infinite) + { + // NOTE: Cannot return AsyncResult instance because its Cancel implementation throws NotSupportedException. + return new AsyncCompletionSource(AsyncOperationStatus.Running); + } + + var result = new UpdatableDelayResult(millisecondsDelay, updateSource); + result.Start(); + return result; + } + + /// + /// Creates an operation that completes after a specified time interval. + /// + /// The time span to wait before completing the returned operation, or TimeSpan.FromMilliseconds(-1) to wait indefinitely. + /// Thrown if the represents a negative time interval other than TimeSpan.FromMillseconds(-1). + /// An operation that represents the time delay. + /// + /// + public static AsyncResult Delay(TimeSpan delay) + { + var millisecondsDelay = (long)delay.TotalMilliseconds; + + if (millisecondsDelay > int.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(delay)); + } + + return Delay((int)millisecondsDelay); + } + + /// + /// Creates an operation that completes after a specified time interval. This method creates a more effecient operation + /// than but requires a specialized updates source. + /// + /// The time span to wait before completing the returned operation, or TimeSpan.FromMilliseconds(-1) to wait indefinitely. + /// Update notifications provider. + /// Thrown if is . + /// Thrown if the represents a negative time interval other than TimeSpan.FromMillseconds(-1). + /// An operation that represents the time delay. + /// + public static AsyncResult Delay(TimeSpan delay, IAsyncUpdateSource updateSource) + { + var millisecondsDelay = (long)delay.TotalMilliseconds; + + if (millisecondsDelay > int.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(delay)); + } + + return Delay((int)millisecondsDelay, updateSource); + } + + #endregion + + #region Retry + + /// + /// Creates an operation that completes when the source operation is completed successfully. + /// + /// A delegate that initiates the source operation. + /// The number of milliseconds to wait after a failed try before starting a new operation. + /// Thrown if the is . + /// Thrown if is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func opFactory, int millisecondsRetryDelay) + { + return Retry(opFactory, millisecondsRetryDelay, 0); + } + + /// + /// Creates an operation that completes when the source operation is completed successfully. + /// + /// A delegate that initiates the source operation. + /// The number of milliseconds to wait after a failed try before starting a new operation. + /// Update notifications provider. + /// Thrown if the is . + /// Thrown if is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func opFactory, int millisecondsRetryDelay, IAsyncUpdateSource updateSource) + { + return Retry(opFactory, millisecondsRetryDelay, 0, updateSource); + } + + /// + /// Creates an operation that completes when the source operation is completed successfully or maximum number of retries exceeded. + /// + /// A delegate that initiates the source operation. + /// The number of milliseconds to wait after a failed try before starting a new operation. + /// Maximum number of retries. Zero means no limits. + /// Thrown if the is . + /// Thrown if or is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func opFactory, int millisecondsRetryDelay, int maxRetryCount) + { + if (opFactory == null) + { + throw new ArgumentNullException(nameof(opFactory)); + } + + if (millisecondsRetryDelay < 0) + { + throw new ArgumentOutOfRangeException(nameof(millisecondsRetryDelay), millisecondsRetryDelay, Constants.ErrorValueIsLessThanZero); + } + + if (maxRetryCount < 0) + { + throw new ArgumentOutOfRangeException(nameof(maxRetryCount), maxRetryCount, Constants.ErrorValueIsLessThanZero); + } + + var result = new TimerRetryResult(opFactory, millisecondsRetryDelay, maxRetryCount); + result.Start(); + return result; + } + + /// + /// Creates an operation that completes when the source operation is completed successfully or maximum number of retries exceeded. + /// + /// A delegate that initiates the source operation. + /// The number of milliseconds to wait after a failed try before starting a new operation. + /// Maximum number of retries. Zero means no limits. + /// Update notifications provider. + /// Thrown if the or is . + /// Thrown if or is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func opFactory, int millisecondsRetryDelay, int maxRetryCount, IAsyncUpdateSource updateSource) + { + if (opFactory == null) + { + throw new ArgumentNullException(nameof(opFactory)); + } + + if (updateSource == null) + { + throw new ArgumentNullException(nameof(updateSource)); + } + + if (millisecondsRetryDelay < 0) + { + throw new ArgumentOutOfRangeException(nameof(millisecondsRetryDelay), millisecondsRetryDelay, Constants.ErrorValueIsLessThanZero); + } + + if (maxRetryCount < 0) + { + throw new ArgumentOutOfRangeException(nameof(maxRetryCount), maxRetryCount, Constants.ErrorValueIsLessThanZero); + } + + var result = new UpdatableRetryResult(opFactory, millisecondsRetryDelay, maxRetryCount, updateSource); + result.Start(); + return result; + } + + /// + /// Creates an operation that completes when the source operation is completed successfully. + /// + /// A delegate that initiates the source operation. + /// The time to wait after a failed try before starting a new operation. + /// Thrown if the is . + /// Thrown if is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func opFactory, TimeSpan retryDelay) + { + return Retry(opFactory, retryDelay, 0); + } + + /// + /// Creates an operation that completes when the source operation is completed successfully. + /// + /// A delegate that initiates the source operation. + /// The time to wait after a failed try before starting a new operation. + /// Update notifications provider. + /// Thrown if the or is . + /// Thrown if is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func opFactory, TimeSpan retryDelay, IAsyncUpdateSource updateSource) + { + return Retry(opFactory, retryDelay, 0, updateSource); + } + + /// + /// Creates an operation that completes when the source operation is completed successfully or maximum number of retries exceeded. + /// + /// A delegate that initiates the source operation. + /// The time to wait after a failed try before starting a new operation. + /// Maximum number of retries. Zero means no limits. + /// Thrown if the is . + /// Thrown if or is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func opFactory, TimeSpan retryDelay, int maxRetryCount) + { + var millisecondsDelay = (long)retryDelay.TotalMilliseconds; + + if (millisecondsDelay > int.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(retryDelay)); + } + + return Retry(opFactory, (int)millisecondsDelay, maxRetryCount); + } + + /// + /// Creates an operation that completes when the source operation is completed successfully or maximum number of retries exceeded. + /// + /// A delegate that initiates the source operation. + /// The time to wait after a failed try before starting a new operation. + /// Maximum number of retries. Zero means no limits. + /// Update notifications provider. + /// Thrown if the or is . + /// Thrown if or is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func opFactory, TimeSpan retryDelay, int maxRetryCount, IAsyncUpdateSource updateSource) + { + var millisecondsDelay = (long)retryDelay.TotalMilliseconds; + + if (millisecondsDelay > int.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(retryDelay)); + } + + return Retry(opFactory, (int)millisecondsDelay, maxRetryCount, updateSource); + } + + /// + /// Creates an operation that completes when the source operation is completed successfully. + /// + /// A delegate that initiates the source operation. + /// The number of milliseconds to wait after a failed try before starting a new operation. + /// Thrown if the is . + /// Thrown if is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func> opFactory, int millisecondsRetryDelay) + { + return Retry(opFactory, millisecondsRetryDelay, 0); + } + + /// + /// Creates an operation that completes when the source operation is completed successfully. + /// + /// A delegate that initiates the source operation. + /// The number of milliseconds to wait after a failed try before starting a new operation. + /// Update notifications provider. + /// Thrown if the or is . + /// Thrown if is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func> opFactory, int millisecondsRetryDelay, IAsyncUpdateSource updateSource) + { + return Retry(opFactory, millisecondsRetryDelay, 0, updateSource); + } + + /// + /// Creates an operation that completes when the source operation is completed successfully or maximum number of retries exceeded. + /// + /// A delegate that initiates the source operation. + /// The number of milliseconds to wait after a failed try before starting a new operation. + /// Maximum number of retries. Zero means no limits. + /// Thrown if the is . + /// Thrown if or is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func> opFactory, int millisecondsRetryDelay, int maxRetryCount) + { + if (opFactory == null) + { + throw new ArgumentNullException(nameof(opFactory)); + } + + if (millisecondsRetryDelay < 0) + { + throw new ArgumentOutOfRangeException(nameof(millisecondsRetryDelay), millisecondsRetryDelay, Constants.ErrorValueIsLessThanZero); + } + + if (maxRetryCount < 0) + { + throw new ArgumentOutOfRangeException(nameof(maxRetryCount), maxRetryCount, Constants.ErrorValueIsLessThanZero); + } + + var result = new TimerRetryResult(opFactory, millisecondsRetryDelay, maxRetryCount); + result.Start(); + return result; + } + + /// + /// Creates an operation that completes when the source operation is completed successfully or maximum number of retries exceeded. + /// + /// A delegate that initiates the source operation. + /// The number of milliseconds to wait after a failed try before starting a new operation. + /// Maximum number of retries. Zero means no limits. + /// Update notifications provider. + /// Thrown if the or is . + /// Thrown if or is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func> opFactory, int millisecondsRetryDelay, int maxRetryCount, IAsyncUpdateSource updateSource) + { + if (opFactory == null) + { + throw new ArgumentNullException(nameof(opFactory)); + } + + if (updateSource == null) + { + throw new ArgumentNullException(nameof(updateSource)); + } + + if (millisecondsRetryDelay < 0) + { + throw new ArgumentOutOfRangeException(nameof(millisecondsRetryDelay), millisecondsRetryDelay, Constants.ErrorValueIsLessThanZero); + } + + if (maxRetryCount < 0) + { + throw new ArgumentOutOfRangeException(nameof(maxRetryCount), maxRetryCount, Constants.ErrorValueIsLessThanZero); + } + + var result = new UpdatableRetryResult(opFactory, millisecondsRetryDelay, maxRetryCount, updateSource); + result.Start(); + return result; + } + + /// + /// Creates an operation that completes when the source operation is completed successfully. + /// + /// A delegate that initiates the source operation. + /// The time to wait after a failed try before starting a new operation. + /// Thrown if the is . + /// Thrown if is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func> opFactory, TimeSpan retryDelay) + { + return Retry(opFactory, retryDelay, 0); + } + + /// + /// Creates an operation that completes when the source operation is completed successfully. + /// + /// A delegate that initiates the source operation. + /// The time to wait after a failed try before starting a new operation. + /// Update notifications provider. + /// Thrown if the or is . + /// Thrown if is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func> opFactory, TimeSpan retryDelay, IAsyncUpdateSource updateSource) + { + return Retry(opFactory, retryDelay, 0, updateSource); + } + + /// + /// Creates an operation that completes when the source operation is completed successfully or maximum number of retries exceeded. + /// + /// A delegate that initiates the source operation. + /// The time to wait after a failed try before starting a new operation. + /// Maximum number of retries. Zero means no limits. + /// Thrown if the is . + /// Thrown if or is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func> opFactory, TimeSpan retryDelay, int maxRetryCount) + { + var millisecondsDelay = (long)retryDelay.TotalMilliseconds; + + if (millisecondsDelay > int.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(retryDelay)); + } + + return Retry(opFactory, (int)millisecondsDelay, maxRetryCount); + } + + /// + /// Creates an operation that completes when the source operation is completed successfully or maximum number of retries exceeded. + /// + /// A delegate that initiates the source operation. + /// The time to wait after a failed try before starting a new operation. + /// Maximum number of retries. Zero means no limits. + /// Update notifications provider. + /// Thrown if the or is . + /// Thrown if or is less than zero. + /// An operation that represents the retry process. + /// + public static AsyncResult Retry(Func> opFactory, TimeSpan retryDelay, int maxRetryCount, IAsyncUpdateSource updateSource) + { + var millisecondsDelay = (long)retryDelay.TotalMilliseconds; + + if (millisecondsDelay > int.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(retryDelay)); + } + + return Retry(opFactory, (int)millisecondsDelay, maxRetryCount, updateSource); + } + + #endregion + + #region WhenAll + + /// + /// Creates an operation that will complete when all of the specified objects in an enumerable collection have completed. + /// + /// The operations to wait on for completion. + /// An operation that represents the completion of all of the supplied operations. + /// Thrown if is . + /// Thrown if the collection contained a operation.. + /// + /// + public static AsyncResult WhenAll(IEnumerable ops) + { + if (ops == null) + { + throw new ArgumentNullException(nameof(ops)); + } + + var opList = new List(); + + foreach (var op in ops) + { + if (op == null) + { + throw new ArgumentException(Constants.ErrorListElementIsNull, nameof(ops)); + } + + opList.Add(op); + } + + if (opList.Count == 0) + { + return CompletedOperation; + } + + return new WhenAllResult(opList.ToArray()); + } + + /// + /// Creates an operation that will complete when all of the specified objects in an enumerable collection have completed. + /// + /// The operations to wait on for completion. + /// An operation that represents the completion of all of the supplied operations. + /// Thrown if is . + /// Thrown if the collection contained a operation.. + /// + /// + public static AsyncResult WhenAll(IEnumerable> ops) + { + if (ops == null) + { + throw new ArgumentNullException(nameof(ops)); + } + + var opList = new List>(); + + foreach (var op in ops) + { + if (op == null) + { + throw new ArgumentException(Constants.ErrorListElementIsNull, nameof(ops)); + } + + opList.Add(op); + } + + if (opList.Count == 0) + { + return FromResult(new T[0]); + } + + return new WhenAllResult(opList.ToArray()); + } + + /// + /// Creates an operation that will complete when all of the specified objects in an array have completed. + /// + /// The operations to wait on for completion. + /// An operation that represents the completion of all of the supplied operations. + /// Thrown if is . + /// Thrown if the collection contained a operation.. + /// + /// + public static AsyncResult WhenAll(params IAsyncOperation[] ops) + { + if (ops == null) + { + throw new ArgumentNullException(nameof(ops)); + } + + if (ops.Length == 0) + { + return CompletedOperation; + } + + var opArray = new IAsyncOperation[ops.Length]; + + for (var i = 0; i < ops.Length; i++) + { + if (ops[i] == null) + { + throw new ArgumentException(Constants.ErrorListElementIsNull, nameof(ops)); + } + + opArray[i] = ops[i]; + } + + return new WhenAllResult(opArray); + } + + /// + /// Creates an operation that will complete when all of the specified objects in an array have completed. + /// + /// The operations to wait on for completion. + /// An operation that represents the completion of all of the supplied operations. + /// Thrown if is . + /// Thrown if the collection contained a operation.. + /// + /// + public static AsyncResult WhenAll(params IAsyncOperation[] ops) + { + if (ops == null) + { + throw new ArgumentNullException(nameof(ops)); + } + + if (ops.Length == 0) + { + return FromResult(new T[0]); + } + + var opArray = new IAsyncOperation[ops.Length]; + + for (var i = 0; i < ops.Length; i++) + { + if (ops[i] == null) + { + throw new ArgumentException(Constants.ErrorListElementIsNull, nameof(ops)); + } + + opArray[i] = ops[i]; + } + + return new WhenAllResult(opArray); + } + + #endregion + + #region WhenAny + + /// + /// Creates an operation that will complete when any of the specified objects in an enumerable collection have completed. + /// + /// The operations to wait on for completion. + /// An operation that represents the completion of any of the supplied operations. + /// Thrown if is . + /// Thrown if the collection contained a operation.. + /// + public static AsyncResult WhenAny(IEnumerable ops) where T : IAsyncOperation + { + if (ops == null) + { + throw new ArgumentNullException(nameof(ops)); + } + + var opList = new List(); + + foreach (var op in ops) + { + if (op == null) + { + throw new ArgumentException(Constants.ErrorListElementIsNull, nameof(ops)); + } + + opList.Add(op); + } + + if (opList.Count == 0) + { + throw new ArgumentException(Constants.ErrorListIsEmpty, nameof(ops)); + } + + return new WhenAnyResult(opList.ToArray()); + } + + /// + /// Creates an operation that will complete when any of the specified objects in an array have completed. + /// + /// The operations to wait on for completion. + /// An operation that represents the completion of any of the supplied operations. + /// Thrown if is . + /// Thrown if the collection contained a operation.. + /// + public static AsyncResult WhenAny(params T[] ops) where T : IAsyncOperation + { + if (ops == null) + { + throw new ArgumentNullException(nameof(ops)); + } + + if (ops.Length == 0) + { + throw new ArgumentException(Constants.ErrorListIsEmpty, nameof(ops)); + } + + var opArray = new T[ops.Length]; + + for (var i = 0; i < ops.Length; i++) + { + if (ops[i] == null) + { + throw new ArgumentException(Constants.ErrorListElementIsNull, nameof(ops)); + } + + opArray[i] = ops[i]; + } + + return new WhenAnyResult(opArray); + } + + #endregion + + #endregion + } +} diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index a7413d4..6cdef3c 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -9,9 +9,6 @@ using System.Runtime.ExceptionServices; #endif using System.Threading; -#if UNITYFX_SUPPORT_TAP -using System.Threading.Tasks; -#endif namespace UnityFx.Async { @@ -48,7 +45,7 @@ namespace UnityFx.Async /// /// [DebuggerDisplay("{DebuggerDisplay,nq}")] - public class AsyncResult : IAsyncOperation, IAsyncCancellable, IEnumerator + public partial class AsyncResult : IAsyncOperation, IAsyncCancellable, IEnumerator { #region data @@ -63,9 +60,6 @@ public class AsyncResult : IAsyncOperation, IAsyncCancellable, IEnumerator private const int _resetMask = 0x70000000; private static readonly object _continuationCompletionSentinel = new object(); - private static AsyncResult _completedOperation; - private static AsyncResult _canceledOperation; - private readonly object _asyncState; private AggregateException _exception; @@ -636,1039 +630,6 @@ protected virtual void Dispose(bool disposing) #endregion - #region static interface - - /// - /// Gets an operation that's already been completed successfully. - /// - /// - /// Note that call have no effect on operations returned with the property. May not always return the same instance. - /// - /// Completed instance. - public static AsyncResult CompletedOperation - { - get - { - if (_completedOperation == null) - { - _completedOperation = new AsyncResult(_flagDoNotDispose | _flagCompletedSynchronously | StatusRanToCompletion); - } - - return _completedOperation; - } - } - - /// - /// Gets an operation that's already been canceled. - /// - /// - /// Note that call have no effect on operations returned with the property. May not always return the same instance. - /// - /// Completed instance. - public static AsyncResult CanceledOperation - { - get - { - if (_canceledOperation == null) - { - _canceledOperation = new AsyncResult(_flagDoNotDispose | _flagCompletedSynchronously | StatusCanceled); - } - - return _canceledOperation; - } - } - - #region From* - - /// - /// Creates a that is canceled. - /// - /// A canceled operation. - /// - /// - /// - /// - public static AsyncResult FromCanceled() - { - return new AsyncResult(AsyncOperationStatus.Canceled); - } - - /// - /// Creates a that is canceled. - /// - /// User-defined data returned by . - /// A canceled operation. - /// - /// - /// - /// - public static AsyncResult FromCanceled(object asyncState) - { - return new AsyncResult(AsyncOperationStatus.Canceled, asyncState); - } - - /// - /// Creates a that is canceled. - /// - /// A canceled operation. - /// - /// - /// - /// - public static AsyncResult FromCanceled() - { - return new AsyncResult(AsyncOperationStatus.Canceled); - } - - /// - /// Creates a that is canceled. - /// - /// User-defined data returned by . - /// A canceled operation. - /// - /// - /// - /// - public static AsyncResult FromCanceled(object asyncState) - { - return new AsyncResult(AsyncOperationStatus.Canceled, asyncState); - } - - /// - /// Creates a that has completed with a specified exception. - /// - /// The exception to complete the operation with. - /// A faulted operation. - /// - /// - /// - /// - public static AsyncResult FromException(Exception exception) - { - return new AsyncResult(exception, null); - } - - /// - /// Creates a that has completed with a specified exception. - /// - /// The exception to complete the operation with. - /// User-defined data returned by . - /// A faulted operation. - /// - /// - /// - /// - public static AsyncResult FromException(Exception exception, object asyncState) - { - return new AsyncResult(exception, asyncState); - } - - /// - /// Creates a that has completed with specified exceptions. - /// - /// Exceptions to complete the operation with. - /// A faulted operation. - /// - /// - /// - /// - public static AsyncResult FromExceptions(IEnumerable exceptions) - { - return new AsyncResult(exceptions, null); - } - - /// - /// Creates a that has completed with specified exceptions. - /// - /// Exceptions to complete the operation with. - /// User-defined data returned by . - /// A faulted operation. - /// - /// - /// - /// - public static AsyncResult FromExceptions(IEnumerable exceptions, object asyncState) - { - return new AsyncResult(exceptions, asyncState); - } - - /// - /// Creates a that has completed with a specified exception. - /// - /// The exception to complete the operation with. - /// A faulted operation. - /// - /// - /// - /// - public static AsyncResult FromException(Exception exception) - { - return new AsyncResult(exception, null); - } - - /// - /// Creates a that has completed with a specified exception. - /// - /// The exception to complete the operation with. - /// User-defined data returned by . - /// A faulted operation. - /// - /// - /// - /// - public static AsyncResult FromException(Exception exception, object asyncState) - { - return new AsyncResult(exception, asyncState); - } - - /// - /// Creates a that has completed with specified exceptions. - /// - /// Exceptions to complete the operation with. - /// A faulted operation. - /// - /// - /// - /// - public static AsyncResult FromExceptions(IEnumerable exceptions) - { - return new AsyncResult(exceptions, null); - } - - /// - /// Creates a that has completed with specified exceptions. - /// - /// Exceptions to complete the operation with. - /// User-defined data returned by . - /// A faulted operation. - /// - /// - /// - /// - public static AsyncResult FromExceptions(IEnumerable exceptions, object asyncState) - { - return new AsyncResult(exceptions, asyncState); - } - - /// - /// Creates a that has completed with a specified result. - /// - /// The result value with which to complete the operation. - /// A completed operation with the specified result value. - /// - /// - /// - /// - public static AsyncResult FromResult(T result) - { - return new AsyncResult(result, null); - } - - /// - /// Creates a that has completed with a specified result. - /// - /// The result value with which to complete the operation. - /// User-defined data returned by . - /// A completed operation with the specified result value. - /// - /// - /// - /// - public static AsyncResult FromResult(T result, object asyncState) - { - return new AsyncResult(result, asyncState); - } - -#if UNITYFX_SUPPORT_TAP - - /// - /// Creates an instance that completes when the specified completes. - /// - /// The source instance. - /// Thrown if the reference is . - /// An that represents the source . - /// - public static AsyncResult FromTask(Task task) - { - if (task == null) - { - throw new ArgumentNullException(nameof(task)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - task.ContinueWith( - t => - { - if (t.IsFaulted) - { - result.SetException(t.Exception); - } - else if (t.IsCanceled) - { - result.SetCanceled(); - } - else - { - result.SetCompleted(); - } - }, - TaskContinuationOptions.ExecuteSynchronously); - - return result; - } - - /// - /// Creates an instance that completes when the specified completes. - /// - /// The source instance. - /// Thrown if the reference is . - /// An that represents the source . - /// - public static AsyncResult FromTask(Task task) - { - if (task == null) - { - throw new ArgumentNullException(nameof(task)); - } - - var result = new AsyncCompletionSource(AsyncOperationStatus.Running); - - task.ContinueWith( - t => - { - if (t.IsFaulted) - { - result.SetException(t.Exception); - } - else if (t.IsCanceled) - { - result.SetCanceled(); - } - else - { - result.SetResult(t.Result); - } - }, - TaskContinuationOptions.ExecuteSynchronously); - - return result; - } - -#endif - -#if !NET35 - - /// - /// Creates a instance that can be used to track the source observable. - /// - /// Type of the operation result. - /// The source observable. - /// Thrown if the reference is . - /// Returns an instance that can be used to track the observable. - public static AsyncResult FromObservable(IObservable observable) - { - if (observable == null) - { - throw new ArgumentNullException(nameof(observable)); - } - - return new AsyncObservableResult(observable); - } - -#endif - - #endregion - - #region Delay - - /// - /// Creates an operation that completes after a time delay. - /// - /// The number of milliseconds to wait before completing the returned operation, or (-1) to wait indefinitely. - /// Thrown if the is less than -1. - /// An operation that represents the time delay. - /// - /// - public static AsyncResult Delay(int millisecondsDelay) - { - if (millisecondsDelay < Timeout.Infinite) - { - throw new ArgumentOutOfRangeException(nameof(millisecondsDelay), millisecondsDelay, Constants.ErrorValueIsLessThanZero); - } - - if (millisecondsDelay == 0) - { - return CompletedOperation; - } - - if (millisecondsDelay == Timeout.Infinite) - { - // NOTE: Cannot return AsyncResult instance because its Cancel implementation throws NotSupportedException. - return new AsyncCompletionSource(AsyncOperationStatus.Running); - } - - var result = new TimerDelayResult(millisecondsDelay); - result.Start(); - return result; - } - - /// - /// Creates an operation that completes after a time delay. This method creates a more effecient operation - /// than but requires a specialized updates source. - /// - /// The number of milliseconds to wait before completing the returned operation, or (-1) to wait indefinitely. - /// Update notifications provider. - /// Thrown if is . - /// Thrown if the is less than -1. - /// An operation that represents the time delay. - /// - public static AsyncResult Delay(int millisecondsDelay, IAsyncUpdateSource updateSource) - { - if (updateSource == null) - { - throw new ArgumentNullException(nameof(updateSource)); - } - - if (millisecondsDelay < Timeout.Infinite) - { - throw new ArgumentOutOfRangeException(nameof(millisecondsDelay), millisecondsDelay, Constants.ErrorValueIsLessThanZero); - } - - if (millisecondsDelay == 0) - { - return CompletedOperation; - } - - if (millisecondsDelay == Timeout.Infinite) - { - // NOTE: Cannot return AsyncResult instance because its Cancel implementation throws NotSupportedException. - return new AsyncCompletionSource(AsyncOperationStatus.Running); - } - - var result = new UpdatableDelayResult(millisecondsDelay, updateSource); - result.Start(); - return result; - } - - /// - /// Creates an operation that completes after a specified time interval. - /// - /// The time span to wait before completing the returned operation, or TimeSpan.FromMilliseconds(-1) to wait indefinitely. - /// Thrown if the represents a negative time interval other than TimeSpan.FromMillseconds(-1). - /// An operation that represents the time delay. - /// - /// - public static AsyncResult Delay(TimeSpan delay) - { - var millisecondsDelay = (long)delay.TotalMilliseconds; - - if (millisecondsDelay > int.MaxValue) - { - throw new ArgumentOutOfRangeException(nameof(delay)); - } - - return Delay((int)millisecondsDelay); - } - - /// - /// Creates an operation that completes after a specified time interval. This method creates a more effecient operation - /// than but requires a specialized updates source. - /// - /// The time span to wait before completing the returned operation, or TimeSpan.FromMilliseconds(-1) to wait indefinitely. - /// Update notifications provider. - /// Thrown if is . - /// Thrown if the represents a negative time interval other than TimeSpan.FromMillseconds(-1). - /// An operation that represents the time delay. - /// - public static AsyncResult Delay(TimeSpan delay, IAsyncUpdateSource updateSource) - { - var millisecondsDelay = (long)delay.TotalMilliseconds; - - if (millisecondsDelay > int.MaxValue) - { - throw new ArgumentOutOfRangeException(nameof(delay)); - } - - return Delay((int)millisecondsDelay, updateSource); - } - - #endregion - - #region Retry - - /// - /// Creates an operation that completes when the source operation is completed successfully. - /// - /// A delegate that initiates the source operation. - /// The number of milliseconds to wait after a failed try before starting a new operation. - /// Thrown if the is . - /// Thrown if is less than zero. - /// An operation that represents the retry process. - /// - public static AsyncResult Retry(Func opFactory, int millisecondsRetryDelay) - { - return Retry(opFactory, millisecondsRetryDelay, 0); - } - - /// - /// Creates an operation that completes when the source operation is completed successfully. - /// - /// A delegate that initiates the source operation. - /// The number of milliseconds to wait after a failed try before starting a new operation. - /// Update notifications provider. - /// Thrown if the is . - /// Thrown if is less than zero. - /// An operation that represents the retry process. - /// - public static AsyncResult Retry(Func opFactory, int millisecondsRetryDelay, IAsyncUpdateSource updateSource) - { - return Retry(opFactory, millisecondsRetryDelay, 0, updateSource); - } - - /// - /// Creates an operation that completes when the source operation is completed successfully or maximum number of retries exceeded. - /// - /// A delegate that initiates the source operation. - /// The number of milliseconds to wait after a failed try before starting a new operation. - /// Maximum number of retries. Zero means no limits. - /// Thrown if the is . - /// Thrown if or is less than zero. - /// An operation that represents the retry process. - /// - public static AsyncResult Retry(Func opFactory, int millisecondsRetryDelay, int maxRetryCount) - { - if (opFactory == null) - { - throw new ArgumentNullException(nameof(opFactory)); - } - - if (millisecondsRetryDelay < 0) - { - throw new ArgumentOutOfRangeException(nameof(millisecondsRetryDelay), millisecondsRetryDelay, Constants.ErrorValueIsLessThanZero); - } - - if (maxRetryCount < 0) - { - throw new ArgumentOutOfRangeException(nameof(maxRetryCount), maxRetryCount, Constants.ErrorValueIsLessThanZero); - } - - var result = new TimerRetryResult(opFactory, millisecondsRetryDelay, maxRetryCount); - result.Start(); - return result; - } - - /// - /// Creates an operation that completes when the source operation is completed successfully or maximum number of retries exceeded. - /// - /// A delegate that initiates the source operation. - /// The number of milliseconds to wait after a failed try before starting a new operation. - /// Maximum number of retries. Zero means no limits. - /// Update notifications provider. - /// Thrown if the or is . - /// Thrown if or is less than zero. - /// An operation that represents the retry process. - /// - public static AsyncResult Retry(Func opFactory, int millisecondsRetryDelay, int maxRetryCount, IAsyncUpdateSource updateSource) - { - if (opFactory == null) - { - throw new ArgumentNullException(nameof(opFactory)); - } - - if (updateSource == null) - { - throw new ArgumentNullException(nameof(updateSource)); - } - - if (millisecondsRetryDelay < 0) - { - throw new ArgumentOutOfRangeException(nameof(millisecondsRetryDelay), millisecondsRetryDelay, Constants.ErrorValueIsLessThanZero); - } - - if (maxRetryCount < 0) - { - throw new ArgumentOutOfRangeException(nameof(maxRetryCount), maxRetryCount, Constants.ErrorValueIsLessThanZero); - } - - var result = new UpdatableRetryResult(opFactory, millisecondsRetryDelay, maxRetryCount, updateSource); - result.Start(); - return result; - } - - /// - /// Creates an operation that completes when the source operation is completed successfully. - /// - /// A delegate that initiates the source operation. - /// The time to wait after a failed try before starting a new operation. - /// Thrown if the is . - /// Thrown if is less than zero. - /// An operation that represents the retry process. - /// - public static AsyncResult Retry(Func opFactory, TimeSpan retryDelay) - { - return Retry(opFactory, retryDelay, 0); - } - - /// - /// Creates an operation that completes when the source operation is completed successfully. - /// - /// A delegate that initiates the source operation. - /// The time to wait after a failed try before starting a new operation. - /// Update notifications provider. - /// Thrown if the or is . - /// Thrown if is less than zero. - /// An operation that represents the retry process. - /// - public static AsyncResult Retry(Func opFactory, TimeSpan retryDelay, IAsyncUpdateSource updateSource) - { - return Retry(opFactory, retryDelay, 0, updateSource); - } - - /// - /// Creates an operation that completes when the source operation is completed successfully or maximum number of retries exceeded. - /// - /// A delegate that initiates the source operation. - /// The time to wait after a failed try before starting a new operation. - /// Maximum number of retries. Zero means no limits. - /// Thrown if the is . - /// Thrown if or is less than zero. - /// An operation that represents the retry process. - /// - public static AsyncResult Retry(Func opFactory, TimeSpan retryDelay, int maxRetryCount) - { - var millisecondsDelay = (long)retryDelay.TotalMilliseconds; - - if (millisecondsDelay > int.MaxValue) - { - throw new ArgumentOutOfRangeException(nameof(retryDelay)); - } - - return Retry(opFactory, (int)millisecondsDelay, maxRetryCount); - } - - /// - /// Creates an operation that completes when the source operation is completed successfully or maximum number of retries exceeded. - /// - /// A delegate that initiates the source operation. - /// The time to wait after a failed try before starting a new operation. - /// Maximum number of retries. Zero means no limits. - /// Update notifications provider. - /// Thrown if the or is . - /// Thrown if or is less than zero. - /// An operation that represents the retry process. - /// - public static AsyncResult Retry(Func opFactory, TimeSpan retryDelay, int maxRetryCount, IAsyncUpdateSource updateSource) - { - var millisecondsDelay = (long)retryDelay.TotalMilliseconds; - - if (millisecondsDelay > int.MaxValue) - { - throw new ArgumentOutOfRangeException(nameof(retryDelay)); - } - - return Retry(opFactory, (int)millisecondsDelay, maxRetryCount, updateSource); - } - - /// - /// Creates an operation that completes when the source operation is completed successfully. - /// - /// A delegate that initiates the source operation. - /// The number of milliseconds to wait after a failed try before starting a new operation. - /// Thrown if the is . - /// Thrown if is less than zero. - /// An operation that represents the retry process. - /// - public static AsyncResult Retry(Func> opFactory, int millisecondsRetryDelay) - { - return Retry(opFactory, millisecondsRetryDelay, 0); - } - - /// - /// Creates an operation that completes when the source operation is completed successfully. - /// - /// A delegate that initiates the source operation. - /// The number of milliseconds to wait after a failed try before starting a new operation. - /// Update notifications provider. - /// Thrown if the or is . - /// Thrown if is less than zero. - /// An operation that represents the retry process. - /// - public static AsyncResult Retry(Func> opFactory, int millisecondsRetryDelay, IAsyncUpdateSource updateSource) - { - return Retry(opFactory, millisecondsRetryDelay, 0, updateSource); - } - - /// - /// Creates an operation that completes when the source operation is completed successfully or maximum number of retries exceeded. - /// - /// A delegate that initiates the source operation. - /// The number of milliseconds to wait after a failed try before starting a new operation. - /// Maximum number of retries. Zero means no limits. - /// Thrown if the is . - /// Thrown if or is less than zero. - /// An operation that represents the retry process. - /// - public static AsyncResult Retry(Func> opFactory, int millisecondsRetryDelay, int maxRetryCount) - { - if (opFactory == null) - { - throw new ArgumentNullException(nameof(opFactory)); - } - - if (millisecondsRetryDelay < 0) - { - throw new ArgumentOutOfRangeException(nameof(millisecondsRetryDelay), millisecondsRetryDelay, Constants.ErrorValueIsLessThanZero); - } - - if (maxRetryCount < 0) - { - throw new ArgumentOutOfRangeException(nameof(maxRetryCount), maxRetryCount, Constants.ErrorValueIsLessThanZero); - } - - var result = new TimerRetryResult(opFactory, millisecondsRetryDelay, maxRetryCount); - result.Start(); - return result; - } - - /// - /// Creates an operation that completes when the source operation is completed successfully or maximum number of retries exceeded. - /// - /// A delegate that initiates the source operation. - /// The number of milliseconds to wait after a failed try before starting a new operation. - /// Maximum number of retries. Zero means no limits. - /// Update notifications provider. - /// Thrown if the or is . - /// Thrown if or is less than zero. - /// An operation that represents the retry process. - /// - public static AsyncResult Retry(Func> opFactory, int millisecondsRetryDelay, int maxRetryCount, IAsyncUpdateSource updateSource) - { - if (opFactory == null) - { - throw new ArgumentNullException(nameof(opFactory)); - } - - if (updateSource == null) - { - throw new ArgumentNullException(nameof(updateSource)); - } - - if (millisecondsRetryDelay < 0) - { - throw new ArgumentOutOfRangeException(nameof(millisecondsRetryDelay), millisecondsRetryDelay, Constants.ErrorValueIsLessThanZero); - } - - if (maxRetryCount < 0) - { - throw new ArgumentOutOfRangeException(nameof(maxRetryCount), maxRetryCount, Constants.ErrorValueIsLessThanZero); - } - - var result = new UpdatableRetryResult(opFactory, millisecondsRetryDelay, maxRetryCount, updateSource); - result.Start(); - return result; - } - - /// - /// Creates an operation that completes when the source operation is completed successfully. - /// - /// A delegate that initiates the source operation. - /// The time to wait after a failed try before starting a new operation. - /// Thrown if the is . - /// Thrown if is less than zero. - /// An operation that represents the retry process. - /// - public static AsyncResult Retry(Func> opFactory, TimeSpan retryDelay) - { - return Retry(opFactory, retryDelay, 0); - } - - /// - /// Creates an operation that completes when the source operation is completed successfully. - /// - /// A delegate that initiates the source operation. - /// The time to wait after a failed try before starting a new operation. - /// Update notifications provider. - /// Thrown if the or is . - /// Thrown if is less than zero. - /// An operation that represents the retry process. - /// - public static AsyncResult Retry(Func> opFactory, TimeSpan retryDelay, IAsyncUpdateSource updateSource) - { - return Retry(opFactory, retryDelay, 0, updateSource); - } - - /// - /// Creates an operation that completes when the source operation is completed successfully or maximum number of retries exceeded. - /// - /// A delegate that initiates the source operation. - /// The time to wait after a failed try before starting a new operation. - /// Maximum number of retries. Zero means no limits. - /// Thrown if the is . - /// Thrown if or is less than zero. - /// An operation that represents the retry process. - /// - public static AsyncResult Retry(Func> opFactory, TimeSpan retryDelay, int maxRetryCount) - { - var millisecondsDelay = (long)retryDelay.TotalMilliseconds; - - if (millisecondsDelay > int.MaxValue) - { - throw new ArgumentOutOfRangeException(nameof(retryDelay)); - } - - return Retry(opFactory, (int)millisecondsDelay, maxRetryCount); - } - - /// - /// Creates an operation that completes when the source operation is completed successfully or maximum number of retries exceeded. - /// - /// A delegate that initiates the source operation. - /// The time to wait after a failed try before starting a new operation. - /// Maximum number of retries. Zero means no limits. - /// Update notifications provider. - /// Thrown if the or is . - /// Thrown if or is less than zero. - /// An operation that represents the retry process. - /// - public static AsyncResult Retry(Func> opFactory, TimeSpan retryDelay, int maxRetryCount, IAsyncUpdateSource updateSource) - { - var millisecondsDelay = (long)retryDelay.TotalMilliseconds; - - if (millisecondsDelay > int.MaxValue) - { - throw new ArgumentOutOfRangeException(nameof(retryDelay)); - } - - return Retry(opFactory, (int)millisecondsDelay, maxRetryCount, updateSource); - } - - #endregion - - #region WhenAll - - /// - /// Creates an operation that will complete when all of the specified objects in an enumerable collection have completed. - /// - /// The operations to wait on for completion. - /// An operation that represents the completion of all of the supplied operations. - /// Thrown if is . - /// Thrown if the collection contained a operation.. - /// - /// - public static AsyncResult WhenAll(IEnumerable ops) - { - if (ops == null) - { - throw new ArgumentNullException(nameof(ops)); - } - - var opList = new List(); - - foreach (var op in ops) - { - if (op == null) - { - throw new ArgumentException(Constants.ErrorListElementIsNull, nameof(ops)); - } - - opList.Add(op); - } - - if (opList.Count == 0) - { - return CompletedOperation; - } - - return new WhenAllResult(opList.ToArray()); - } - - /// - /// Creates an operation that will complete when all of the specified objects in an enumerable collection have completed. - /// - /// The operations to wait on for completion. - /// An operation that represents the completion of all of the supplied operations. - /// Thrown if is . - /// Thrown if the collection contained a operation.. - /// - /// - public static AsyncResult WhenAll(IEnumerable> ops) - { - if (ops == null) - { - throw new ArgumentNullException(nameof(ops)); - } - - var opList = new List>(); - - foreach (var op in ops) - { - if (op == null) - { - throw new ArgumentException(Constants.ErrorListElementIsNull, nameof(ops)); - } - - opList.Add(op); - } - - if (opList.Count == 0) - { - return FromResult(new T[0]); - } - - return new WhenAllResult(opList.ToArray()); - } - - /// - /// Creates an operation that will complete when all of the specified objects in an array have completed. - /// - /// The operations to wait on for completion. - /// An operation that represents the completion of all of the supplied operations. - /// Thrown if is . - /// Thrown if the collection contained a operation.. - /// - /// - public static AsyncResult WhenAll(params IAsyncOperation[] ops) - { - if (ops == null) - { - throw new ArgumentNullException(nameof(ops)); - } - - if (ops.Length == 0) - { - return CompletedOperation; - } - - var opArray = new IAsyncOperation[ops.Length]; - - for (var i = 0; i < ops.Length; i++) - { - if (ops[i] == null) - { - throw new ArgumentException(Constants.ErrorListElementIsNull, nameof(ops)); - } - - opArray[i] = ops[i]; - } - - return new WhenAllResult(opArray); - } - - /// - /// Creates an operation that will complete when all of the specified objects in an array have completed. - /// - /// The operations to wait on for completion. - /// An operation that represents the completion of all of the supplied operations. - /// Thrown if is . - /// Thrown if the collection contained a operation.. - /// - /// - public static AsyncResult WhenAll(params IAsyncOperation[] ops) - { - if (ops == null) - { - throw new ArgumentNullException(nameof(ops)); - } - - if (ops.Length == 0) - { - return FromResult(new T[0]); - } - - var opArray = new IAsyncOperation[ops.Length]; - - for (var i = 0; i < ops.Length; i++) - { - if (ops[i] == null) - { - throw new ArgumentException(Constants.ErrorListElementIsNull, nameof(ops)); - } - - opArray[i] = ops[i]; - } - - return new WhenAllResult(opArray); - } - - #endregion - - #region WhenAny - - /// - /// Creates an operation that will complete when any of the specified objects in an enumerable collection have completed. - /// - /// The operations to wait on for completion. - /// An operation that represents the completion of any of the supplied operations. - /// Thrown if is . - /// Thrown if the collection contained a operation.. - /// - public static AsyncResult WhenAny(IEnumerable ops) where T : IAsyncOperation - { - if (ops == null) - { - throw new ArgumentNullException(nameof(ops)); - } - - var opList = new List(); - - foreach (var op in ops) - { - if (op == null) - { - throw new ArgumentException(Constants.ErrorListElementIsNull, nameof(ops)); - } - - opList.Add(op); - } - - if (opList.Count == 0) - { - throw new ArgumentException(Constants.ErrorListIsEmpty, nameof(ops)); - } - - return new WhenAnyResult(opList.ToArray()); - } - - /// - /// Creates an operation that will complete when any of the specified objects in an array have completed. - /// - /// The operations to wait on for completion. - /// An operation that represents the completion of any of the supplied operations. - /// Thrown if is . - /// Thrown if the collection contained a operation.. - /// - public static AsyncResult WhenAny(params T[] ops) where T : IAsyncOperation - { - if (ops == null) - { - throw new ArgumentNullException(nameof(ops)); - } - - if (ops.Length == 0) - { - throw new ArgumentException(Constants.ErrorListIsEmpty, nameof(ops)); - } - - var opArray = new T[ops.Length]; - - for (var i = 0; i < ops.Length; i++) - { - if (ops[i] == null) - { - throw new ArgumentException(Constants.ErrorListElementIsNull, nameof(ops)); - } - - opArray[i] = ops[i]; - } - - return new WhenAnyResult(opArray); - } - - #endregion - - #endregion - #region internals internal const int StatusCreated = 0; From df6555d7895ccbb5221fe4c33069f746c504ccd1 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 19 Apr 2018 12:11:58 +0300 Subject: [PATCH 102/128] Added cancellable overloads for Join extensions --- .../Api/Extensions/AsyncExtensions.Wait.cs | 362 +++++++++++++----- .../Api/Extensions/AsyncExtensions.cs | 4 + 2 files changed, 266 insertions(+), 100 deletions(-) diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs index 4bc4566..418dc39 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs @@ -117,37 +117,11 @@ public static bool Wait(this IAsyncOperation op, TimeSpan timeout) /// A cancellation token to observe while waiting for the operation to complete. /// Thrown if the operation was canceled or faulted. /// Thrown is the operation is disposed. - /// The cancellationToken was canceled. + /// The was canceled. /// public static void Wait(this IAsyncOperation op, CancellationToken cancellationToken) { -#if UNITYFX_NOT_THREAD_SAFE - - SpinUntilCompleted(op, cancellationToken); - -#else - - 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(); - } - } - -#endif - + WaitInternal(op, cancellationToken); ThrowIfNonSuccess(op, true); } @@ -161,49 +135,18 @@ public static void Wait(this IAsyncOperation op, CancellationToken cancellationT /// if the operation completed execution within the allotted time; otherwise, . /// is a negative number other than -1. /// Thrown if the operation was canceled or faulted. + /// The was canceled. /// Thrown is the operation is disposed. /// public static bool Wait(this IAsyncOperation op, int millisecondsTimeout, CancellationToken cancellationToken) { -#if UNITYFX_NOT_THREAD_SAFE - - var result = SpinUntilCompleted(op, millisecondsTimeout, cancellationToken); - -#else - - 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); - } - } - -#endif - - if (result) + if (WaitInternal(op, millisecondsTimeout, cancellationToken)) { ThrowIfNonSuccess(op, true); + return true; } - return result; + return false; } /// @@ -216,49 +159,18 @@ public static bool Wait(this IAsyncOperation op, int millisecondsTimeout, Cancel /// if the operation completed execution within the allotted time; otherwise, . /// is a negative number other than -1 milliseconds, or is greater than . /// Thrown if the operation was canceled or faulted. + /// The was canceled. /// Thrown is the operation is disposed. /// public static bool Wait(this IAsyncOperation op, TimeSpan timeout, CancellationToken cancellationToken) { -#if UNITYFX_NOT_THREAD_SAFE - - var result = SpinUntilCompleted(op, timeout, cancellationToken); - -#else - - 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); - } - } - -#endif - - if (result) + if (WaitInternal(op, timeout, cancellationToken)) { ThrowIfNonSuccess(op, true); + return true; } - return result; + return false; } #endif @@ -375,8 +287,8 @@ public static void Join(this IAsyncOperation op, TimeSpan timeout) /// The operation to join. /// The operation result. /// Thrown is the operation is disposed. - /// - /// + /// + /// /// public static TResult Join(this IAsyncOperation op) { @@ -479,6 +391,256 @@ public static TResult Join(this IAsyncOperation op, TimeSpan t 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, false); + } + + /// + /// 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, false); + } + 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, false); + } + 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); + ThrowIfNonSuccess(op, false); + 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)) + { + ThrowIfNonSuccess(op, false); + } + else + { + 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)) + { + ThrowIfNonSuccess(op, false); + } + else + { + throw new TimeoutException(); + } + + return op.Result; + } + +#endif + + #endregion + + #region implementation + +#if !NET35 + + private static void WaitInternal(IAsyncOperation op, CancellationToken cancellationToken) + { +#if UNITYFX_NOT_THREAD_SAFE + + SpinUntilCompleted(op, cancellationToken); + +#else + + 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(); + } + } + +#endif + } + + private static bool WaitInternal(IAsyncOperation op, int millisecondsTimeout, CancellationToken cancellationToken) + { +#if UNITYFX_NOT_THREAD_SAFE + + return SpinUntilCompleted(op, millisecondsTimeout, cancellationToken); + +#else + + 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; +#endif + } + + private static bool WaitInternal(IAsyncOperation op, TimeSpan timeout, CancellationToken cancellationToken) + { +#if UNITYFX_NOT_THREAD_SAFE + + return SpinUntilCompleted(op, timeout, cancellationToken); + +#else + + 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 + } + +#endif + #endregion } } diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs index a2d2829..1bd2d14 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs @@ -16,8 +16,12 @@ public static partial class AsyncExtensions { #region data +#if !NET35 + private static Action _cancelHandler; +#endif + #endregion #region Common From 47f242cb945d38d6eb946d9b413ffeb5cd476d4d Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 19 Apr 2018 17:33:16 +0300 Subject: [PATCH 103/128] Minor comment fixes --- src/UnityFx.Async/Api/Core/AsyncResult.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 6cdef3c..4903dbd 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -22,8 +22,7 @@ namespace UnityFx.Async /// call or implicitly using async/await keywords). These continuations can be /// invoked on a captured . The class inherits /// (just like Task) and can be used to implement Asynchronous Programming Model (APM). - /// There is a number of operation state accessors that can be used exactly like corresponding - /// properties of Task. + /// There are operation state accessors that can be used exactly like corresponding properties of Task. /// /// The class implements interface. So strictly speaking /// should be called when the operation is no longed in use. In practice that is only required From 1263a3076b423931439ac85abafb94c98dbc9332 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 19 Apr 2018 17:33:26 +0300 Subject: [PATCH 104/128] README update --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f780be5..ed43dfb 100644 --- a/README.md +++ b/README.md @@ -131,7 +131,7 @@ InitiateSomeAsyncOperation() ``` This does exaclty the same job as the callbacks sample, but it's much more readable. -That said promises are still not an ideal solution (at least for C#). They require quite a lot of filler code and rely heavily on delegate usage. +That said promises are still not an ideal solution (at least for C#). They require quite much filler code and rely heavily on delegate usage. ### Observables and reactive programming Observable event streams as defined in [reactive programming](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754) provide a convenient way of managing push-based event notifications (opposed to pull-based nature of `IEnumerable`). One of the core differences is multiple result values for observables versus single promise result. While observables may represent an asynchronous operation it is not always the case (and it is generally not recommended to use them in this way). That is why the concept is ot of the scope covered by this document. @@ -153,6 +153,8 @@ catch (Exception e) ``` In fact the only notable difference from synchronous implementation is usage of the mentioned `async` and `await` keywords. It's worth mentioning that a lot of hidden work is done by both the C# compliter and asynchronous operation to allow this. +*UnityFx.Async* supports all of the asynchronous programming approaches described. + ## Using the library Reference the DLL and import the namespace: ```csharp @@ -360,7 +362,7 @@ var op4 = AsyncResult.FromCanceled(); ``` ### Convertions -Library defines convertion methods between `IAsyncOperation` and `Task`, `IObservable`, `UnityWebRequest`, `AsyncOperation`, `WWW`: +Library defines convertion methods between `IAsyncOperation` and `Task`, `IObservable`, `UnityWebRequest`, `AsyncOperation`, `WWW` with corresponding extension methods: ```csharp var task = op.ToTask(); var observable = op.ToObservable(); From f3271f148bff8fc45c6c93c2835499dbf8b43a37 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 19 Apr 2018 17:36:34 +0300 Subject: [PATCH 105/128] CHANGELOG update --- CHANGELOG.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 14cab48..6547c22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,8 +14,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/); this proj - Added `ToAsync` extension method for `IObservable` interface. - Added `TryAddContinuation`/`RemoveContinuation` methods to `IAsyncOperationEvents` for non-delegate continuations. - Added `IAsyncUpdatable` and `IAsyncUpdateSource` interfaces. -- Added `Delay` overload that uses `IAsyncUpdateSource`-based service for time management. -- Added cancellation support (`IAsyncCancellable` interface). +- Added `Delay`/`Retry` overload that uses `IAsyncUpdateSource`-based service for time management. +- Added cancellation support (`IAsyncCancellable` interface, `WithCancellation` extension method and many implementation changes). +- Added `Wait`/`Join` overloads with `CancellationToken` argument. ### Changed - Changed `ContinueWith` extension signatures to match corresponding `Task` methods. From 6776604fcd053283957d8bf39c31f9bb094654ac Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 19 Apr 2018 22:06:33 +0300 Subject: [PATCH 106/128] Added possibility to force continuations to run asynchronously --- .../Tests/CompletionCallbackTests.cs | 30 +++++++++ src/UnityFx.Async/Api/Core/AsyncResult.cs | 17 ++--- .../Continuations/AsyncContinuation.cs | 62 ++++++++++++++----- 3 files changed, 86 insertions(+), 23 deletions(-) diff --git a/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs b/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs index 2bec968..ac7a022 100644 --- a/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs +++ b/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs @@ -54,6 +54,36 @@ public async Task TryAddCompletionCallback_ExecutesWhenOperationCompletes() Assert.True(callbackCalled); } + [Fact] + public async Task TryAddCompletionCallback_IsThreadSafe() + { + // Arrange + var op = new AsyncCompletionSource(); + var counter = 0; + + void CompletionCallback(IAsyncOperation o) + { + ++counter; + } + + void TestMethod() + { + for (int i = 0; i < 1000; ++i) + { + op.TryAddCompletionCallback(CompletionCallback); + } + } + + // Act + await Task.Run(new Action(TestMethod)); + await Task.Run(new Action(TestMethod)); + await Task.Run(new Action(TestMethod)); + op.SetCompleted(); + + // Assert + Assert.Equal(3000, counter); + } + [Fact] public void TryAddContinuation_ExecutesWhenOperationCompletes() { diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 4903dbd..fa0a62a 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -55,6 +55,7 @@ public partial class AsyncResult : IAsyncOperation, IAsyncCancellable, IEnumerat private const int _flagCompletedSynchronously = _flagCompleted | _flagCompletionReserved | _flagSynchronous; private const int _flagDisposed = 0x01000000; private const int _flagDoNotDispose = 0x10000000; + private const int _flagRunContinuationsAsynchronously = 0x20000000; private const int _statusMask = 0x0000000f; private const int _resetMask = 0x70000000; @@ -578,13 +579,13 @@ protected virtual void OnCompleted() { foreach (var item in continuationList) { - InvokeContinuation(this, item); + InvokeContinuation(item); } } } else { - InvokeContinuation(this, continuation); + InvokeContinuation(continuation); } } } @@ -1245,9 +1246,11 @@ private AsyncResult(int flags) /// Returns if the continuation was added; otherwise. private bool TryAddContinuationInternal(object continuation, SynchronizationContext syncContext) { - if (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) + var runContinuationsAsynchronously = (_flags & _flagRunContinuationsAsynchronously) != 0; + + if ((syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) || runContinuationsAsynchronously) { - continuation = new AsyncContinuation(syncContext, continuation); + continuation = new AsyncContinuation(syncContext, continuation, runContinuationsAsynchronously); } return TryAddContinuationInternal(continuation); @@ -1434,15 +1437,15 @@ private bool TryRemoveContinuationInternal(object valueToRemove) /// /// Invokes the specified continuation instance. /// - private static void InvokeContinuation(IAsyncOperation op, object continuation) + private void InvokeContinuation(object continuation) { if (continuation is IAsyncContinuation c) { - c.Invoke(op); + c.Invoke(this); } else { - AsyncContinuation.InvokeDelegate(op, continuation); + AsyncContinuation.InvokeDelegate(this, continuation); } } diff --git a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs index fdbffee..c821769 100644 --- a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs +++ b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs @@ -13,20 +13,23 @@ internal class AsyncContinuation : IAsyncContinuation { #region data + private static WaitCallback _waitCallback; private static SendOrPostCallback _postCallback; - private readonly SynchronizationContext _syncContext; - private readonly object _continuation; + private SynchronizationContext _syncContext; + private object _continuation; + private bool _runAsynchronously; private IAsyncOperation _op; #endregion #region interface - internal AsyncContinuation(SynchronizationContext syncContext, object continuation) + internal AsyncContinuation(SynchronizationContext syncContext, object continuation, bool runAsynchronously) { _syncContext = syncContext; _continuation = continuation; + _runAsynchronously = runAsynchronously; } internal static bool CanInvoke(IAsyncOperation op, AsyncContinuationOptions options) @@ -114,22 +117,18 @@ public void Invoke(IAsyncOperation op) { if (_syncContext == null || _syncContext == SynchronizationContext.Current) { - InvokeDelegate(op, _continuation); + if (_runAsynchronously) + { + InvokeAsync(op, SynchronizationContext.Current); + } + else + { + InvokeDelegate(op, _continuation); + } } else { - _op = op; - - if (_postCallback == null) - { - _postCallback = args => - { - var c = args as AsyncContinuation; - InvokeDelegate(c._op, c._continuation); - }; - } - - _syncContext.Post(_postCallback, this); + InvokeAsync(op, _syncContext); } } @@ -155,6 +154,37 @@ public override int GetHashCode() #endregion #region implementation + + private void InvokeAsync(IAsyncOperation op, SynchronizationContext syncContext) + { + _op = op; + + if (syncContext != null) + { + if (_postCallback == null) + { + _postCallback = PostCallback; + } + + _syncContext.Post(_postCallback, this); + } + else + { + if (_waitCallback == null) + { + _waitCallback = PostCallback; + } + + ThreadPool.QueueUserWorkItem(_waitCallback, this); + } + } + + private static void PostCallback(object args) + { + var c = args as AsyncContinuation; + InvokeDelegate(c._op, c._continuation); + } + #endregion } } From 3a5860d4ee27f0977e9174ae1586248668f6f2e3 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 19 Apr 2018 22:13:07 +0300 Subject: [PATCH 107/128] README typo fixes --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ed43dfb..0693cf1 100644 --- a/README.md +++ b/README.md @@ -100,7 +100,7 @@ Coroutines are another popular approach of programming asynchronous operations a * Coroutines can't handle exceptions, because `yield return` statements cannot be surrounded with a `try`-`catch` construction. This makes error handling a pain. * Coroutine requires a `MonoBehaviour` to run. * There is no way to wait for a coroutine other than yield. -* There is no way to get coroutine state information. +* There is no way to get coroutine status information. That said, here is the previous example rewrited using coroutines: ```csharp @@ -116,10 +116,10 @@ yield return InitiateAsyncOperation3(result2, result3); /// ... /// No way to handle exceptions here ``` -As you can see we had to wrap result values into custom classes (which resulted in quire unobvious code) and no error handling can be done at this level (have to rely on the methods been called). +As you can see we had to wrap result values into custom classes (which resulted in quite unobvious code) and no error handling can be done at this level. ### Promises to the rescue -Promises are a design pattern to structure asynchronous code as a sequence of chained (not nested!) operations. This concept was introduces for JS and has even become a [standard](https://promisesaplus.com/) since then. At low level a promise is an object containing a state (Running, Resolved or Rejected), a result value and (optionally) success/error callbacks. At high level the point of promises is to give us functional composition and error handling is the async world. +Promises are a design pattern to structure asynchronous code as a sequence of chained (not nested!) operations. This concept was introduces for JS and has even become a [standard](https://promisesaplus.com/) since then. At low level a promise is an object containing a state (Running, Resolved or Rejected), a result value and (optionally) success/error callbacks. At high level the point of promises is to provide functional composition and error handling is the async world. Let's rewrite the last callback hell sample using promises: ```csharp @@ -131,10 +131,10 @@ InitiateSomeAsyncOperation() ``` This does exaclty the same job as the callbacks sample, but it's much more readable. -That said promises are still not an ideal solution (at least for C#). They require quite much filler code and rely heavily on delegate usage. +That said promises are still not an ideal solution (at least for C#). They require quite much filler code and rely heavily on delegates usage. ### Observables and reactive programming -Observable event streams as defined in [reactive programming](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754) provide a convenient way of managing push-based event notifications (opposed to pull-based nature of `IEnumerable`). One of the core differences is multiple result values for observables versus single promise result. While observables may represent an asynchronous operation it is not always the case (and it is generally not recommended to use them in this way). That is why the concept is ot of the scope covered by this document. +Observable event streams as defined in [reactive programming](https://gist.github.com/staltz/868e7e9bc2a7b8c1f754) provide a convenient way of managing push-based event notifications (opposed to pull-based nature of `IEnumerable`). One of the core differences is multiple result values for observables versus single promise result. While observables may represent an asynchronous operation it is not always the case (and it is generally not recommended to use them in this way). That is why the concept is out of the scope covered by this document. ### Asynchronous programming with async and await C# 5.0/.NET 4.5 introduced a new appoach to asynchronous programming. By using `async` and `await` one can write asynchronous methods almost as synchronous methods. The following example shows implementation of the callback hell method with this technique: From 24f28eb9eb5d6c511796a9f20e55a481f8fd3344 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 20 Apr 2018 16:49:43 +0300 Subject: [PATCH 108/128] Moved all promise-related stuff to namespace UnityFx.Async.Promises --- src/UnityFx.Async.Tests/Tests/CatchTests.cs | 2 +- src/UnityFx.Async.Tests/Tests/FinallyTests.cs | 2 +- src/UnityFx.Async.Tests/Tests/ThenAllTests.cs | 2 +- src/UnityFx.Async.Tests/Tests/ThenAnyTests.cs | 2 +- src/UnityFx.Async.Tests/Tests/ThenTests.cs | 2 +- src/UnityFx.Async.Tests/Tests/ToTaskTests.cs | 4 ++-- .../Api/Extensions/AsyncExtensions.Promises.cs | 14 +++++++++----- 7 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/UnityFx.Async.Tests/Tests/CatchTests.cs b/src/UnityFx.Async.Tests/Tests/CatchTests.cs index cb1ad05..8a2c596 100644 --- a/src/UnityFx.Async.Tests/Tests/CatchTests.cs +++ b/src/UnityFx.Async.Tests/Tests/CatchTests.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Xunit; -namespace UnityFx.Async +namespace UnityFx.Async.Promises { public class CatchTests { diff --git a/src/UnityFx.Async.Tests/Tests/FinallyTests.cs b/src/UnityFx.Async.Tests/Tests/FinallyTests.cs index 387c33c..3d19169 100644 --- a/src/UnityFx.Async.Tests/Tests/FinallyTests.cs +++ b/src/UnityFx.Async.Tests/Tests/FinallyTests.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Xunit; -namespace UnityFx.Async +namespace UnityFx.Async.Promises { public class FinallyTests { diff --git a/src/UnityFx.Async.Tests/Tests/ThenAllTests.cs b/src/UnityFx.Async.Tests/Tests/ThenAllTests.cs index bb6f42b..6ede873 100644 --- a/src/UnityFx.Async.Tests/Tests/ThenAllTests.cs +++ b/src/UnityFx.Async.Tests/Tests/ThenAllTests.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Xunit; -namespace UnityFx.Async +namespace UnityFx.Async.Promises { public class ThenAllTests { diff --git a/src/UnityFx.Async.Tests/Tests/ThenAnyTests.cs b/src/UnityFx.Async.Tests/Tests/ThenAnyTests.cs index cf9e2c4..8ad6e36 100644 --- a/src/UnityFx.Async.Tests/Tests/ThenAnyTests.cs +++ b/src/UnityFx.Async.Tests/Tests/ThenAnyTests.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Xunit; -namespace UnityFx.Async +namespace UnityFx.Async.Promises { public class ThenAnyTests { diff --git a/src/UnityFx.Async.Tests/Tests/ThenTests.cs b/src/UnityFx.Async.Tests/Tests/ThenTests.cs index 08670b6..41dcb7a 100644 --- a/src/UnityFx.Async.Tests/Tests/ThenTests.cs +++ b/src/UnityFx.Async.Tests/Tests/ThenTests.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Xunit; -namespace UnityFx.Async +namespace UnityFx.Async.Promises { public class ThenTests { diff --git a/src/UnityFx.Async.Tests/Tests/ToTaskTests.cs b/src/UnityFx.Async.Tests/Tests/ToTaskTests.cs index e5c4a26..7a6bfeb 100644 --- a/src/UnityFx.Async.Tests/Tests/ToTaskTests.cs +++ b/src/UnityFx.Async.Tests/Tests/ToTaskTests.cs @@ -29,7 +29,7 @@ public async Task ToTask_CompletesWhenSourceCompletes() public async Task ToTask_FailsWhenSourceFails() { // Arrange - var op = AsyncResult.Delay(1).Then(() => AsyncResult.FromException(new Exception())); + var op = AsyncResult.FromException(new Exception()); var task = op.ToTask(); // Act/Assert @@ -40,7 +40,7 @@ public async Task ToTask_FailsWhenSourceFails() public async Task ToTask_FailsWhenSourceIsCanceled() { // Arrange - var op = AsyncResult.Delay(1).Then(() => AsyncResult.FromCanceled()); + var op = AsyncResult.FromCanceled(); var task = op.ToTask(); // Act/Assert diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs index 41ad68a..c1d2f02 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs @@ -3,11 +3,15 @@ using System; using System.Collections.Generic; -using System.Threading; +using System.ComponentModel; -namespace UnityFx.Async +namespace UnityFx.Async.Promises { - partial class AsyncExtensions + /// + /// Promise extensions for . + /// + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static class AsyncExtensions { #region Then @@ -369,7 +373,7 @@ public static IAsyncOperation ThenAny(this IAsyncOperation< #region Rebind /// - /// Schedules a callback to be executed after the promise has been resolved. + /// Transforms the promise result to another type. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. @@ -386,7 +390,7 @@ public static IAsyncOperation Rebind(this IAsyncOperation op, } /// - /// Schedules a callback to be executed after the promise has been resolved. + /// Transforms the promise result to another type. /// /// An operation to be continued. /// The callback to be executed when the operation has completed. From 8eac7561d5c720c79206a19268493b6837fb79fe Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 20 Apr 2018 16:50:12 +0300 Subject: [PATCH 109/128] Fixed ToTask extensions for already canceled tasks --- src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs index 7c2187f..40c21bc 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs @@ -228,7 +228,7 @@ public static Task ToTask(this IAsyncOperation op) } else if (status == AsyncOperationStatus.Canceled) { - return Task.FromCanceled(CancellationToken.None); + return Task.FromCanceled(new CancellationToken(true)); } else { @@ -262,7 +262,7 @@ public static Task ToTask(this IAsyncOperation op) } else if (status == AsyncOperationStatus.Canceled) { - return Task.FromCanceled(CancellationToken.None); + return Task.FromCanceled(new CancellationToken(true)); } else { From 74625563eb9bd80c25aa6adc589d79e3e87d897f Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 20 Apr 2018 16:56:39 +0300 Subject: [PATCH 110/128] Moved promises implementation to own namespace and folder --- src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs | 2 +- .../Continuations/{ => Promises}/CatchResult{T,TException}.cs | 2 +- .../Continuations/{ => Promises}/FinallyResult{T}.cs | 2 +- .../Continuations/{ => Promises}/RebindResult{T,U}.cs | 2 +- .../Continuations/{ => Promises}/ThenAllResult{T,U}.cs | 2 +- .../Continuations/{ => Promises}/ThenAnyResult{T,U}.cs | 2 +- .../Continuations/{ => Promises}/ThenResult{T,U}.cs | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) rename src/UnityFx.Async/Implementation/Continuations/{ => Promises}/CatchResult{T,TException}.cs (97%) rename src/UnityFx.Async/Implementation/Continuations/{ => Promises}/FinallyResult{T}.cs (97%) rename src/UnityFx.Async/Implementation/Continuations/{ => Promises}/RebindResult{T,U}.cs (97%) rename src/UnityFx.Async/Implementation/Continuations/{ => Promises}/ThenAllResult{T,U}.cs (97%) rename src/UnityFx.Async/Implementation/Continuations/{ => Promises}/ThenAnyResult{T,U}.cs (98%) rename src/UnityFx.Async/Implementation/Continuations/{ => Promises}/ThenResult{T,U}.cs (99%) diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs index 1bd2d14..7400407 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs @@ -9,7 +9,7 @@ namespace UnityFx.Async { /// - /// Extension methods for related classes. + /// Extension methods for . /// [EditorBrowsable(EditorBrowsableState.Advanced)] public static partial class AsyncExtensions diff --git a/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/CatchResult{T,TException}.cs similarity index 97% rename from src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs rename to src/UnityFx.Async/Implementation/Continuations/Promises/CatchResult{T,TException}.cs index 8340d47..92b7666 100644 --- a/src/UnityFx.Async/Implementation/Continuations/CatchResult{T,TException}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/CatchResult{T,TException}.cs @@ -3,7 +3,7 @@ using System; -namespace UnityFx.Async +namespace UnityFx.Async.Promises { internal sealed class CatchResult : ContinuationResult, IAsyncContinuation where TException : Exception { diff --git a/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs similarity index 97% rename from src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs rename to src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs index b3bdb42..293cddb 100644 --- a/src/UnityFx.Async/Implementation/Continuations/FinallyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs @@ -3,7 +3,7 @@ using System; -namespace UnityFx.Async +namespace UnityFx.Async.Promises { internal sealed class FinallyResult : ContinuationResult, IAsyncContinuation { diff --git a/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/RebindResult{T,U}.cs similarity index 97% rename from src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs rename to src/UnityFx.Async/Implementation/Continuations/Promises/RebindResult{T,U}.cs index af617f7..83df018 100644 --- a/src/UnityFx.Async/Implementation/Continuations/RebindResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/RebindResult{T,U}.cs @@ -4,7 +4,7 @@ using System; using System.Threading; -namespace UnityFx.Async +namespace UnityFx.Async.Promises { internal sealed class RebindResult : ContinuationResult, IAsyncContinuation { diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenAllResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/ThenAllResult{T,U}.cs similarity index 97% rename from src/UnityFx.Async/Implementation/Continuations/ThenAllResult{T,U}.cs rename to src/UnityFx.Async/Implementation/Continuations/Promises/ThenAllResult{T,U}.cs index b2b4fd6..a3b0f77 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ThenAllResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/ThenAllResult{T,U}.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Linq; -namespace UnityFx.Async +namespace UnityFx.Async.Promises { internal sealed class ThenAllResult : ThenResult { diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/ThenAnyResult{T,U}.cs similarity index 98% rename from src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs rename to src/UnityFx.Async/Implementation/Continuations/Promises/ThenAnyResult{T,U}.cs index dbb3601..b3064e2 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ThenAnyResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/ThenAnyResult{T,U}.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Linq; -namespace UnityFx.Async +namespace UnityFx.Async.Promises { internal sealed class ThenAnyResult : ThenResult { diff --git a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/ThenResult{T,U}.cs similarity index 99% rename from src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs rename to src/UnityFx.Async/Implementation/Continuations/Promises/ThenResult{T,U}.cs index df6b6c8..17f8d06 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ThenResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/ThenResult{T,U}.cs @@ -3,7 +3,7 @@ using System; -namespace UnityFx.Async +namespace UnityFx.Async.Promises { internal class ThenResult : ContinuationResult, IAsyncContinuation { From 2a7bf5321988976b39701cf412d8e8c58af6ae17 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 20 Apr 2018 19:25:46 +0300 Subject: [PATCH 111/128] Many minor continuation-related changes --- .../Continuations/AsyncContinuation.cs | 69 ++++++++++++------- .../Continuations/ContinuationResult{T}.cs | 64 +++++++++++------ .../Continuations/ContinueWithResult{T,U}.cs | 2 +- .../Continuations/ContinueWithResult{T}.cs | 2 +- .../Promises/CatchResult{T,TException}.cs | 2 +- .../Promises/FinallyResult{T}.cs | 2 +- .../Promises/RebindResult{T,U}.cs | 2 +- .../Continuations/Promises/ThenResult{T,U}.cs | 2 +- 8 files changed, 95 insertions(+), 50 deletions(-) diff --git a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs index c821769..ddad0dd 100644 --- a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs +++ b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs @@ -2,6 +2,7 @@ // 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; #if UNITYFX_SUPPORT_TAP using System.Threading.Tasks; @@ -47,6 +48,11 @@ internal static bool CanInvoke(IAsyncOperation op, AsyncContinuationOptions opti return (options & AsyncContinuationOptions.NotOnCanceled) == 0; } + internal static bool CanInvokeInline(IAsyncOperation op, SynchronizationContext syncContext) + { + return syncContext == null || syncContext == SynchronizationContext.Current; + } + internal static void InvokeDelegate(IAsyncOperation op, object continuation) { switch (continuation) @@ -115,20 +121,33 @@ internal static void InvokeTaskContinuation(IAsyncOperation op, TaskComple public void Invoke(IAsyncOperation op) { + ////if (CanInvokeInline(op, _syncContext)) + ////{ + //// InvokeDelegate(op, _continuation); + ////} + ////else + ////{ + //// if (_syncContext != null) + //// { + //// InvokeOnSyncContext(op, _syncContext); + //// } + //// else if (SynchronizationContext.Current != null) + //// { + //// InvokeOnSyncContext(op, SynchronizationContext.Current); + //// } + //// else + //// { + //// InvokeAsync(op); + //// } + ////} + if (_syncContext == null || _syncContext == SynchronizationContext.Current) { - if (_runAsynchronously) - { - InvokeAsync(op, SynchronizationContext.Current); - } - else - { - InvokeDelegate(op, _continuation); - } + InvokeDelegate(op, _continuation); } else { - InvokeAsync(op, _syncContext); + InvokeOnSyncContext(op, _syncContext); } } @@ -155,28 +174,30 @@ public override int GetHashCode() #region implementation - private void InvokeAsync(IAsyncOperation op, SynchronizationContext syncContext) + private void InvokeOnSyncContext(IAsyncOperation op, SynchronizationContext syncContext) { + Debug.Assert(_syncContext != null); + _op = op; - if (syncContext != null) + if (_postCallback == null) { - if (_postCallback == null) - { - _postCallback = PostCallback; - } - - _syncContext.Post(_postCallback, this); + _postCallback = PostCallback; } - else - { - if (_waitCallback == null) - { - _waitCallback = PostCallback; - } - ThreadPool.QueueUserWorkItem(_waitCallback, this); + syncContext.Post(_postCallback, this); + } + + private void InvokeAsync(IAsyncOperation op) + { + _op = op; + + if (_waitCallback == null) + { + _waitCallback = PostCallback; } + + ThreadPool.QueueUserWorkItem(_waitCallback, this); } private static void PostCallback(object args) diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs index 40a1049..fd3970c 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs @@ -11,6 +11,7 @@ internal abstract class ContinuationResult : AsyncResult { #region data + private static WaitCallback _waitCallback; private static SendOrPostCallback _postCallback; private readonly SynchronizationContext _syncContext; @@ -46,7 +47,7 @@ protected void InvokeOnSyncContext(IAsyncOperation op, bool completedSynchronous { try { - InvokeUnsafe(op, completedSynchronously); + InvokeInline(op, completedSynchronously); } catch (Exception e) { @@ -55,28 +56,11 @@ protected void InvokeOnSyncContext(IAsyncOperation op, bool completedSynchronous } else { - if (_postCallback == null) - { - _postCallback = args => - { - var c = args as ContinuationResult; - - try - { - c.InvokeUnsafe(c._op, false); - } - catch (Exception e) - { - c.TrySetException(e, false); - } - }; - } - - _syncContext.Post(_postCallback, this); + InvokeOnSyncContext(op, _syncContext); } } - protected abstract void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously); + protected abstract void InvokeInline(IAsyncOperation op, bool completedSynchronously); #endregion @@ -91,5 +75,45 @@ protected override void OnCancel() } #endregion + + #region implementation + + private void InvokeOnSyncContext(IAsyncOperation op, SynchronizationContext syncContext) + { + Debug.Assert(_syncContext != null); + + if (_postCallback == null) + { + _postCallback = PostCallback; + } + + syncContext.Post(_postCallback, this); + } + + private void InvokeAsync(IAsyncOperation op) + { + if (_waitCallback == null) + { + _waitCallback = PostCallback; + } + + ThreadPool.QueueUserWorkItem(_waitCallback, this); + } + + private static void PostCallback(object args) + { + var c = args as ContinuationResult; + + try + { + c.InvokeInline(c._op, false); + } + catch (Exception e) + { + c.TrySetException(e, false); + } + } + + #endregion } } diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs index 88b11a7..b19e656 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs @@ -35,7 +35,7 @@ internal ContinueWithResult(IAsyncOperation op, AsyncContinuationOptions options #region ContinuationResult - protected override void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously) + protected override void InvokeInline(IAsyncOperation op, bool completedSynchronously) { var result = default(U); diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs index 1b90e7e..6b8e183 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs @@ -35,7 +35,7 @@ internal ContinueWithResult(IAsyncOperation op, AsyncContinuationOptions options #region ContinuationResult - protected override void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously) + protected override void InvokeInline(IAsyncOperation op, bool completedSynchronously) { var result = default(T); diff --git a/src/UnityFx.Async/Implementation/Continuations/Promises/CatchResult{T,TException}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/CatchResult{T,TException}.cs index 92b7666..c9e402b 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/CatchResult{T,TException}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/CatchResult{T,TException}.cs @@ -31,7 +31,7 @@ public CatchResult(IAsyncOperation op, Action errorCallback) #region ContinuationResult - protected override void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously) + protected override void InvokeInline(IAsyncOperation op, bool completedSynchronously) { _errorCallback.Invoke(op.Exception.InnerException as TException); TrySetCompleted(completedSynchronously); diff --git a/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs index 293cddb..5d2c50a 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs @@ -31,7 +31,7 @@ public FinallyResult(IAsyncOperation op, object action) #region ContinuationResult - protected override void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously) + protected override void InvokeInline(IAsyncOperation op, bool completedSynchronously) { switch (_continuation) { diff --git a/src/UnityFx.Async/Implementation/Continuations/Promises/RebindResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/RebindResult{T,U}.cs index 83df018..83d2e92 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/RebindResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/RebindResult{T,U}.cs @@ -32,7 +32,7 @@ public RebindResult(IAsyncOperation op, object action) #region ContinuationResult - protected override void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously) + protected override void InvokeInline(IAsyncOperation op, bool completedSynchronously) { switch (_continuation) { diff --git a/src/UnityFx.Async/Implementation/Continuations/Promises/ThenResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/ThenResult{T,U}.cs index 17f8d06..6a80d9b 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/ThenResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/ThenResult{T,U}.cs @@ -75,7 +75,7 @@ protected virtual void InvokeSuccessCallback(IAsyncOperation op, bool completedS #region ContinuationResult - protected sealed override void InvokeUnsafe(IAsyncOperation op, bool completedSynchronously) + protected sealed override void InvokeInline(IAsyncOperation op, bool completedSynchronously) { if (op.IsCompletedSuccessfully) { From 8e92cbc2b49cbf9d21f136bedfb9df5484e1f42a Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 20 Apr 2018 19:25:57 +0300 Subject: [PATCH 112/128] README update --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0693cf1..58d838c 100644 --- a/README.md +++ b/README.md @@ -159,6 +159,7 @@ In fact the only notable difference from synchronous implementation is usage of Reference the DLL and import the namespace: ```csharp using UnityFx.Async; +using UnityFx.Async.Promises; // For promises-specific stuff. ``` Create an operation instance like this: ```csharp From d1f51cbfe810dc56e8c495809a0c2e53fbe9738b Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 20 Apr 2018 21:07:10 +0300 Subject: [PATCH 113/128] Removed AsynCompletionSource methods with completedSynchronously argument --- .../Tests/CompletionSourceTests.cs | 62 ------ .../Api/Core/AsyncCompletionSource.cs | 188 +---------------- .../Core/AsyncCompletionSource{TResult}.cs | 191 +----------------- 3 files changed, 10 insertions(+), 431 deletions(-) diff --git a/src/UnityFx.Async.Tests/Tests/CompletionSourceTests.cs b/src/UnityFx.Async.Tests/Tests/CompletionSourceTests.cs index 11a98b3..3cf2ef9 100644 --- a/src/UnityFx.Async.Tests/Tests/CompletionSourceTests.cs +++ b/src/UnityFx.Async.Tests/Tests/CompletionSourceTests.cs @@ -74,21 +74,6 @@ public void TrySetCanceled_CallsOnCompleted() Assert.True(op.OnStatusChangedCalled); } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void TrySetCanceled_SetsCompletedSynchronously(bool completedSynchronously) - { - // Arrange - var op = new AsyncCompletionSource(); - - // Act - op.TrySetCanceled(completedSynchronously); - - // Assert - Assert.Equal(completedSynchronously, op.CompletedSynchronously); - } - [Fact] public void TrySetCanceled_FailsIfOperationIsCompleted() { @@ -206,22 +191,6 @@ public void TrySetException_CallsOnCompleted() Assert.True(op.OnStatusChangedCalled); } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void TrySetException_SetsCompletedSynchronously(bool completedSynchronously) - { - // Arrange - var e = new Exception(); - var op = new AsyncCompletionSource(); - - // Act - op.TrySetException(e, completedSynchronously); - - // Assert - Assert.Equal(completedSynchronously, op.CompletedSynchronously); - } - [Fact] public void TrySetException_FailsIfOperationIsCompleted() { @@ -325,21 +294,6 @@ public void TrySetCompleted_CallsOnCompleted() Assert.True(op.OnStatusChangedCalled); } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void TrySetCompleted_SetsCompletedSynchronously(bool completedSynchronously) - { - // Arrange - var op = new AsyncCompletionSource(); - - // Act - op.TrySetCompleted(completedSynchronously); - - // Assert - Assert.Equal(completedSynchronously, op.CompletedSynchronously); - } - [Fact] public void TrySetCompleted_FailsIfOperationIsCompleted() { @@ -434,22 +388,6 @@ public void TrySetResult_CallsOnCompleted() Assert.True(op.OnStatusChangedCalled); } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void TrySetResult_SetsCompletedSynchronously(bool completedSynchronously) - { - // Arrange - var result = new object(); - var op = new AsyncCompletionSource(); - - // Act - op.TrySetResult(result, completedSynchronously); - - // Assert - Assert.Equal(completedSynchronously, op.CompletedSynchronously); - } - [Fact] public void TrySetResult_FailsIfOperationIsCompleted() { diff --git a/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs b/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs index d286b14..09addc7 100644 --- a/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs +++ b/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs @@ -118,176 +118,6 @@ public void SetRunning() /// public new bool TrySetRunning() => base.TrySetRunning(); - /// - /// Transitions the operation into the state. - /// - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - public void SetCanceled() - { - if (!base.TrySetCanceled(false)) - { - throw new InvalidOperationException(); - } - } - - /// - /// Transitions the operation into the state. - /// - /// Value of the property. - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - public void SetCanceled(bool completedSynchronously) - { - if (!base.TrySetCanceled(completedSynchronously)) - { - throw new InvalidOperationException(); - } - } - - /// - /// Attempts to transition the operation into the state. - /// - /// Value of the property. - /// Thrown is the operation is disposed. - /// Returns if the attemp was successfull; otherwise. - /// - /// - public new bool TrySetCanceled(bool completedSynchronously) => base.TrySetCanceled(completedSynchronously); - - /// - /// Transitions the operation into the state. - /// - /// An exception that caused the operation to end prematurely. - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - public void SetException(Exception exception) - { - if (!base.TrySetException(exception, false)) - { - throw new InvalidOperationException(); - } - } - - /// - /// Transitions the operation into the state. - /// - /// An exception that caused the operation to end prematurely. - /// Value of the property. - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - public void SetException(Exception exception, bool completedSynchronously) - { - if (!base.TrySetException(exception, completedSynchronously)) - { - throw new InvalidOperationException(); - } - } - - /// - /// Attempts to transition the operation into the state. - /// - /// An exception that caused the operation to end prematurely. - /// Value of the property. - /// Thrown is the operation is disposed. - /// Returns if the attemp was successfull; otherwise. - /// - /// - public new bool TrySetException(Exception exception, bool completedSynchronously) => base.TrySetException(exception, completedSynchronously); - - /// - /// Transitions the operation into the state. - /// - /// Exceptions that caused the operation to end prematurely. - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - public void SetExceptions(IEnumerable exceptions) - { - if (!base.TrySetExceptions(exceptions, false)) - { - throw new InvalidOperationException(); - } - } - - /// - /// Transitions the operation into the state. - /// - /// Exceptions that caused the operation to end prematurely. - /// Value of the property. - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - public void SetExceptions(IEnumerable exceptions, bool completedSynchronously) - { - if (!base.TrySetExceptions(exceptions, completedSynchronously)) - { - throw new InvalidOperationException(); - } - } - - /// - /// Attempts to transition the operation into the state. - /// - /// Exceptions that caused the operation to end prematurely. - /// Value of the property. - /// Thrown is the operation is disposed. - /// Returns if the attemp was successfull; otherwise. - /// - /// - public new bool TrySetExceptions(IEnumerable exceptions, bool completedSynchronously) => base.TrySetExceptions(exceptions, completedSynchronously); - - /// - /// Transitions the operation into the state. - /// - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - public void SetCompleted() - { - if (!base.TrySetCompleted(false)) - { - throw new InvalidOperationException(); - } - } - - /// - /// Transitions the operation into the state. - /// - /// Value of the property. - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - public void SetCompleted(bool completedSynchronously) - { - if (!base.TrySetCompleted(completedSynchronously)) - { - throw new InvalidOperationException(); - } - } - - /// - /// Attempts to transition the operation into the state. - /// - /// Value of the property. - /// Thrown is the operation is disposed. - /// Returns if the attemp was successfull; otherwise. - /// - /// - public new bool TrySetCompleted(bool completedSynchronously) => base.TrySetCompleted(completedSynchronously); - #endregion #region AsyncResult @@ -295,7 +125,7 @@ public void SetCompleted(bool completedSynchronously) /// protected override void OnCancel() { - base.TrySetCanceled(false); + TrySetCanceled(false); } #endregion @@ -306,24 +136,16 @@ protected override void OnCancel() public IAsyncOperation Operation => this; /// - /// - /// - public bool TrySetCanceled() => base.TrySetCanceled(false); + public bool TrySetCanceled() => TrySetCanceled(false); /// - /// - /// - public bool TrySetCompleted() => base.TrySetCompleted(false); + public bool TrySetCompleted() => TrySetCompleted(false); /// - /// - /// - public bool TrySetException(Exception exception) => base.TrySetException(exception, false); + public bool TrySetException(Exception exception) => TrySetException(exception, false); /// - /// - /// - public bool TrySetExceptions(IEnumerable exceptions) => base.TrySetExceptions(exceptions, false); + public bool TrySetExceptions(IEnumerable exceptions) => TrySetExceptions(exceptions, false); #endregion } diff --git a/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs b/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs index 8a5e87f..a16ad2a 100644 --- a/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs @@ -119,179 +119,6 @@ public void SetRunning() /// public new bool TrySetRunning() => base.TrySetRunning(); - /// - /// Transitions the operation into the state. - /// - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - public void SetCanceled() - { - if (!base.TrySetCanceled(false)) - { - throw new InvalidOperationException(); - } - } - - /// - /// Transitions the operation into the state. - /// - /// Value of the property. - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - public void SetCanceled(bool completedSynchronously) - { - if (!base.TrySetCanceled(completedSynchronously)) - { - throw new InvalidOperationException(); - } - } - - /// - /// Attempts to transition the operation into the state. - /// - /// Value of the property. - /// Thrown is the operation is disposed. - /// Returns if the attemp was successfull; otherwise. - /// - /// - public new bool TrySetCanceled(bool completedSynchronously) => base.TrySetCanceled(completedSynchronously); - - /// - /// Transitions the operation into the state. - /// - /// An exception that caused the operation to end prematurely. - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - public void SetException(Exception exception) - { - if (!base.TrySetException(exception, false)) - { - throw new InvalidOperationException(); - } - } - - /// - /// Transitions the operation into the state. - /// - /// An exception that caused the operation to end prematurely. - /// Value of the property. - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - public void SetException(Exception exception, bool completedSynchronously) - { - if (!base.TrySetException(exception, completedSynchronously)) - { - throw new InvalidOperationException(); - } - } - - /// - /// Attempts to transition the operation into the state. - /// - /// An exception that caused the operation to end prematurely. - /// Value of the property. - /// Thrown is the operation is disposed. - /// Returns if the attemp was successfull; otherwise. - /// - /// - public new bool TrySetException(Exception exception, bool completedSynchronously) => base.TrySetException(exception, completedSynchronously); - - /// - /// Transitions the operation into the state. - /// - /// Exceptions that caused the operation to end prematurely. - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - public void SetExceptions(IEnumerable exceptions) - { - if (!base.TrySetExceptions(exceptions, false)) - { - throw new InvalidOperationException(); - } - } - - /// - /// Transitions the operation into the state. - /// - /// Exceptions that caused the operation to end prematurely. - /// Value of the property. - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - public void SetExceptions(IEnumerable exceptions, bool completedSynchronously) - { - if (!base.TrySetExceptions(exceptions, completedSynchronously)) - { - throw new InvalidOperationException(); - } - } - - /// - /// Attempts to transition the operation into the state. - /// - /// Exceptions that caused the operation to end prematurely. - /// Value of the property. - /// Thrown is the operation is disposed. - /// Returns if the attemp was successfull; otherwise. - /// - /// - public new bool TrySetExceptions(IEnumerable exceptions, bool completedSynchronously) => base.TrySetExceptions(exceptions, completedSynchronously); - - /// - /// Transitions the operation into the state. - /// - /// The operation result. - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - public void SetResult(TResult result) - { - if (!base.TrySetResult(result, false)) - { - throw new InvalidOperationException(); - } - } - - /// - /// Transitions the operation into the state. - /// - /// The operation result. - /// Value of the property. - /// Thrown if the transition fails. - /// Thrown is the operation is disposed. - /// - /// - public void SetResult(TResult result, bool completedSynchronously) - { - if (!base.TrySetResult(result, completedSynchronously)) - { - throw new InvalidOperationException(); - } - } - - /// - /// Attempts to transition the operation into the state. - /// - /// The operation result. - /// Value of the property. - /// Thrown is the operation is disposed. - /// Returns if the attemp was successfull; otherwise. - /// - /// - public new bool TrySetResult(TResult result, bool completedSynchronously) => base.TrySetResult(result, completedSynchronously); - #endregion #region AsyncResult @@ -299,7 +126,7 @@ public void SetResult(TResult result, bool completedSynchronously) /// protected override void OnCancel() { - base.TrySetCanceled(false); + TrySetCanceled(false); } #endregion @@ -310,24 +137,16 @@ protected override void OnCancel() public IAsyncOperation Operation => this; /// - /// - /// - public bool TrySetCanceled() => base.TrySetCanceled(false); + public bool TrySetCanceled() => TrySetCanceled(false); /// - /// - /// - public bool TrySetException(Exception exception) => base.TrySetException(exception, false); + public bool TrySetException(Exception exception) => TrySetException(exception, false); /// - /// - /// - public bool TrySetExceptions(IEnumerable exceptions) => base.TrySetExceptions(exceptions, false); + public bool TrySetExceptions(IEnumerable exceptions) => TrySetExceptions(exceptions, false); /// - /// - /// - public bool TrySetResult(TResult result) => base.TrySetResult(result, false); + public bool TrySetResult(TResult result) => TrySetResult(result, false); #endregion } From fbc9fe9dc2ee3ee51f6023367f8c80625eb9072d Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 20 Apr 2018 21:42:16 +0300 Subject: [PATCH 114/128] Made AddCompletionCallback/AddContinuation members --- .../Api/Core/AsyncResult.Events.cs | 466 ++++++++++++++++++ src/UnityFx.Async/Api/Core/AsyncResult.cs | 389 +-------------- .../AsyncExtensions.Continuations.cs | 67 --- .../Api/Interfaces/IAsyncOperationEvents.cs | 48 +- 4 files changed, 511 insertions(+), 459 deletions(-) create mode 100644 src/UnityFx.Async/Api/Core/AsyncResult.Events.cs diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs new file mode 100644 index 0000000..b486465 --- /dev/null +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs @@ -0,0 +1,466 @@ +// 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.Diagnostics; +using System.Threading; + +namespace UnityFx.Async +{ + partial class AsyncResult + { + #region data + + private static readonly object _continuationCompletionSentinel = new object(); + +#if UNITYFX_NOT_THREAD_SAFE + + private object _continuation; + +#else + + private volatile object _continuation; + +#endif + + #endregion + + #region internals + + /// + /// Adds a completion callback for await implementation. + /// + internal void SetContinuationForAwait(Action continuation, SynchronizationContext syncContext) + { + ThrowIfDisposed(); + + if (!TryAddContinuationInternal(continuation, syncContext)) + { + continuation(); + } + } + + #endregion + + #region IAsyncOperationEvents + + /// + public event AsyncOperationCallback Completed + { + add + { + ThrowIfDisposed(); + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + +#if UNITYFX_NOT_THREAD_SAFE + + if (!TryAddContinuationInternal(value)) + { + value(this); + } + +#else + + if (!TryAddContinuationInternal(value, SynchronizationContext.Current)) + { + value(this); + } + +#endif + } + remove + { + ThrowIfDisposed(); + + if (value != null) + { + TryRemoveContinuationInternal(value); + } + } + } + + /// + public void AddCompletionCallback(AsyncOperationCallback action) + { + if (!TryAddCompletionCallback(action)) + { + action(this); + } + } + + /// + public bool TryAddCompletionCallback(AsyncOperationCallback action) + { + ThrowIfDisposed(); + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + +#if UNITYFX_NOT_THREAD_SAFE + + return TryAddContinuationInternal(action); + +#else + + return TryAddContinuationInternal(action, SynchronizationContext.Current); + +#endif + } + + /// + public void AddCompletionCallback(AsyncOperationCallback action, SynchronizationContext syncContext) + { + if (!TryAddCompletionCallback(action, syncContext)) + { + if (syncContext == null || syncContext.GetType() == typeof(SynchronizationContext) || syncContext == SynchronizationContext.Current) + { + action(this); + } + else + { + syncContext.Post(args => action(args as IAsyncOperation), this); + } + } + } + + /// + public bool TryAddCompletionCallback(AsyncOperationCallback action, SynchronizationContext syncContext) + { + ThrowIfDisposed(); + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return TryAddContinuationInternal(action, syncContext); + } + + /// + public bool RemoveCompletionCallback(AsyncOperationCallback action) + { + ThrowIfDisposed(); + + if (action != null) + { + return TryRemoveContinuationInternal(action); + } + + return false; + } + + /// + public void AddContinuation(IAsyncContinuation continuation) + { + if (!TryAddContinuation(continuation)) + { + continuation.Invoke(this); + } + } + + /// + public bool TryAddContinuation(IAsyncContinuation continuation) + { + ThrowIfDisposed(); + + if (continuation == null) + { + throw new ArgumentNullException(nameof(continuation)); + } + +#if UNITYFX_NOT_THREAD_SAFE + + return TryAddContinuationInternal(continuation); + +#else + + return TryAddContinuationInternal(continuation, null); + +#endif + } + + /// + public bool RemoveContinuation(IAsyncContinuation continuation) + { + ThrowIfDisposed(); + + if (continuation != null) + { + return TryRemoveContinuationInternal(continuation); + } + + return false; + } + + #endregion + + #region implementation + + private void InvokeContinuations() + { +#if UNITYFX_NOT_THREAD_SAFE + + var continuation = _continuation; + + if (continuation != null) + { + if (continuation is IEnumerable continuationList) + { + foreach (var item in continuationList) + { + InvokeContinuation(this, item); + } + } + else + { + InvokeContinuation(this, continuation); + } + } + +#else + + var continuation = Interlocked.Exchange(ref _continuation, _continuationCompletionSentinel); + + if (continuation != null) + { + if (continuation is IEnumerable continuationList) + { + lock (continuationList) + { + foreach (var item in continuationList) + { + InvokeContinuation(item); + } + } + } + else + { + InvokeContinuation(continuation); + } + } + +#endif + } + + /// + /// Attempts to register a continuation object. For internal use only. + /// + /// The continuation object to add. + /// A instance to execute continuation on. + /// Returns if the continuation was added; otherwise. + private bool TryAddContinuationInternal(object continuation, SynchronizationContext syncContext) + { + var runContinuationsAsynchronously = (_flags & _flagRunContinuationsAsynchronously) != 0; + + if ((syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) || runContinuationsAsynchronously) + { + continuation = new AsyncContinuation(syncContext, continuation, runContinuationsAsynchronously); + } + + return TryAddContinuationInternal(continuation); + } + +#if UNITYFX_NOT_THREAD_SAFE + + /// + /// Attempts to register a continuation object. For internal use only. + /// + /// The continuation object to add. + /// Returns if the continuation was added; otherwise. + private bool TryAddContinuationInternal(object valueToAdd) + { + var oldValue = _continuation; + + // Quick return if the operation is completed. + if (oldValue != _continuationCompletionSentinel) + { + // If no continuation is stored yet, try to store it as _continuation. + if (oldValue == null) + { + _continuation = valueToAdd; + } + + // Logic for the case where we were previously storing a single continuation. + if (oldValue is IList list) + { + list.Add(valueToAdd); + } + else + { + _continuation = new List() { oldValue, valueToAdd }; + } + + return true; + } + + return false; + } + + /// + /// Attempts to remove the specified continuation. For internal use only. + /// + /// The continuation object to remove. + /// Returns if the continuation was removed; otherwise. + private bool TryRemoveContinuationInternal(object valueToRemove) + { + var value = _continuation; + + if (value != _continuationCompletionSentinel) + { + if (value is IList list) + { + list.Remove(valueToRemove); + } + else + { + _continuation = null; + } + + return true; + } + + return false; + } + +#else + + /// + /// Attempts to register a continuation object. For internal use only. + /// + /// The continuation object to add. + /// Returns if the continuation was added; otherwise. + private bool TryAddContinuationInternal(object valueToAdd) + { + // NOTE: The code below is adapted from https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs. + var oldValue = _continuation; + + // Quick return if the operation is completed. + if (oldValue != _continuationCompletionSentinel) + { + // If no continuation is stored yet, try to store it as _continuation. + if (oldValue == null) + { + oldValue = Interlocked.CompareExchange(ref _continuation, valueToAdd, null); + + // Quick return if exchange succeeded. + if (oldValue == null) + { + return true; + } + } + + // Logic for the case where we were previously storing a single continuation. + if (oldValue != _continuationCompletionSentinel && !(oldValue is IList)) + { + var newList = new List() { oldValue }; + + Interlocked.CompareExchange(ref _continuation, newList, oldValue); + + // We might be racing against another thread converting the single into a list, + // or we might be racing against operation completion, so resample "list" below. + } + + // If list is null, it can only mean that _continuationCompletionSentinel has been exchanged + // into _continuation. Thus, the task has completed and we should return false from this method, + // as we will not be queuing up the continuation. + if (_continuation is IList list) + { + lock (list) + { + // It is possible for the operation to complete right after we snap the copy of the list. + // If so, then fall through and return false without queuing the continuation. + if (_continuation != _continuationCompletionSentinel) + { + list.Add(valueToAdd); + return true; + } + } + } + } + + return false; + } + + /// + /// Attempts to remove the specified continuation. For internal use only. + /// + /// The continuation object to remove. + /// Returns if the continuation was removed; otherwise. + private bool TryRemoveContinuationInternal(object valueToRemove) + { + // NOTE: The code below is adapted from https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs. + var value = _continuation; + + if (value != _continuationCompletionSentinel) + { + var list = value as IList; + + if (list == null) + { + // This is not a list. If we have a single object (the one we want to remove) we try to replace it with an empty list. + // Note we cannot go back to a null state, since it will mess up the TryAddContinuation logic. + if (Interlocked.CompareExchange(ref _continuation, new List(), valueToRemove) == valueToRemove) + { + return true; + } + else + { + // If we fail it means that either TryAddContinuation won the race condition and _continuation is now a List + // that contains the element we want to remove. Or it set the _continuationCompletionSentinel. + // So we should try to get a list one more time. + list = value as IList; + } + } + + // If list is null it means _continuationCompletionSentinel has been set already and there is nothing else to do. + if (list != null) + { + lock (list) + { + // There is a small chance that the operation completed since we took a local snapshot into + // list. In that case, just return; we don't want to be manipulating the continuation list as it is being processed. + if (_continuation != _continuationCompletionSentinel) + { + var index = list.IndexOf(valueToRemove); + + if (index != -1) + { + list.RemoveAt(index); + return true; + } + } + } + } + } + + return false; + } + +#endif + + /// + /// Invokes the specified continuation instance. + /// + private void InvokeContinuation(object continuation) + { + if (continuation is IAsyncContinuation c) + { + c.Invoke(this); + } + else + { + AsyncContinuation.InvokeDelegate(this, continuation); + } + } + + #endregion + } +} diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index fa0a62a..4a3698b 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -59,20 +59,16 @@ public partial class AsyncResult : IAsyncOperation, IAsyncCancellable, IEnumerat private const int _statusMask = 0x0000000f; private const int _resetMask = 0x70000000; - private static readonly object _continuationCompletionSentinel = new object(); private readonly object _asyncState; - private AggregateException _exception; #if UNITYFX_NOT_THREAD_SAFE - private object _continuation; private int _flags; #else private EventWaitHandle _waitHandle; - private volatile object _continuation; private volatile int _flags; #endif @@ -548,46 +544,13 @@ protected virtual void OnCompleted() { #if UNITYFX_NOT_THREAD_SAFE - var continuation = _continuation; - - if (continuation != null) - { - if (continuation is IEnumerable continuationList) - { - foreach (var item in continuationList) - { - InvokeContinuation(this, item); - } - } - else - { - InvokeContinuation(this, continuation); - } - } + InvokeContinuations(); #else try { - var continuation = Interlocked.Exchange(ref _continuation, _continuationCompletionSentinel); - - if (continuation != null) - { - if (continuation is IEnumerable continuationList) - { - lock (continuationList) - { - foreach (var item in continuationList) - { - InvokeContinuation(item); - } - } - } - else - { - InvokeContinuation(continuation); - } - } + InvokeContinuations(); } finally { @@ -883,19 +846,6 @@ internal bool TryCopyCompletionState(IAsyncOperation patternOp, bool completedSy return false; } - /// - /// Adds a completion callback for await implementation. - /// - internal void SetContinuationForAwait(Action continuation, SynchronizationContext syncContext) - { - ThrowIfDisposed(); - - if (!TryAddContinuationInternal(continuation, syncContext)) - { - continuation(); - } - } - /// /// Rethrows the specified . /// @@ -935,130 +885,6 @@ internal static bool TryThrowException(AggregateException e) #endregion - #region IAsyncOperationEvents - - /// - public event AsyncOperationCallback Completed - { - add - { - ThrowIfDisposed(); - - if (value == null) - { - throw new ArgumentNullException(nameof(value)); - } - -#if UNITYFX_NOT_THREAD_SAFE - - if (!TryAddContinuationInternal(value)) - { - value(this); - } - -#else - - if (!TryAddContinuationInternal(value, SynchronizationContext.Current)) - { - value(this); - } - -#endif - } - remove - { - ThrowIfDisposed(); - - if (value != null) - { - TryRemoveContinuationInternal(value); - } - } - } - - /// - public bool TryAddCompletionCallback(AsyncOperationCallback action) - { - ThrowIfDisposed(); - - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - -#if UNITYFX_NOT_THREAD_SAFE - - return TryAddContinuationInternal(action); - -#else - - return TryAddContinuationInternal(action, SynchronizationContext.Current); - -#endif - } - - /// - public bool TryAddCompletionCallback(AsyncOperationCallback action, SynchronizationContext syncContext) - { - ThrowIfDisposed(); - - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - return TryAddContinuationInternal(action, syncContext); - } - - /// - public bool RemoveCompletionCallback(AsyncOperationCallback action) - { - ThrowIfDisposed(); - - if (action != null) - { - return TryRemoveContinuationInternal(action); - } - - return false; - } - - /// - public bool TryAddContinuation(IAsyncContinuation continuation) - { - ThrowIfDisposed(); - - if (continuation == null) - { - throw new ArgumentNullException(nameof(continuation)); - } - -#if UNITYFX_NOT_THREAD_SAFE - - return TryAddContinuationInternal(continuation); - -#else - - return TryAddContinuationInternal(continuation, null); - -#endif - } - - /// - public bool RemoveContinuation(IAsyncContinuation continuation) - { - ThrowIfDisposed(); - - if (continuation != null) - { - return TryRemoveContinuationInternal(continuation); - } - - return false; - } - - #endregion - #region IAsyncResult /// @@ -1238,217 +1064,6 @@ private AsyncResult(int flags) } } - /// - /// Attempts to register a continuation object. For internal use only. - /// - /// The continuation object to add. - /// A instance to execute continuation on. - /// Returns if the continuation was added; otherwise. - private bool TryAddContinuationInternal(object continuation, SynchronizationContext syncContext) - { - var runContinuationsAsynchronously = (_flags & _flagRunContinuationsAsynchronously) != 0; - - if ((syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) || runContinuationsAsynchronously) - { - continuation = new AsyncContinuation(syncContext, continuation, runContinuationsAsynchronously); - } - - return TryAddContinuationInternal(continuation); - } - -#if UNITYFX_NOT_THREAD_SAFE - - /// - /// Attempts to register a continuation object. For internal use only. - /// - /// The continuation object to add. - /// Returns if the continuation was added; otherwise. - private bool TryAddContinuationInternal(object valueToAdd) - { - var oldValue = _continuation; - - // Quick return if the operation is completed. - if (oldValue != _continuationCompletionSentinel) - { - // If no continuation is stored yet, try to store it as _continuation. - if (oldValue == null) - { - _continuation = valueToAdd; - } - - // Logic for the case where we were previously storing a single continuation. - if (oldValue is IList list) - { - list.Add(valueToAdd); - } - else - { - _continuation = new List() { oldValue, valueToAdd }; - } - - return true; - } - - return false; - } - - /// - /// Attempts to remove the specified continuation. For internal use only. - /// - /// The continuation object to remove. - /// Returns if the continuation was removed; otherwise. - private bool TryRemoveContinuationInternal(object valueToRemove) - { - var value = _continuation; - - if (value != _continuationCompletionSentinel) - { - if (value is IList list) - { - list.Remove(valueToRemove); - } - else - { - _continuation = null; - } - - return true; - } - - return false; - } - -#else - - /// - /// Attempts to register a continuation object. For internal use only. - /// - /// The continuation object to add. - /// Returns if the continuation was added; otherwise. - private bool TryAddContinuationInternal(object valueToAdd) - { - // NOTE: The code below is adapted from https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs. - var oldValue = _continuation; - - // Quick return if the operation is completed. - if (oldValue != _continuationCompletionSentinel) - { - // If no continuation is stored yet, try to store it as _continuation. - if (oldValue == null) - { - oldValue = Interlocked.CompareExchange(ref _continuation, valueToAdd, null); - - // Quick return if exchange succeeded. - if (oldValue == null) - { - return true; - } - } - - // Logic for the case where we were previously storing a single continuation. - if (oldValue != _continuationCompletionSentinel && !(oldValue is IList)) - { - var newList = new List() { oldValue }; - - Interlocked.CompareExchange(ref _continuation, newList, oldValue); - - // We might be racing against another thread converting the single into a list, - // or we might be racing against operation completion, so resample "list" below. - } - - // If list is null, it can only mean that _continuationCompletionSentinel has been exchanged - // into _continuation. Thus, the task has completed and we should return false from this method, - // as we will not be queuing up the continuation. - if (_continuation is IList list) - { - lock (list) - { - // It is possible for the operation to complete right after we snap the copy of the list. - // If so, then fall through and return false without queuing the continuation. - if (_continuation != _continuationCompletionSentinel) - { - list.Add(valueToAdd); - return true; - } - } - } - } - - return false; - } - - /// - /// Attempts to remove the specified continuation. For internal use only. - /// - /// The continuation object to remove. - /// Returns if the continuation was removed; otherwise. - private bool TryRemoveContinuationInternal(object valueToRemove) - { - // NOTE: The code below is adapted from https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs. - var value = _continuation; - - if (value != _continuationCompletionSentinel) - { - var list = value as IList; - - if (list == null) - { - // This is not a list. If we have a single object (the one we want to remove) we try to replace it with an empty list. - // Note we cannot go back to a null state, since it will mess up the TryAddContinuation logic. - if (Interlocked.CompareExchange(ref _continuation, new List(), valueToRemove) == valueToRemove) - { - return true; - } - else - { - // If we fail it means that either TryAddContinuation won the race condition and _continuation is now a List - // that contains the element we want to remove. Or it set the _continuationCompletionSentinel. - // So we should try to get a list one more time. - list = value as IList; - } - } - - // If list is null it means _continuationCompletionSentinel has been set already and there is nothing else to do. - if (list != null) - { - lock (list) - { - // There is a small chance that the operation completed since we took a local snapshot into - // list. In that case, just return; we don't want to be manipulating the continuation list as it is being processed. - if (_continuation != _continuationCompletionSentinel) - { - var index = list.IndexOf(valueToRemove); - - if (index != -1) - { - list.RemoveAt(index); - return true; - } - } - } - } - } - - return false; - } - -#endif - - /// - /// Invokes the specified continuation instance. - /// - private void InvokeContinuation(object continuation) - { - if (continuation is IAsyncContinuation c) - { - c.Invoke(this); - } - else - { - AsyncContinuation.InvokeDelegate(this, continuation); - } - } - #endregion } } diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs index 6618308..1b3de35 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs @@ -8,73 +8,6 @@ namespace UnityFx.Async { partial class AsyncExtensions { - #region AddCompletionCallback/AddContinuation - - /// - /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked synchronously. - /// - /// The target operation. - /// The callback to be executed when the operation has completed. - /// Thrown if the is . - /// Thrown is the operation has been disposed. - /// - /// - public static void AddContinuation(this IAsyncOperation op, IAsyncContinuation continuation) - { - if (!op.TryAddContinuation(continuation)) - { - continuation.Invoke(op); - } - } - - /// - /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked synchronously. - /// - /// The target operation. - /// The callback to be executed when the operation has completed. - /// Thrown if the is . - /// Thrown is the operation has been disposed. - /// - /// - /// - public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action) - { - if (!op.TryAddCompletionCallback(action)) - { - action(op); - } - } - - /// - /// Adds a completion callback to be executed after the operation has completed. If the operation is completed the is invoked - /// on the specified. - /// - /// The target operation. - /// The callback to be executed when the operation has completed. - /// If not method attempts to marshal the continuation to the synchronization context. - /// Otherwise the callback is invoked on a thread that initiated the operation completion. - /// - /// Thrown if the is . - /// Thrown is the operation has been disposed. - /// - /// - public static void AddCompletionCallback(this IAsyncOperation op, AsyncOperationCallback action, SynchronizationContext syncContext) - { - if (!op.TryAddCompletionCallback(action, syncContext)) - { - if (syncContext == null || syncContext.GetType() == typeof(SynchronizationContext) || syncContext == SynchronizationContext.Current) - { - action(op); - } - else - { - syncContext.Post(args => action(args as IAsyncOperation), op); - } - } - } - - #endregion - #region ContinueWith /// diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs index 597cccd..6764fcd 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs @@ -34,18 +34,43 @@ public interface IAsyncOperationEvents /// event AsyncOperationCallback Completed; + /// + /// Adds a completion callback to be executed after the operation has completed. If the operation is completed is invoked synchronously. + /// + /// The callback to be executed when the operation has completed. + /// Thrown if is . + /// Thrown is the operation has been disposed. + /// + /// + void AddCompletionCallback(AsyncOperationCallback action); + /// /// Attempts to add a completion callback to be executed after the operation has finished. If the operation is already completed /// the method does nothing and just returns . /// /// The callback to be executed when the operation has completed. /// Returns if the callback was added; otherwise (the operation is completed). - /// Thrown if the is . + /// Thrown if is . /// Thrown is the operation has been disposed. + /// /// /// bool TryAddCompletionCallback(AsyncOperationCallback action); + /// + /// Adds a completion callback to be executed after the operation has completed. If the operation is completed is invoked + /// on the specified. + /// + /// The callback to be executed when the operation has completed. + /// If not method attempts to marshal the continuation to the synchronization context. + /// Otherwise the callback is invoked on a thread that initiated the operation completion. + /// + /// Thrown if or is . + /// Thrown is the operation has been disposed. + /// + /// + void AddCompletionCallback(AsyncOperationCallback action, SynchronizationContext syncContext); + /// /// Attempts to add a completion callback to be executed after the operation has finished. If the operation is already completed /// the method does nothing and just returns . @@ -55,8 +80,9 @@ public interface IAsyncOperationEvents /// Otherwise the callback is invoked on a thread that initiated the operation completion. /// /// Returns if the callback was added; otherwise (the operation is completed). - /// Thrown if the is . + /// Thrown if or is . /// Thrown is the operation has been disposed. + /// /// /// bool TryAddCompletionCallback(AsyncOperationCallback action, SynchronizationContext syncContext); @@ -65,19 +91,31 @@ public interface IAsyncOperationEvents /// Removes an existing completion callback. /// /// The callback to remove. Can be . - /// Returns if the was removed; otherwise. + /// Returns if was removed; otherwise. /// Thrown is the operation has been disposed. + /// /// bool RemoveCompletionCallback(AsyncOperationCallback action); + /// + /// Adds a completion callback to be executed after the operation has completed. If the operation is completed is invoked synchronously. + /// + /// The callback to be executed when the operation has completed. + /// Thrown if is . + /// Thrown is the operation has been disposed. + /// + /// + void AddContinuation(IAsyncContinuation continuation); + /// /// Attempts to add a completion callback to be executed after the operation has finished. If the operation is already completed /// the method does nothing and just returns . /// /// The cotinuation to be executed when the operation has completed. /// Returns if the callback was added; otherwise (the operation is completed). - /// Thrown if the is . + /// Thrown if is . /// Thrown is the operation has been disposed. + /// /// bool TryAddContinuation(IAsyncContinuation continuation); @@ -85,7 +123,7 @@ public interface IAsyncOperationEvents /// Removes an existing completion callback. /// /// The continuation to remove. Can be . - /// Returns if the was removed; otherwise. + /// Returns if was removed; otherwise. /// Thrown is the operation has been disposed. /// bool RemoveContinuation(IAsyncContinuation continuation); From f1e00f7321819d55337adb2f0bf466831cc4b78a Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 20 Apr 2018 23:31:49 +0300 Subject: [PATCH 115/128] Continuations refactoring to support RunContinuationsAsynchronously option --- .../Api/Core/AsyncResult.Events.cs | 168 +++++++++++------- .../Api/Interfaces/IAsyncOperationEvents.cs | 4 +- .../Continuations/AsyncContinuation.cs | 107 +++++------ 3 files changed, 155 insertions(+), 124 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs index b486465..e72df91 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs @@ -69,7 +69,7 @@ public event AsyncOperationCallback Completed if (!TryAddContinuationInternal(value, SynchronizationContext.Current)) { - value(this); + InvokeContinuation(value, SynchronizationContext.Current); } #endif @@ -90,7 +90,7 @@ public void AddCompletionCallback(AsyncOperationCallback action) { if (!TryAddCompletionCallback(action)) { - action(this); + InvokeContinuation(action, SynchronizationContext.Current); } } @@ -120,14 +120,7 @@ public void AddCompletionCallback(AsyncOperationCallback action, Synchronization { if (!TryAddCompletionCallback(action, syncContext)) { - if (syncContext == null || syncContext.GetType() == typeof(SynchronizationContext) || syncContext == SynchronizationContext.Current) - { - action(this); - } - else - { - syncContext.Post(args => action(args as IAsyncOperation), this); - } + InvokeContinuation(action, syncContext); } } @@ -162,7 +155,7 @@ public void AddContinuation(IAsyncContinuation continuation) { if (!TryAddContinuation(continuation)) { - continuation.Invoke(this); + InvokeContinuation(continuation, SynchronizationContext.Current); } } @@ -204,52 +197,6 @@ public bool RemoveContinuation(IAsyncContinuation continuation) #region implementation - private void InvokeContinuations() - { -#if UNITYFX_NOT_THREAD_SAFE - - var continuation = _continuation; - - if (continuation != null) - { - if (continuation is IEnumerable continuationList) - { - foreach (var item in continuationList) - { - InvokeContinuation(this, item); - } - } - else - { - InvokeContinuation(this, continuation); - } - } - -#else - - var continuation = Interlocked.Exchange(ref _continuation, _continuationCompletionSentinel); - - if (continuation != null) - { - if (continuation is IEnumerable continuationList) - { - lock (continuationList) - { - foreach (var item in continuationList) - { - InvokeContinuation(item); - } - } - } - else - { - InvokeContinuation(continuation); - } - } - -#endif - } - /// /// Attempts to register a continuation object. For internal use only. /// @@ -262,7 +209,7 @@ private bool TryAddContinuationInternal(object continuation, SynchronizationCont if ((syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) || runContinuationsAsynchronously) { - continuation = new AsyncContinuation(syncContext, continuation, runContinuationsAsynchronously); + continuation = new AsyncContinuation(this, syncContext, continuation); } return TryAddContinuationInternal(continuation); @@ -444,23 +391,116 @@ private bool TryRemoveContinuationInternal(object valueToRemove) return false; } + private void InvokeContinuations() + { +#if UNITYFX_NOT_THREAD_SAFE + + var continuation = _continuation; + + if (continuation != null) + { + if (continuation is IEnumerable continuationList) + { + foreach (var item in continuationList) + { + InvokeContinuationInline(this, item); + } + } + else + { + InvokeContinuationInline(this, continuation); + } + } + +#else + + var continuation = Interlocked.Exchange(ref _continuation, _continuationCompletionSentinel); + + if (continuation != null) + { + if (continuation is IEnumerable continuationList) + { + lock (continuationList) + { + foreach (var item in continuationList) + { + InvokeContinuation(item); + } + } + } + else + { + InvokeContinuation(continuation); + } + } + #endif + } - /// - /// Invokes the specified continuation instance. - /// private void InvokeContinuation(object continuation) { - if (continuation is IAsyncContinuation c) + var runContinuationsAsynchronously = (_flags & _flagRunContinuationsAsynchronously) != 0; + + if (runContinuationsAsynchronously) + { + if (continuation is AsyncContinuation c) + { + // NOTE: This is more effective than InvokeContinuationAsync(). + c.InvokeAsync(); + } + else + { + InvokeContinuationAsync(continuation, SynchronizationContext.Current); + } + } + else + { + if (continuation is AsyncContinuation c) + { + c.Invoke(); + } + else + { + InvokeContinuationInline(continuation); + } + } + } + + private void InvokeContinuation(object continuation, SynchronizationContext syncContext) + { + if ((_flags & _flagRunContinuationsAsynchronously) != 0) + { + InvokeContinuationAsync(continuation, syncContext); + } + else if (syncContext == null || syncContext == SynchronizationContext.Current) + { + InvokeContinuationInline(continuation); + } + else + { + syncContext.Post(args => InvokeContinuationInline(args), continuation); + } + } + + private void InvokeContinuationAsync(object continuation, SynchronizationContext syncContext) + { + if (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) { - c.Invoke(this); + syncContext.Post(args => InvokeContinuationInline(args), continuation); } else { - AsyncContinuation.InvokeDelegate(this, continuation); + ThreadPool.QueueUserWorkItem(args => InvokeContinuationInline(args), continuation); } } +#endif + + private void InvokeContinuationInline(object continuation) + { + AsyncContinuation.InvokeInline(this, continuation); + } + #endregion } } diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs index 6764fcd..a161c24 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs @@ -65,7 +65,7 @@ public interface IAsyncOperationEvents /// If not method attempts to marshal the continuation to the synchronization context. /// Otherwise the callback is invoked on a thread that initiated the operation completion. /// - /// Thrown if or is . + /// Thrown if is . /// Thrown is the operation has been disposed. /// /// @@ -80,7 +80,7 @@ public interface IAsyncOperationEvents /// Otherwise the callback is invoked on a thread that initiated the operation completion. /// /// Returns if the callback was added; otherwise (the operation is completed). - /// Thrown if or is . + /// Thrown if is . /// Thrown is the operation has been disposed. /// /// diff --git a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs index ddad0dd..9c6cdca 100644 --- a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs +++ b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs @@ -10,27 +10,64 @@ namespace UnityFx.Async { - internal class AsyncContinuation : IAsyncContinuation + internal class AsyncContinuation { #region data private static WaitCallback _waitCallback; private static SendOrPostCallback _postCallback; + private IAsyncOperation _op; private SynchronizationContext _syncContext; private object _continuation; - private bool _runAsynchronously; - private IAsyncOperation _op; #endregion #region interface - internal AsyncContinuation(SynchronizationContext syncContext, object continuation, bool runAsynchronously) + internal AsyncContinuation(IAsyncOperation op, SynchronizationContext syncContext, object continuation) { + _op = op; _syncContext = syncContext; _continuation = continuation; - _runAsynchronously = runAsynchronously; + } + + internal void InvokeAsync() + { + if (_syncContext != null) + { + InvokeOnSyncContext(_syncContext); + } + else + { + var syncContext = SynchronizationContext.Current; + + if (syncContext != null) + { + InvokeOnSyncContext(syncContext); + } + else + { + if (_waitCallback == null) + { + _waitCallback = PostCallback; + } + + ThreadPool.QueueUserWorkItem(_waitCallback, this); + } + } + } + + internal void Invoke() + { + if (_syncContext == null || _syncContext == SynchronizationContext.Current) + { + InvokeInline(_op, _continuation); + } + else + { + InvokeOnSyncContext(_syncContext); + } } internal static bool CanInvoke(IAsyncOperation op, AsyncContinuationOptions options) @@ -53,10 +90,14 @@ internal static bool CanInvokeInline(IAsyncOperation op, SynchronizationContext return syncContext == null || syncContext == SynchronizationContext.Current; } - internal static void InvokeDelegate(IAsyncOperation op, object continuation) + internal static void InvokeInline(IAsyncOperation op, object continuation) { switch (continuation) { + case IAsyncContinuation c: + c.Invoke(op); + break; + case AsyncOperationCallback aoc: aoc.Invoke(op); break; @@ -117,42 +158,6 @@ internal static void InvokeTaskContinuation(IAsyncOperation op, TaskComple #endregion - #region IAsyncContinuation - - public void Invoke(IAsyncOperation op) - { - ////if (CanInvokeInline(op, _syncContext)) - ////{ - //// InvokeDelegate(op, _continuation); - ////} - ////else - ////{ - //// if (_syncContext != null) - //// { - //// InvokeOnSyncContext(op, _syncContext); - //// } - //// else if (SynchronizationContext.Current != null) - //// { - //// InvokeOnSyncContext(op, SynchronizationContext.Current); - //// } - //// else - //// { - //// InvokeAsync(op); - //// } - ////} - - if (_syncContext == null || _syncContext == SynchronizationContext.Current) - { - InvokeDelegate(op, _continuation); - } - else - { - InvokeOnSyncContext(op, _syncContext); - } - } - - #endregion - #region Object public override bool Equals(object obj) @@ -174,12 +179,10 @@ public override int GetHashCode() #region implementation - private void InvokeOnSyncContext(IAsyncOperation op, SynchronizationContext syncContext) + private void InvokeOnSyncContext(SynchronizationContext syncContext) { Debug.Assert(_syncContext != null); - _op = op; - if (_postCallback == null) { _postCallback = PostCallback; @@ -188,22 +191,10 @@ private void InvokeOnSyncContext(IAsyncOperation op, SynchronizationContext sync syncContext.Post(_postCallback, this); } - private void InvokeAsync(IAsyncOperation op) - { - _op = op; - - if (_waitCallback == null) - { - _waitCallback = PostCallback; - } - - ThreadPool.QueueUserWorkItem(_waitCallback, this); - } - private static void PostCallback(object args) { var c = args as AsyncContinuation; - InvokeDelegate(c._op, c._continuation); + InvokeInline(c._op, c._continuation); } #endregion From 00d5626e41b3ce35a4ebcad3458523faea8d474c Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 20 Apr 2018 23:39:28 +0300 Subject: [PATCH 116/128] CHANGELOG update --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6547c22..e85c4c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,12 +21,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/); this proj ### Changed - Changed `ContinueWith` extension signatures to match corresponding `Task` methods. - Changed `IAsyncOperation.Exception` to always return an exception instance if completed with non-success. +- Changed `AddCompletionCallback`/`AddContinuation` to instance methods (instead of extensions). ### Fixed - Fixed exception not initialized properly for canceled operations sometimes. ### Removed - Removed `GetAwaiter`/`ConfigureAwait` instance methods from `AsyncResult` to avoid code duplication (extension methods should be used). +- Removed all `AsyncCompletionSource` methods having `completedSynchronously` argument. ----------------------- ## [0.8.2] - 2018-03-28 From 3c7ee73e026709b863008888fab5d3c080d84c5a Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 21 Apr 2018 15:45:17 +0300 Subject: [PATCH 117/128] Massive refactoring of continuations --- .../Tests/CompletionCallbackTests.cs | 10 +- .../Api/Core/AsyncResult.Events.cs | 57 ++++++--- .../AsyncExtensions.Continuations.cs | 8 +- .../Api/Interfaces/IAsyncContinuation.cs | 4 +- .../Api/Interfaces/IAsyncOperationEvents.cs | 32 +++++ .../Continuations/AsyncContinuation.cs | 8 +- .../Continuations/ContinuationResult{T}.cs | 119 ------------------ .../Continuations/ContinueWithResult{T,U}.cs | 111 +++++++++------- .../Continuations/ContinueWithResult{T}.cs | 95 -------------- .../Promises/CatchResult{T,TException}.cs | 50 ++++---- .../Promises/FinallyResult{T}.cs | 64 +++++----- .../Promises/RebindResult{T,U}.cs | 69 +++++----- .../Promises/ThenAllResult{T,U}.cs | 2 +- .../Promises/ThenAnyResult{T,U}.cs | 2 +- .../Continuations/Promises/ThenResult{T,U}.cs | 84 +++++-------- .../Continuations/UnwrapResult{T}.cs | 10 +- .../Specialized/RetryResult{T}.cs | 2 +- .../Specialized/WhenAllResult{T}.cs | 18 +-- .../Specialized/WhenAnyResult{T}.cs | 10 +- 19 files changed, 299 insertions(+), 456 deletions(-) delete mode 100644 src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs delete mode 100644 src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs diff --git a/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs b/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs index ac7a022..59c97dc 100644 --- a/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs +++ b/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs @@ -68,19 +68,17 @@ void CompletionCallback(IAsyncOperation o) void TestMethod() { - for (int i = 0; i < 1000; ++i) + for (var i = 0; i < 1000; ++i) { op.TryAddCompletionCallback(CompletionCallback); } } // Act - await Task.Run(new Action(TestMethod)); - await Task.Run(new Action(TestMethod)); - await Task.Run(new Action(TestMethod)); - op.SetCompleted(); + await Task.WhenAll(Task.Run(new Action(TestMethod)), Task.Run(new Action(TestMethod)), Task.Run(new Action(TestMethod))); // Assert + op.SetCompleted(); Assert.Equal(3000, counter); } @@ -96,7 +94,7 @@ public void TryAddContinuation_ExecutesWhenOperationCompletes() op.SetCompleted(); // Assert - continuation.Received(1).Invoke(op); + continuation.Received(1).Invoke(op, false); } } } diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs index e72df91..c5a1161 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs @@ -175,7 +175,37 @@ public bool TryAddContinuation(IAsyncContinuation continuation) #else - return TryAddContinuationInternal(continuation, null); + return TryAddContinuationInternal(continuation, SynchronizationContext.Current); + +#endif + } + + /// + public void AddContinuation(IAsyncContinuation continuation, SynchronizationContext syncContext) + { + if (!TryAddContinuation(continuation, syncContext)) + { + InvokeContinuation(continuation, syncContext); + } + } + + /// + public bool TryAddContinuation(IAsyncContinuation continuation, SynchronizationContext syncContext) + { + ThrowIfDisposed(); + + if (continuation == null) + { + throw new ArgumentNullException(nameof(continuation)); + } + +#if UNITYFX_NOT_THREAD_SAFE + + return TryAddContinuationInternal(continuation); + +#else + + return TryAddContinuationInternal(continuation, syncContext); #endif } @@ -403,12 +433,12 @@ private void InvokeContinuations() { foreach (var item in continuationList) { - InvokeContinuationInline(this, item); + AsyncContinuation.InvokeInline(this, item); } } else { - InvokeContinuationInline(this, continuation); + AsyncContinuation.InvokeInline(this, continuation); } } @@ -450,7 +480,7 @@ private void InvokeContinuation(object continuation) } else { - InvokeContinuationAsync(continuation, SynchronizationContext.Current); + InvokeContinuationAsync(continuation, SynchronizationContext.Current, false); } } else @@ -461,7 +491,7 @@ private void InvokeContinuation(object continuation) } else { - InvokeContinuationInline(continuation); + AsyncContinuation.InvokeInline(this, continuation, false); } } } @@ -470,37 +500,32 @@ private void InvokeContinuation(object continuation, SynchronizationContext sync { if ((_flags & _flagRunContinuationsAsynchronously) != 0) { - InvokeContinuationAsync(continuation, syncContext); + InvokeContinuationAsync(continuation, syncContext, true); } else if (syncContext == null || syncContext == SynchronizationContext.Current) { - InvokeContinuationInline(continuation); + AsyncContinuation.InvokeInline(this, continuation, true); } else { - syncContext.Post(args => InvokeContinuationInline(args), continuation); + syncContext.Post(args => AsyncContinuation.InvokeInline(this, args, true), continuation); } } - private void InvokeContinuationAsync(object continuation, SynchronizationContext syncContext) + private void InvokeContinuationAsync(object continuation, SynchronizationContext syncContext, bool inline) { if (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) { - syncContext.Post(args => InvokeContinuationInline(args), continuation); + syncContext.Post(args => AsyncContinuation.InvokeInline(this, args, inline), continuation); } else { - ThreadPool.QueueUserWorkItem(args => InvokeContinuationInline(args), continuation); + ThreadPool.QueueUserWorkItem(args => AsyncContinuation.InvokeInline(this, args, inline), continuation); } } #endif - private void InvokeContinuationInline(object continuation) - { - AsyncContinuation.InvokeInline(this, continuation); - } - #endregion } } diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs index 1b3de35..debf775 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Continuations.cs @@ -39,7 +39,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action(op, options, action, null); + return new ContinueWithResult(op, options, action, null); } /// @@ -73,7 +73,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperation op, Action(op, options, action, userState); + return new ContinueWithResult(op, options, action, userState); } /// @@ -105,7 +105,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperatio throw new ArgumentNullException(nameof(action)); } - return new ContinueWithResult(op, options, action, null); + return new ContinueWithResult(op, options, action, null); } /// @@ -139,7 +139,7 @@ public static IAsyncOperation ContinueWith(this IAsyncOperatio throw new ArgumentNullException(nameof(action)); } - return new ContinueWithResult(op, options, action, userState); + return new ContinueWithResult(op, options, action, userState); } /// diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs index 68db7a6..975efa5 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs @@ -66,6 +66,8 @@ public interface IAsyncContinuation /// Starts the continuation. /// /// The completed antecedent operation. - void Invoke(IAsyncOperation op); + /// Inline call flag: means the continuation was called without actually being + /// added to the operation continuation list (the operation was already completed at the time). + void Invoke(IAsyncOperation op, bool inline); } } diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs index a161c24..70cd86f 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs @@ -104,6 +104,7 @@ public interface IAsyncOperationEvents /// Thrown if is . /// Thrown is the operation has been disposed. /// + /// /// void AddContinuation(IAsyncContinuation continuation); @@ -116,9 +117,40 @@ public interface IAsyncOperationEvents /// Thrown if is . /// Thrown is the operation has been disposed. /// + /// /// bool TryAddContinuation(IAsyncContinuation continuation); + /// + /// Adds a completion callback to be executed after the operation has completed. If the operation is completed is invoked synchronously. + /// + /// The callback to be executed when the operation has completed. + /// If not method attempts to marshal the continuation to the synchronization context. + /// Otherwise the callback is invoked on a thread that initiated the operation completion. + /// + /// Thrown if is . + /// Thrown is the operation has been disposed. + /// + /// + /// + void AddContinuation(IAsyncContinuation continuation, SynchronizationContext syncContext); + + /// + /// Attempts to add a completion callback to be executed after the operation has finished. If the operation is already completed + /// the method does nothing and just returns . + /// + /// The cotinuation to be executed when the operation has completed. + /// If not method attempts to marshal the continuation to the synchronization context. + /// Otherwise the callback is invoked on a thread that initiated the operation completion. + /// + /// Returns if the callback was added; otherwise (the operation is completed). + /// Thrown if is . + /// Thrown is the operation has been disposed. + /// + /// + /// + bool TryAddContinuation(IAsyncContinuation continuation, SynchronizationContext syncContext); + /// /// Removes an existing completion callback. /// diff --git a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs index 9c6cdca..3b40915 100644 --- a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs +++ b/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs @@ -62,7 +62,7 @@ internal void Invoke() { if (_syncContext == null || _syncContext == SynchronizationContext.Current) { - InvokeInline(_op, _continuation); + InvokeInline(_op, _continuation, false); } else { @@ -90,12 +90,12 @@ internal static bool CanInvokeInline(IAsyncOperation op, SynchronizationContext return syncContext == null || syncContext == SynchronizationContext.Current; } - internal static void InvokeInline(IAsyncOperation op, object continuation) + internal static void InvokeInline(IAsyncOperation op, object continuation, bool inline) { switch (continuation) { case IAsyncContinuation c: - c.Invoke(op); + c.Invoke(op, inline); break; case AsyncOperationCallback aoc: @@ -194,7 +194,7 @@ private void InvokeOnSyncContext(SynchronizationContext syncContext) private static void PostCallback(object args) { var c = args as AsyncContinuation; - InvokeInline(c._op, c._continuation); + InvokeInline(c._op, c._continuation, false); } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs deleted file mode 100644 index fd3970c..0000000 --- a/src/UnityFx.Async/Implementation/Continuations/ContinuationResult{T}.cs +++ /dev/null @@ -1,119 +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.Diagnostics; -using System.Threading; - -namespace UnityFx.Async -{ - internal abstract class ContinuationResult : AsyncResult - { - #region data - - private static WaitCallback _waitCallback; - private static SendOrPostCallback _postCallback; - - private readonly SynchronizationContext _syncContext; - private readonly IAsyncOperation _op; - - #endregion - - #region interface - - protected ContinuationResult(IAsyncOperation op) - : base(AsyncOperationStatus.Running) - { - _syncContext = SynchronizationContext.Current; - _op = op; - } - - protected ContinuationResult(IAsyncOperation op, bool captureSynchronizationContext) - : base(AsyncOperationStatus.Running) - { - if (captureSynchronizationContext) - { - _syncContext = SynchronizationContext.Current; - } - - _op = op; - } - - protected void InvokeOnSyncContext(IAsyncOperation op, bool completedSynchronously) - { - Debug.Assert(op == _op); - - if (completedSynchronously || _syncContext == null || _syncContext == SynchronizationContext.Current) - { - try - { - InvokeInline(op, completedSynchronously); - } - catch (Exception e) - { - TrySetException(e, completedSynchronously); - } - } - else - { - InvokeOnSyncContext(op, _syncContext); - } - } - - protected abstract void InvokeInline(IAsyncOperation op, bool completedSynchronously); - - #endregion - - #region AsyncResult - - protected override void OnCancel() - { - if (_op is IAsyncCancellable c) - { - c.Cancel(); - } - } - - #endregion - - #region implementation - - private void InvokeOnSyncContext(IAsyncOperation op, SynchronizationContext syncContext) - { - Debug.Assert(_syncContext != null); - - if (_postCallback == null) - { - _postCallback = PostCallback; - } - - syncContext.Post(_postCallback, this); - } - - private void InvokeAsync(IAsyncOperation op) - { - if (_waitCallback == null) - { - _waitCallback = PostCallback; - } - - ThreadPool.QueueUserWorkItem(_waitCallback, this); - } - - private static void PostCallback(object args) - { - var c = args as ContinuationResult; - - try - { - c.InvokeInline(c._op, false); - } - catch (Exception e) - { - c.TrySetException(e, false); - } - } - - #endregion - } -} diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs index b19e656..dbcb4fe 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs @@ -2,91 +2,116 @@ // 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 ContinueWithResult : ContinuationResult, IAsyncContinuation + internal sealed class ContinueWithResult : AsyncResult, IAsyncContinuation { #region data + private readonly IAsyncOperation _op; private readonly AsyncContinuationOptions _options; private readonly object _continuation; private readonly object _userState; + private readonly SynchronizationContext _syncContext; #endregion #region interface internal ContinueWithResult(IAsyncOperation op, AsyncContinuationOptions options, object continuation, object userState) - : base(op, (options & AsyncContinuationOptions.ExecuteSynchronously) == 0) + : base(AsyncOperationStatus.Running) { + _op = op; _options = options; _continuation = continuation; _userState = userState; - // NOTE: Cannot move this to base class because this call might trigger _continuation (and it would be uninitialized in base ctor) - if (!op.TryAddContinuation(this)) + if ((options & AsyncContinuationOptions.ExecuteSynchronously) != 0) { - InvokeInternal(op, true); + op.AddContinuation(this, null); + } + else + { + op.AddContinuation(this); } } #endregion - #region ContinuationResult + #region AsyncResult - protected override void InvokeInline(IAsyncOperation op, bool completedSynchronously) + protected override void OnCancel() { - var result = default(U); - - switch (_continuation) + if (_op is IAsyncCancellable c) { - case Action> a: - a.Invoke(op as IAsyncOperation); - break; - - case Func, U> f: - result = f.Invoke(op as IAsyncOperation); - break; - - case Action, object> ao: - ao.Invoke(op as IAsyncOperation, _userState); - break; - - case Func, object, U> fo: - result = fo.Invoke(op as IAsyncOperation, _userState); - break; - - default: - TrySetCanceled(completedSynchronously); - return; + c.Cancel(); } - - TrySetResult(result, completedSynchronously); } #endregion #region IAsyncContinuation - public void Invoke(IAsyncOperation op) - { - InvokeInternal(op, false); - } - - #endregion - - #region implementation - - private void InvokeInternal(IAsyncOperation op, bool completedSynchronously) + public void Invoke(IAsyncOperation op, bool inline) { if (AsyncContinuation.CanInvoke(op, _options)) { - InvokeOnSyncContext(op, completedSynchronously); + try + { + var result = default(U); + + switch (_continuation) + { + case Action a: + a.Invoke(op); + break; + + case Action> a1: + a1.Invoke(op as IAsyncOperation); + break; + + case Func f: + result = f.Invoke(op); + break; + + case Func, U> f1: + result = f1.Invoke(op as IAsyncOperation); + break; + + case Action ao: + ao.Invoke(op, _userState); + break; + + case Action, object> ao1: + ao1.Invoke(op as IAsyncOperation, _userState); + break; + + case Func fo: + result = fo.Invoke(op, _userState); + break; + + case Func, object, U> fo1: + result = fo1.Invoke(op as IAsyncOperation, _userState); + break; + + default: + TrySetCanceled(inline); + return; + } + + TrySetResult(result, inline); + } + catch (Exception e) + { + TrySetException(e, inline); + } } else { - TrySetCanceled(completedSynchronously); + TrySetCanceled(inline); } } diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs deleted file mode 100644 index 6b8e183..0000000 --- a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T}.cs +++ /dev/null @@ -1,95 +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 -{ - internal sealed class ContinueWithResult : ContinuationResult, IAsyncContinuation - { - #region data - - private readonly AsyncContinuationOptions _options; - private readonly object _continuation; - private readonly object _userState; - - #endregion - - #region interface - - internal ContinueWithResult(IAsyncOperation op, AsyncContinuationOptions options, object continuation, object userState) - : base(op, (options & AsyncContinuationOptions.ExecuteSynchronously) == 0) - { - _options = options; - _continuation = continuation; - _userState = userState; - - // NOTE: Cannot move this to base class because this call might trigger _continuation (and it would be uninitialized in base ctor) - if (!op.TryAddContinuation(this)) - { - InvokeInternal(op, true); - } - } - - #endregion - - #region ContinuationResult - - protected override void InvokeInline(IAsyncOperation op, bool completedSynchronously) - { - var result = default(T); - - switch (_continuation) - { - case Action a: - a.Invoke(op); - break; - - case Func f: - result = f.Invoke(op); - break; - - case Action ao: - ao.Invoke(op, _userState); - break; - - case Func fo: - result = fo.Invoke(op, _userState); - break; - - default: - TrySetCanceled(completedSynchronously); - return; - } - - TrySetResult(result, completedSynchronously); - } - - #endregion - - #region IAsyncContinuation - - public void Invoke(IAsyncOperation op) - { - InvokeInternal(op, false); - } - - #endregion - - #region implementation - - private void InvokeInternal(IAsyncOperation op, bool completedSynchronously) - { - if (AsyncContinuation.CanInvoke(op, _options)) - { - InvokeOnSyncContext(op, completedSynchronously); - } - else - { - TrySetCanceled(completedSynchronously); - } - } - - #endregion - } -} diff --git a/src/UnityFx.Async/Implementation/Continuations/Promises/CatchResult{T,TException}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/CatchResult{T,TException}.cs index c9e402b..c2859d8 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/CatchResult{T,TException}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/CatchResult{T,TException}.cs @@ -5,10 +5,11 @@ namespace UnityFx.Async.Promises { - internal sealed class CatchResult : ContinuationResult, IAsyncContinuation where TException : Exception + internal sealed class CatchResult : AsyncResult, IAsyncContinuation where TException : Exception { #region data + private readonly IAsyncOperation _op; private readonly Action _errorCallback; #endregion @@ -16,56 +17,57 @@ internal sealed class CatchResult : ContinuationResult, IAsync #region interface public CatchResult(IAsyncOperation op, Action errorCallback) - : base(op) + : base(AsyncOperationStatus.Running) { + _op = op; _errorCallback = errorCallback; - // NOTE: Cannot move this to base class because this call might trigger virtual Invoke - if (!op.TryAddContinuation(this)) - { - InvokeInternal(op, true); - } + op.AddContinuation(this); } #endregion - #region ContinuationResult + #region AsyncResult - protected override void InvokeInline(IAsyncOperation op, bool completedSynchronously) + protected override void OnCancel() { - _errorCallback.Invoke(op.Exception.InnerException as TException); - TrySetCompleted(completedSynchronously); + if (_op is IAsyncCancellable c) + { + c.Cancel(); + } } #endregion #region IAsyncContinuation - public void Invoke(IAsyncOperation op) - { - InvokeInternal(op, false); - } - - #endregion - - #region implementation - - private void InvokeInternal(IAsyncOperation op, bool completedSynchronously) + public void Invoke(IAsyncOperation op, bool inline) { if (op.IsCompletedSuccessfully) { - TrySetCompleted(completedSynchronously); + TrySetCompleted(inline); } else if (!(op.Exception.InnerException is TException)) { - TrySetException(op.Exception, completedSynchronously); + TrySetException(op.Exception, inline); } else { - InvokeOnSyncContext(op, completedSynchronously); + try + { + _errorCallback.Invoke(op.Exception.InnerException as TException); + TrySetCompleted(inline); + } + catch (Exception e) + { + TrySetException(e, inline); + } } } #endregion + + #region implementation + #endregion } } diff --git a/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs index 5d2c50a..58641ad 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs @@ -5,10 +5,11 @@ namespace UnityFx.Async.Promises { - internal sealed class FinallyResult : ContinuationResult, IAsyncContinuation + internal sealed class FinallyResult : AsyncResult, IAsyncContinuation { #region data + private readonly IAsyncOperation _op; private readonly object _continuation; #endregion @@ -16,41 +17,23 @@ internal sealed class FinallyResult : ContinuationResult, IAsyncContinuati #region interface public FinallyResult(IAsyncOperation op, object action) - : base(op) + : base(AsyncOperationStatus.Running) { + _op = op; _continuation = action; - // NOTE: Cannot move this to base class because this call might trigger virtual Invoke - if (!op.TryAddContinuation(this)) - { - InvokeOnSyncContext(op, true); - } + op.AddContinuation(this); } #endregion - #region ContinuationResult + #region AsyncResult - protected override void InvokeInline(IAsyncOperation op, bool completedSynchronously) + protected override void OnCancel() { - switch (_continuation) + if (_op is IAsyncCancellable c) { - case Action a: - a.Invoke(); - TrySetCompleted(completedSynchronously); - break; - - case Func> f1: - f1().AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); - break; - - case Func f2: - f2().AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); - break; - - default: - TrySetCanceled(completedSynchronously); - break; + c.Cancel(); } } @@ -58,9 +41,34 @@ protected override void InvokeInline(IAsyncOperation op, bool completedSynchrono #region IAsyncContinuation - public void Invoke(IAsyncOperation op) + public void Invoke(IAsyncOperation op, bool inline) { - InvokeOnSyncContext(op, false); + try + { + switch (_continuation) + { + case Action a: + a.Invoke(); + TrySetCompleted(inline); + break; + + case Func> f1: + f1().AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); + break; + + case Func f2: + f2().AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); + break; + + default: + TrySetCanceled(inline); + break; + } + } + catch (Exception e) + { + TrySetException(e, inline); + } } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/Promises/RebindResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/RebindResult{T,U}.cs index 83d2e92..e5d6920 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/RebindResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/RebindResult{T,U}.cs @@ -6,10 +6,11 @@ namespace UnityFx.Async.Promises { - internal sealed class RebindResult : ContinuationResult, IAsyncContinuation + internal sealed class RebindResult : AsyncResult, IAsyncContinuation { #region data + private readonly IAsyncOperation _op; private readonly object _continuation; #endregion @@ -17,36 +18,23 @@ internal sealed class RebindResult : ContinuationResult, IAsyncContinua #region interface public RebindResult(IAsyncOperation op, object action) - : base(op) + : base(AsyncOperationStatus.Running) { + _op = op; _continuation = action; - // NOTE: Cannot move this to base class because this call might trigger virtual Invoke - if (!op.TryAddContinuation(this)) - { - InvokeInternal(op, true); - } + op.AddContinuation(this); } #endregion - #region ContinuationResult + #region AsyncResult - protected override void InvokeInline(IAsyncOperation op, bool completedSynchronously) + protected override void OnCancel() { - switch (_continuation) + if (_op is IAsyncCancellable c) { - case Func f1: - TrySetResult(f1(), completedSynchronously); - break; - - case Func f2: - TrySetResult(f2((op as IAsyncOperation).Result), completedSynchronously); - break; - - default: - TrySetCanceled(completedSynchronously); - break; + c.Cancel(); } } @@ -54,24 +42,35 @@ protected override void InvokeInline(IAsyncOperation op, bool completedSynchrono #region IAsyncContinuation - public void Invoke(IAsyncOperation op) - { - InvokeOnSyncContext(op, false); - } - - #endregion - - #region implementation - - private void InvokeInternal(IAsyncOperation op, bool completedSynchronously) + public void Invoke(IAsyncOperation op, bool inline) { - if (op.IsCompletedSuccessfully) + try { - InvokeOnSyncContext(op, completedSynchronously); + if (op.IsCompletedSuccessfully) + { + switch (_continuation) + { + case Func f1: + TrySetResult(f1(), inline); + break; + + case Func f2: + TrySetResult(f2((op as IAsyncOperation).Result), inline); + break; + + default: + TrySetCanceled(inline); + break; + } + } + else + { + TrySetException(op.Exception, inline); + } } - else + catch (Exception e) { - TrySetException(op.Exception, completedSynchronously); + TrySetException(e, inline); } } diff --git a/src/UnityFx.Async/Implementation/Continuations/Promises/ThenAllResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/ThenAllResult{T,U}.cs index a3b0f77..3397d44 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/ThenAllResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/ThenAllResult{T,U}.cs @@ -24,7 +24,7 @@ public ThenAllResult(IAsyncOperation op, object successCallback, Action : ContinuationResult, IAsyncContinuation + internal class ThenResult : AsyncResult, IAsyncContinuation { #region data + private readonly IAsyncOperation _op; private readonly object _successCallback; private readonly Action _errorCallback; @@ -19,16 +20,13 @@ internal class ThenResult : ContinuationResult, IAsyncContinuation #region interface public ThenResult(IAsyncOperation op, object successCallback, Action errorCallback) - : base(op) + : base(AsyncOperationStatus.Running) { + _op = op; _successCallback = successCallback; _errorCallback = errorCallback; - // NOTE: Cannot move this to base class because this call might trigger virtual Invoke - if (!op.TryAddContinuation(this)) - { - InvokeInternal(op, true); - } + op.AddContinuation(this); } protected virtual void InvokeSuccessCallback(IAsyncOperation op, bool completedSynchronously, object continuation) @@ -73,72 +71,52 @@ protected virtual void InvokeSuccessCallback(IAsyncOperation op, bool completedS #endregion - #region ContinuationResult - - protected sealed override void InvokeInline(IAsyncOperation op, bool completedSynchronously) - { - if (op.IsCompletedSuccessfully) - { - if (IsCancellationRequested) - { - TrySetCanceled(completedSynchronously); - } - else - { - InvokeSuccessCallback(op, completedSynchronously, _successCallback); - } - } - else - { - InvokeErrorCallback(op, completedSynchronously); - } - } - - #endregion - #region AsyncResult protected override void OnCancel() { - base.OnCancel(); - - if (_continuation is IAsyncCancellable c) + if (_op is IAsyncCancellable c) { c.Cancel(); } - } - #endregion - - #region IAsyncContinuation - - public void Invoke(IAsyncOperation op) - { - InvokeInternal(op, false); + if (_continuation is IAsyncCancellable c2) + { + c2.Cancel(); + } } #endregion - #region implementation + #region IAsyncContinuation - private void InvokeInternal(IAsyncOperation op, bool completedSynchronously) + public void Invoke(IAsyncOperation op, bool inline) { - if (op.IsCompletedSuccessfully || _errorCallback != null) + try { - InvokeOnSyncContext(op, completedSynchronously); + if (op.IsCompletedSuccessfully) + { + if (IsCancellationRequested) + { + TrySetCanceled(inline); + } + else + { + InvokeSuccessCallback(op, inline, _successCallback); + } + } + else + { + _errorCallback?.Invoke(op.Exception.InnerException); + TrySetException(op.Exception, inline); + } } - else + catch (Exception e) { - TrySetException(op.Exception, completedSynchronously); + TrySetException(e, inline); } } - private void InvokeErrorCallback(IAsyncOperation op, bool completedSynchronously) - { - _errorCallback?.Invoke(op.Exception.InnerException); - TrySetException(op.Exception, completedSynchronously); - } - #endregion } } diff --git a/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs index c2dfe73..eb6f7a1 100644 --- a/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs @@ -33,7 +33,7 @@ public UnwrapResult(IAsyncOperation outerOp) #region IAsyncContinuation - public void Invoke(IAsyncOperation op) + public void Invoke(IAsyncOperation op, bool inline) { if (_state == State.WaitingForOuterOperation) { @@ -44,21 +44,21 @@ public void Invoke(IAsyncOperation op) switch (op) { case IAsyncOperation> innerOp1: - ProcessInnerOperation(innerOp1.Result, false); + ProcessInnerOperation(innerOp1.Result, inline); break; case IAsyncOperation innerOp2: - ProcessInnerOperation(innerOp2.Result, false); + ProcessInnerOperation(innerOp2.Result, inline); break; default: - ProcessInnerOperation(null, false); + ProcessInnerOperation(null, inline); break; } } else { - TrySetException(op.Exception, false); + TrySetException(op.Exception, inline); } } else if (_state == State.WaitingForInnerOperation) diff --git a/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs index 0d0664d..a65daed 100644 --- a/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs @@ -68,7 +68,7 @@ protected override void OnCancel() #region IAsyncContinuation - public void Invoke(IAsyncOperation op) + public void Invoke(IAsyncOperation op, bool inline) { Debug.Assert(_op == op); Debug.Assert(_op.IsCompleted); diff --git a/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs index 06595c3..e20898a 100644 --- a/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs @@ -12,9 +12,7 @@ internal class WhenAllResult : AsyncResult, IAsyncContinuation #region data private readonly IAsyncOperation[] _ops; - private int _count; - private bool _completedSynchronously; #endregion @@ -25,17 +23,11 @@ public WhenAllResult(IAsyncOperation[] ops) { _ops = ops; _count = ops.Length; - _completedSynchronously = true; foreach (var op in ops) { - if (!op.TryAddContinuation(this)) - { - Invoke(op); - } + op.AddContinuation(this, null); } - - _completedSynchronously = false; } #endregion @@ -57,7 +49,7 @@ protected override void OnCancel() #region IAsyncContinuation - public void Invoke(IAsyncOperation asyncOp) + public void Invoke(IAsyncOperation asyncOp, bool inline) { if (IsCompleted) { @@ -86,15 +78,15 @@ public void Invoke(IAsyncOperation asyncOp) if (exceptions != null) { - TrySetExceptions(exceptions, _completedSynchronously); + TrySetExceptions(exceptions, false); } else if (canceledOp != null) { - TrySetCanceled(_completedSynchronously); + TrySetCanceled(false); } else if (typeof(T) == typeof(VoidResult)) { - TrySetCompleted(_completedSynchronously); + TrySetCompleted(false); } else { diff --git a/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs index e1ce406..e22e968 100644 --- a/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs @@ -22,11 +22,7 @@ public WhenAnyResult(T[] ops) foreach (var op in ops) { - if (!op.TryAddContinuation(this)) - { - TrySetResult(op, true); - break; - } + op.AddContinuation(this, null); } } @@ -49,9 +45,9 @@ protected override void OnCancel() #region IAsyncContinuation - public void Invoke(IAsyncOperation op) + public void Invoke(IAsyncOperation op, bool inline) { - TrySetResult((T)op, false); + TrySetResult((T)op, inline); } #endregion From a6fb5d0af81517b13639da588940ebb85ec548f9 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 21 Apr 2018 15:58:14 +0300 Subject: [PATCH 118/128] Added FromAction helpers --- .../Api/Core/AsyncResult.Helpers.cs | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs index da25d63..779f439 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs @@ -264,6 +264,56 @@ public static AsyncResult FromResult(T result, object asyncState) return new AsyncResult(result, asyncState); } + /// + /// Creates a completed that represents result of the specified. + /// + /// The delegate to execute. + /// Thrown if is . + /// A completed operation that represents result. + /// + public static AsyncResult FromAction(Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + try + { + action(); + return CompletedOperation; + } + catch (Exception e) + { + return new AsyncResult(e, null); + } + } + + /// + /// Creates a completed that represents result of the specified. + /// + /// The delegate to execute. + /// Thrown if is . + /// A completed operation that represents result. + /// + public static AsyncResult FromAction(Func action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + try + { + var result = action(); + return new AsyncResult(result, null); + } + catch (Exception e) + { + return new AsyncResult(e, null); + } + } + #if UNITYFX_SUPPORT_TAP /// @@ -344,6 +394,42 @@ public static AsyncResult FromTask(Task task) #if !NET35 + /// + /// Creates a completed that represents result of the specified. + /// + /// The delegate to execute. + /// A cancellation token to check before executing the . + /// Thrown if is . + /// A completed operation that represents result. + /// + public static AsyncResult FromAction(Action action, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return CanceledOperation; + } + + return FromAction(action); + } + + /// + /// Creates a completed that represents result of the specified. + /// + /// The delegate to execute. + /// A cancellation token to check before executing the . + /// Thrown if is . + /// A completed operation that represents result. + /// + public static AsyncResult FromAction(Func action, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return new AsyncResult(AsyncOperationStatus.Canceled); + } + + return FromAction(action); + } + /// /// Creates a instance that can be used to track the source observable. /// From 823b7d6efbdc5a90ffb9fb28bccb5b96699dcdb0 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 21 Apr 2018 16:50:31 +0300 Subject: [PATCH 119/128] Added AsyncCreationOptions --- .../Api/Core/AsyncCompletionSource.cs | 63 +++++++++++++++ .../Core/AsyncCompletionSource{TResult}.cs | 65 ++++++++++++++- .../Api/Core/AsyncContinuationOptions.cs | 58 ++++++++++++++ .../Api/Core/AsyncCreationOptions.cs | 25 ++++++ src/UnityFx.Async/Api/Core/AsyncResult.cs | 80 +++++++++++++++++-- .../Api/Core/AsyncResult{TResult}.cs | 53 ++++++++++++ .../Api/Interfaces/IAsyncContinuation.cs | 60 ++------------ .../Api/Interfaces/IAsyncOperationEvents.cs | 1 + 8 files changed, 347 insertions(+), 58 deletions(-) create mode 100644 src/UnityFx.Async/Api/Core/AsyncContinuationOptions.cs create mode 100644 src/UnityFx.Async/Api/Core/AsyncCreationOptions.cs diff --git a/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs b/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs index 09addc7..e351306 100644 --- a/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs +++ b/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs @@ -21,6 +21,15 @@ public AsyncCompletionSource() { } + /// + /// Initializes a new instance of the class. + /// + /// The used to customize the operation's behavior. + public AsyncCompletionSource(AsyncCreationOptions options) + : base(options) + { + } + /// /// Initializes a new instance of the class. /// @@ -30,6 +39,16 @@ public AsyncCompletionSource(object asyncState) { } + /// + /// Initializes a new instance of the class. + /// + /// User-defined data returned by . + /// The used to customize the operation's behavior. + public AsyncCompletionSource(object asyncState, AsyncCreationOptions options) + : base(default(AsyncCallback), asyncState, options) + { + } + /// /// Initializes a new instance of the class. /// @@ -40,6 +59,17 @@ public AsyncCompletionSource(AsyncCallback asyncCallback, object asyncState) { } + /// + /// Initializes a new instance of the class. + /// + /// User-defined completion callback. + /// User-defined data returned by . + /// The used to customize the operation's behavior. + public AsyncCompletionSource(AsyncCallback asyncCallback, object asyncState, AsyncCreationOptions options) + : base(asyncCallback, asyncState, options) + { + } + /// /// Initializes a new instance of the class. /// @@ -49,6 +79,16 @@ public AsyncCompletionSource(AsyncOperationStatus status) { } + /// + /// Initializes a new instance of the class. + /// + /// Initial value of the property. + /// The used to customize the operation's behavior. + public AsyncCompletionSource(AsyncOperationStatus status, AsyncCreationOptions options) + : base(status, options) + { + } + /// /// Initializes a new instance of the class. /// @@ -59,6 +99,17 @@ public AsyncCompletionSource(AsyncOperationStatus status, object asyncState) { } + /// + /// Initializes a new instance of the class. + /// + /// Initial value of the property. + /// User-defined data returned by . + /// The used to customize the operation's behavior. + public AsyncCompletionSource(AsyncOperationStatus status, object asyncState, AsyncCreationOptions options) + : base(status, asyncState, options) + { + } + /// /// Initializes a new instance of the class. /// @@ -70,6 +121,18 @@ public AsyncCompletionSource(AsyncOperationStatus status, AsyncCallback asyncCal { } + /// + /// Initializes a new instance of the class. + /// + /// Initial value of the property. + /// User-defined completion callback. + /// User-defined data returned by . + /// The used to customize the operation's behavior. + public AsyncCompletionSource(AsyncOperationStatus status, AsyncCallback asyncCallback, object asyncState, AsyncCreationOptions options) + : base(status, asyncCallback, asyncState, options) + { + } + /// /// Transitions the operation to state. /// diff --git a/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs b/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs index a16ad2a..f94de60 100644 --- a/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs @@ -22,6 +22,15 @@ public AsyncCompletionSource() { } + /// + /// Initializes a new instance of the class. + /// + /// The used to customize the operation's behavior. + public AsyncCompletionSource(AsyncCreationOptions options) + : base(options) + { + } + /// /// Initializes a new instance of the class. /// @@ -31,6 +40,16 @@ public AsyncCompletionSource(object asyncState) { } + /// + /// Initializes a new instance of the class. + /// + /// User-defined data returned by . + /// The used to customize the operation's behavior. + public AsyncCompletionSource(object asyncState, AsyncCreationOptions options) + : base(default(AsyncCallback), asyncState, options) + { + } + /// /// Initializes a new instance of the class. /// @@ -41,6 +60,17 @@ public AsyncCompletionSource(AsyncCallback asyncCallback, object asyncState) { } + /// + /// Initializes a new instance of the class. + /// + /// User-defined completion callback. + /// User-defined data returned by . + /// The used to customize the operation's behavior. + public AsyncCompletionSource(AsyncCallback asyncCallback, object asyncState, AsyncCreationOptions options) + : base(asyncCallback, asyncState, options) + { + } + /// /// Initializes a new instance of the class. /// @@ -50,13 +80,34 @@ public AsyncCompletionSource(AsyncOperationStatus status) { } + /// + /// Initializes a new instance of the class. + /// + /// Initial value of the property. + /// The used to customize the operation's behavior. + public AsyncCompletionSource(AsyncOperationStatus status, AsyncCreationOptions options) + : base(status, options) + { + } + /// /// Initializes a new instance of the class. /// /// Initial value of the property. /// User-defined data returned by . public AsyncCompletionSource(AsyncOperationStatus status, object asyncState) - : base(status, null, asyncState) + : base(status, asyncState) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Initial value of the property. + /// User-defined data returned by . + /// The used to customize the operation's behavior. + public AsyncCompletionSource(AsyncOperationStatus status, object asyncState, AsyncCreationOptions options) + : base(status, asyncState, options) { } @@ -71,6 +122,18 @@ public AsyncCompletionSource(AsyncOperationStatus status, AsyncCallback asyncCal { } + /// + /// Initializes a new instance of the class. + /// + /// Initial value of the property. + /// User-defined completion callback. + /// User-defined data returned by . + /// The used to customize the operation's behavior. + public AsyncCompletionSource(AsyncOperationStatus status, AsyncCallback asyncCallback, object asyncState, AsyncCreationOptions options) + : base(status, asyncCallback, asyncState, options) + { + } + /// /// Transitions the operation to state. /// diff --git a/src/UnityFx.Async/Api/Core/AsyncContinuationOptions.cs b/src/UnityFx.Async/Api/Core/AsyncContinuationOptions.cs new file mode 100644 index 0000000..38428dd --- /dev/null +++ b/src/UnityFx.Async/Api/Core/AsyncContinuationOptions.cs @@ -0,0 +1,58 @@ +// 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 +{ + /// + /// Specifies behavior of an asynchronous opration continuation. + /// + /// + [Flags] + public enum AsyncContinuationOptions + { + /// + /// When no continuation options are specified, specifies that default behavior should be used when executing a continuation. + /// I.e. continuation is scheduled оn the same that was active when the continuation was created. + /// + None = 0, + + /// + /// Specifies that the continuation should not be scheduled if its antecedent ran to completion. + /// + NotOnRanToCompletion = 1, + + /// + /// Specifies that the continuation should not be scheduled if its antecedent threw an unhandled exception. + /// + NotOnFaulted = 2, + + /// + /// Specifies that the continuation should not be scheduled if its antecedent was canceled. + /// + NotOnCanceled = 4, + + /// + /// Specifies that the continuation should be scheduled only if its antecedent ran to completion. + /// + OnlyOnRanToCompletion = NotOnFaulted | NotOnCanceled, + + /// + /// Specifies that the continuation should be scheduled only if its antecedent threw an unhandled exception. + /// + OnlyOnFaulted = NotOnRanToCompletion | NotOnCanceled, + + /// + /// Specifies that the continuation should be scheduled only if its antecedent was canceled. + /// + OnlyOnCanceled = NotOnRanToCompletion | NotOnFaulted, + + /// + /// Specifies that the continuation should be executed synchronously. With this option specified, the continuation runs on + /// the same thread that causes the antecedent operation to transition into its final state. + /// + ExecuteSynchronously = 8 + } +} diff --git a/src/UnityFx.Async/Api/Core/AsyncCreationOptions.cs b/src/UnityFx.Async/Api/Core/AsyncCreationOptions.cs new file mode 100644 index 0000000..d2bb40c --- /dev/null +++ b/src/UnityFx.Async/Api/Core/AsyncCreationOptions.cs @@ -0,0 +1,25 @@ +// 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 +{ + /// + /// Specifies flags that control optional behavior for the creation and execution of operations. + /// + /// + [Flags] + public enum AsyncCreationOptions + { + /// + /// Specifies that the default behavior should be used. + /// + None = 0, + + /// + /// Forces continuations added to the current operation to be executed asynchronously. + /// + RunContinuationsAsynchronously = AsyncResult.OptionRunContinuationsAsynchronously + } +} diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 4a3698b..395a700 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -51,13 +51,16 @@ public partial class AsyncResult : IAsyncOperation, IAsyncCancellable, IEnumerat private const int _flagCompletionReserved = 0x00010000; private const int _flagCompleted = 0x00020000; private const int _flagSynchronous = 0x00040000; - private const int _flagCancellationRequested = 0x00100000; private const int _flagCompletedSynchronously = _flagCompleted | _flagCompletionReserved | _flagSynchronous; - private const int _flagDisposed = 0x01000000; - private const int _flagDoNotDispose = 0x10000000; - private const int _flagRunContinuationsAsynchronously = 0x20000000; + private const int _flagCancellationRequested = 0x00100000; + private const int _flagDisposed = 0x00200000; + + private const int _flagDoNotDispose = OptionDoNotDispose << _optionsOffset; + private const int _flagRunContinuationsAsynchronously = OptionRunContinuationsAsynchronously << _optionsOffset; + private const int _statusMask = 0x0000000f; - private const int _resetMask = 0x70000000; + private const int _optionsMask = 0x70000000; + private const int _optionsOffset = 28; private readonly object _asyncState; private AggregateException _exception; @@ -77,6 +80,12 @@ public partial class AsyncResult : IAsyncOperation, IAsyncCancellable, IEnumerat #region interface + /// + /// Gets the used to create this operation. + /// + /// The operation creation options. + public AsyncCreationOptions CreationOptions => (AsyncCreationOptions)(_flags >> _optionsOffset); + /// /// Gets a value indicating whether the operation instance is disposed. /// @@ -96,6 +105,15 @@ public AsyncResult() { } + /// + /// Initializes a new instance of the class with the specified . + /// + /// The used to customize the operation's behavior. + public AsyncResult(AsyncCreationOptions options) + : this((int)options << _optionsOffset) + { + } + /// /// Initializes a new instance of the class. /// @@ -107,6 +125,19 @@ public AsyncResult(AsyncCallback asyncCallback, object asyncState) _continuation = asyncCallback; } + /// + /// Initializes a new instance of the class. + /// + /// User-defined completion callback. + /// User-defined data returned by . + /// The used to customize the operation's behavior. + public AsyncResult(AsyncCallback asyncCallback, object asyncState, AsyncCreationOptions options) + : this((int)options << _optionsOffset) + { + _asyncState = asyncState; + _continuation = asyncCallback; + } + /// /// Initializes a new instance of the class with the specified . /// @@ -116,6 +147,16 @@ public AsyncResult(AsyncOperationStatus status) { } + /// + /// Initializes a new instance of the class with the specified and . + /// + /// Initial value of the property. + /// The used to customize the operation's behavior. + public AsyncResult(AsyncOperationStatus status, AsyncCreationOptions options) + : this((int)status | ((int)options << _optionsOffset)) + { + } + /// /// Initializes a new instance of the class with the specified . /// @@ -127,6 +168,18 @@ public AsyncResult(AsyncOperationStatus status, object asyncState) _asyncState = asyncState; } + /// + /// Initializes a new instance of the class with the specified and . + /// + /// Initial value of the property. + /// User-defined data returned by . + /// The used to customize the operation's behavior. + public AsyncResult(AsyncOperationStatus status, object asyncState, AsyncCreationOptions options) + : this((int)status | ((int)options << _optionsOffset)) + { + _asyncState = asyncState; + } + /// /// Initializes a new instance of the class with the specified . /// @@ -140,6 +193,20 @@ public AsyncResult(AsyncOperationStatus status, AsyncCallback asyncCallback, obj _continuation = asyncCallback; } + /// + /// Initializes a new instance of the class with the specified and . + /// + /// Initial value of the property. + /// User-defined completion callback. + /// User-defined data returned by . + /// The used to customize the operation's behavior. + public AsyncResult(AsyncOperationStatus status, AsyncCallback asyncCallback, object asyncState, AsyncCreationOptions options) + : this((int)status | ((int)options << _optionsOffset)) + { + _asyncState = asyncState; + _continuation = asyncCallback; + } + /// /// Initializes a new instance of the class that is faulted. For internal use only. /// @@ -602,6 +669,9 @@ protected virtual void Dispose(bool disposing) internal const int StatusCanceled = 4; internal const int StatusFaulted = 5; + internal const int OptionDoNotDispose = 1; + internal const int OptionRunContinuationsAsynchronously = 2; + /// /// Special status setter for and . /// diff --git a/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs b/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs index be372f9..6fa343e 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs @@ -39,6 +39,15 @@ public AsyncResult() { } + /// + /// Initializes a new instance of the class. + /// + /// The used to customize the operation's behavior. + public AsyncResult(AsyncCreationOptions options) + : base(options) + { + } + /// /// Initializes a new instance of the class. /// @@ -49,6 +58,17 @@ public AsyncResult(AsyncCallback asyncCallback, object asyncState) { } + /// + /// Initializes a new instance of the class. + /// + /// User-defined completion callback. + /// User-defined data to assosiate with the operation. + /// The used to customize the operation's behavior. + public AsyncResult(AsyncCallback asyncCallback, object asyncState, AsyncCreationOptions options) + : base(asyncCallback, asyncState, options) + { + } + /// /// Initializes a new instance of the class. /// @@ -58,6 +78,16 @@ public AsyncResult(AsyncOperationStatus status) { } + /// + /// Initializes a new instance of the class. + /// + /// Status value of the operation. + /// The used to customize the operation's behavior. + public AsyncResult(AsyncOperationStatus status, AsyncCreationOptions options) + : base(status, options) + { + } + /// /// Initializes a new instance of the class. /// @@ -68,6 +98,17 @@ public AsyncResult(AsyncOperationStatus status, object asyncState) { } + /// + /// Initializes a new instance of the class. + /// + /// Status value of the operation. + /// User-defined data to assosiate with the operation. + /// The used to customize the operation's behavior. + public AsyncResult(AsyncOperationStatus status, object asyncState, AsyncCreationOptions options) + : base(status, asyncState, options) + { + } + /// /// Initializes a new instance of the class. /// @@ -79,6 +120,18 @@ public AsyncResult(AsyncOperationStatus status, AsyncCallback asyncCallback, obj { } + /// + /// Initializes a new instance of the class. + /// + /// Status value of the operation. + /// User-defined completion callback. + /// User-defined data to assosiate with the operation. + /// The used to customize the operation's behavior. + public AsyncResult(AsyncOperationStatus status, AsyncCallback asyncCallback, object asyncState, AsyncCreationOptions options) + : base(status, asyncCallback, asyncState, options) + { + } + /// /// Initializes a new instance of the class. For internal use only. /// diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs index 975efa5..4000a1a 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs @@ -7,59 +7,15 @@ namespace UnityFx.Async { /// - /// Specifies the behavior of an asynchronous opration continuation. - /// - /// - [Flags] - public enum AsyncContinuationOptions - { - /// - /// When no continuation options are specified, specifies that default behavior should be used when executing a continuation. - /// I.e. continuation is scheduled оn the same that was active when the continuation was created. - /// - None = 0, - - /// - /// Specifies that the continuation should not be scheduled if its antecedent ran to completion. - /// - NotOnRanToCompletion = 1, - - /// - /// Specifies that the continuation should not be scheduled if its antecedent threw an unhandled exception. - /// - NotOnFaulted = 2, - - /// - /// Specifies that the continuation should not be scheduled if its antecedent was canceled. - /// - NotOnCanceled = 4, - - /// - /// Specifies that the continuation should be scheduled only if its antecedent ran to completion. - /// - OnlyOnRanToCompletion = NotOnFaulted | NotOnCanceled, - - /// - /// Specifies that the continuation should be scheduled only if its antecedent threw an unhandled exception. - /// - OnlyOnFaulted = NotOnRanToCompletion | NotOnCanceled, - - /// - /// Specifies that the continuation should be scheduled only if its antecedent was canceled. - /// - OnlyOnCanceled = NotOnRanToCompletion | NotOnFaulted, - - /// - /// Specifies that the continuation should be executed synchronously. With this option specified, the continuation runs on - /// the same thread that causes the antecedent operation to transition into its final state. - /// - ExecuteSynchronously = 8 - } - - /// - /// A generic continuation. + /// A generic non-delegate continuation. /// + /// + /// This interface allows us to combine functionality and reduce allocations. It especially useful for implementing custom + /// continuation operations. + /// /// + /// + /// public interface IAsyncContinuation { /// @@ -67,7 +23,7 @@ public interface IAsyncContinuation /// /// The completed antecedent operation. /// Inline call flag: means the continuation was called without actually being - /// added to the operation continuation list (the operation was already completed at the time). + /// added to the continuation list (the operation was already completed at the time). void Invoke(IAsyncOperation op, bool inline); } } diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs index 70cd86f..f28c0a5 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs @@ -12,6 +12,7 @@ namespace UnityFx.Async /// The asynchronous operation. /// /// + /// public delegate void AsyncOperationCallback(IAsyncOperation op); /// From 080f0d9a17bbf9e28eb85b49d30a3f51101a833f Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 21 Apr 2018 21:03:44 +0300 Subject: [PATCH 120/128] README update --- README.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 58d838c..d12fcfb 100644 --- a/README.md +++ b/README.md @@ -423,12 +423,15 @@ TPL | UnityFx.Async | Notes [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) | [AsyncResult](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncResult.html), [IAsyncOperation](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.IAsyncOperation.html) | Represents an asynchronous operation. [Task<TResult>](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1) | [AsyncResult<TResult>](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncResult-1.html), [IAsyncOperation<TResult>](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.IAsyncOperation-1.html) | Represents an asynchronous operation that can return a value. [TaskStatus](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskstatus) | [AsyncOperationStatus](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncOperationStatus.html) | Represents the current stage in the lifecycle of an asynchronous operation. -[TaskCreationOptions](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcreationoptions) | - | Specifies flags that control optional behavior for the creation and execution of asynchronous operations. +[TaskCreationOptions](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcreationoptions) | [AsyncCreationOptions](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncCreationOptions.html) | Specifies flags that control optional behavior for the creation and execution of asynchronous operations. [TaskContinuationOptions](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcontinuationoptions) | [AsyncContinuationOptions](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncContinuationOptions.html) | Specifies the behavior for an asynchronous operation that is created by using continuation methods (`ContinueWith`). [TaskCanceledException](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcanceledexception) | - | Represents an exception used to communicate an asynchronous operation cancellation. [TaskCompletionSource<TResult>](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskcompletionsource-1) | [AsyncCompletionSource](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncCompletionSource.html), [IAsyncCompletionSource](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.IAsyncCompletionSource.html), [AsyncCompletionSource<TResult>](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncCompletionSource-1.html), [IAsyncCompletionSource<TResult>](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.IAsyncCompletionSource-1.html) | Represents the producer side of an asyncronous operation unbound to a delegate. [TaskScheduler](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskscheduler) | - | Represents an object that handles the low-level work of queuing asynchronous operations onto threads. [TaskFactory](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskfactory), [TaskFactory<TResult>](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskfactory-1) | - | Provides support for creating and scheduling asynchronous operations. +- | [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: @@ -436,10 +439,6 @@ Please note that the library is NOT a replacement for [Tasks](https://docs.micro - Memory usage is a concern ([Tasks](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) tend to do quite a lot of allocations). - An extendable [IAsyncResult](https://docs.microsoft.com/en-us/dotnet/api/system.iasyncresult) implementation is needed. -## Future work -* Implementation of noalloc completion callbacks. -* Progress reporting (via [IProgress](https://docs.microsoft.com/en-us/dotnet/api/system.iprogress-1)). - ## 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 the like 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 for the best of human kind. From 784f7aa32a0384371fa3874940121a4b3e63e0b5 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 21 Apr 2018 21:03:51 +0300 Subject: [PATCH 121/128] CHANGELOG update --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e85c4c3..4598fd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/); this proj ## [0.9.0] - Unreleased ### Added -- Added `AsyncContinuationOptions` support. +- Added `AsyncContinuationOptions`. +- Added `AsyncCreationOptions`. - Added `Promise`-like extensions `Then`, `ThenAll`, `ThenAny`, `Rebind`, `Catch` and `Finally`. - Added `Unwrap` extension methods. - Added `FromTask`/`FromObservable` helpers. +- Added `FromAction` helpers. - Added `ToAsync` extension method for `IObservable` interface. - Added `TryAddContinuation`/`RemoveContinuation` methods to `IAsyncOperationEvents` for non-delegate continuations. - Added `IAsyncUpdatable` and `IAsyncUpdateSource` interfaces. From 333ef9597374fb93991a3493e050204fd4e7fba3 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 23 Apr 2018 11:04:06 +0300 Subject: [PATCH 122/128] README update --- README.md | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index d12fcfb..07a39c2 100644 --- a/README.md +++ b/README.md @@ -314,7 +314,7 @@ DownloadTextAsync("http://www.google.com") .Then(text => ExtractFirstParagraph(text)) .WithCancellation(cancellationToken); ``` -If the [token](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken) passed to `WithCancellation` is cancelled the target operation is cancelled as well (and that means cancelling all of the chain operations) as soon as possible. The cancellation might not be instant (depends on specific operation implementation). Also please note that not all operations might support cancellation. +If the [token](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken) passed to `WithCancellation` is cancelled the target operation is cancelled as well (and that means cancelling all chained operations) as soon as possible. Cancellation might not be instant (depends on specific operation implementation). Also, please note that not all operations might support cancellation. ### Synchronization context capturing The default behaviour of all library methods is to capture current [SynchronizationContext](https://docs.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext) and try to schedule continuations on it. If there is no synchronization context attached to current thread, continuations are executed on a thread that initiated an operation completion. The same behaviour applies to `async` / `await` implementation unless explicitly overriden with `ConfigureAwait`: @@ -327,19 +327,19 @@ await DownloadTextAsync("http://www.yahoo.com").ConfigureAwait(false); ``` ### Completion callbacks -All operations defined in the library support adding of completion callbacks that are executed when an operation completes: +Completion callbacks are basicly low-level continuations. Just like continuations they are executed when parent operation completes: ```csharp var op = DownloadTextAsync("http://www.google.com"); op.Completed += o => Debug.Log("1"); op.AddCompletionCallback(o => Debug.Log("2")); ``` -Unlike `ContinueWith`-like stuff completion callbacks cannot be chained and do not handle exceptions automatically. Throwing an exception from a completion callback results in unspecified behavior. +That said, unlike `ContinueWith`-like stuff completion callbacks cannot be chained and do not handle exceptions automatically. Throwing an exception from a completion callback results in unspecified behavior. -There is also a low-level continuation interface (`IAsyncContinuation`): +There are also non-delegate completion callbacks (`IAsyncContinuation`): ```csharp class MyContinuation : IAsyncContinuation { - public void Invoke(IAsyncOperation op) => Debug.Log("Done"); + public void Invoke(IAsyncOperation op, bool inline) => Debug.Log("Done"); } // ... @@ -357,9 +357,10 @@ Please note that `Dispose` implementation is NOT thread-safe and can only be cal There are a number of helper methods for creating completed operations: ```csharp var op1 = AsyncResult.CompletedOperation; -var op2 = AsyncResult.FromResult(10); -var op3 = AsyncResult.FromException(new Exception()); -var op4 = AsyncResult.FromCanceled(); +var op2 = AsyncResult.CanceledOperation; +var op3 = AsyncResult.FromResult(10); +var op4 = AsyncResult.FromException(new Exception()); +var op5 = AsyncResult.FromCanceled(); ``` ### Convertions @@ -377,8 +378,8 @@ var op5 = unityWWW.ToAsync(); ### Creating own asynchronous operations Most common way of creating own asynchronous operation is instantiating `AsyncCompletionSource` instance and call `SetResult` / `SetException` / `SetCanceled` when done. Still there are cases when more control is required. For this purpose the library provides two public extendable implementations for asynchronous operations: -* `AsyncResult`: an operation without a result value. -* `AsyncResult`: an asynchronous operation with a generic result value. +* `AsyncResult`: a generic asynchronous operation without a result value. +* `AsyncResult`: an asynchronous operation with a result value. The sample code below demostrates creating a delay operation: ```csharp @@ -389,7 +390,11 @@ public class TimerDelayResult : AsyncResult public TimerDelayResult(int millisecondsDelay) : base(AsyncOperationStatus.Running) { - _timer = new Timer(TimerCompletionCallback, this, millisecondsDelay, Timeout.Infinite); + _timer = new Timer( + state => (state as TimerDelayResult).TrySetCompleted(false), + this, + millisecondsDelay, + Timeout.Infinite); } protected override void OnCompleted() @@ -398,6 +403,11 @@ public class TimerDelayResult : AsyncResult base.OnCompleted(); } + protected override void OnCancel() + { + _timer.Dispose(); + } + protected override void Dispose(bool disposing) { if (disposing) @@ -407,11 +417,6 @@ public class TimerDelayResult : AsyncResult base.Dispose(disposing); } - - private static void TimerCompletionCallback(object state) - { - (state as TimerDelayResult).TrySetCompleted(false); - } } ``` From 3cd51ebb869a20b89df21a0669d918cc223d6f83 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 23 Apr 2018 12:12:24 +0300 Subject: [PATCH 123/128] Added a few more tests --- .../Tests/CompletionCallbackTests.cs | 61 ++++++++++++++++++- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs b/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs index 59c97dc..5f6d9d9 100644 --- a/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs +++ b/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs @@ -68,18 +68,21 @@ void CompletionCallback(IAsyncOperation o) void TestMethod() { - for (var i = 0; i < 1000; ++i) + for (var i = 0; i < 10000; ++i) { op.TryAddCompletionCallback(CompletionCallback); } } // Act - await Task.WhenAll(Task.Run(new Action(TestMethod)), Task.Run(new Action(TestMethod)), Task.Run(new Action(TestMethod))); + await Task.WhenAll( + Task.Run(new Action(TestMethod)), + Task.Run(new Action(TestMethod)), + Task.Run(new Action(TestMethod))); // Assert op.SetCompleted(); - Assert.Equal(3000, counter); + Assert.Equal(30000, counter); } [Fact] @@ -95,6 +98,58 @@ public void TryAddContinuation_ExecutesWhenOperationCompletes() // Assert continuation.Received(1).Invoke(op, false); + continuation.Received(0).Invoke(op, true); + } + + [Fact] + public void AddContinuation_ExecutesIfOperationIsCompletedSynchronously() + { + // Arrange + var op = AsyncResult.CompletedOperation; + var continuation = Substitute.For(); + + // Act + op.AddContinuation(continuation); + + // Assert + continuation.Received(1).Invoke(op, true); + continuation.Received(0).Invoke(op, false); + } + + [Fact] + public void AddCompletionCallback_ExecutesIfOperationIsCompletedSynchronously() + { + // Arrange + var op = AsyncResult.CanceledOperation; + var callbackCalled = false; + + // Act + op.AddCompletionCallback(_ => callbackCalled = true, null); + + // Assert + Assert.True(callbackCalled); + } + + [Fact] + public async Task TryAddCompletionCallback_ContinuationsAreRunOnCorrectSynchronozationContext() + { + // Arrange + var op = new AsyncCompletionSource(); + var op2 = new AsyncCompletionSource(); + var sc = Substitute.For(); + var tid = 0; + var tidActual = 0; + + op.TryAddCompletionCallback(_ => { }, sc); + op2.TryAddCompletionCallback(_ => tidActual = Thread.CurrentThread.ManagedThreadId, null); + + // Act + await Task.Run(() => op.SetCompleted()); + await Task.Run(() => { tid = Thread.CurrentThread.ManagedThreadId; op2.SetCompleted(); }); + + // Assert + sc.Received(1).Post(Arg.Any(), Arg.Any()); + Assert.Equal(tid, tidActual); } } } From c94f15576d9ba693b2ff085719113f841eab5644 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 23 Apr 2018 23:46:47 +0300 Subject: [PATCH 124/128] IAsyncOperation now inherits IAsyncCancellable --- src/UnityFx.Async/Api/Core/AsyncResult.cs | 32 +++++++++---------- .../Api/Extensions/AsyncExtensions.cs | 23 +++++-------- .../Api/Interfaces/IAsyncCancellable.cs | 5 ++- .../Api/Interfaces/IAsyncOperation.cs | 2 +- .../Continuations/ContinueWithResult{T,U}.cs | 5 +-- .../Promises/CatchResult{T,TException}.cs | 5 +-- .../Promises/FinallyResult{T}.cs | 5 +-- .../Promises/RebindResult{T,U}.cs | 5 +-- .../Continuations/Promises/ThenResult{T,U}.cs | 11 ++----- .../Specialized/RetryResult{T}.cs | 5 +-- .../Specialized/WhenAllResult{T}.cs | 5 +-- .../Specialized/WhenAnyResult{T}.cs | 5 +-- 12 files changed, 38 insertions(+), 70 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 395a700..6a3ffe5 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -44,7 +44,7 @@ namespace UnityFx.Async /// /// [DebuggerDisplay("{DebuggerDisplay,nq}")] - public partial class AsyncResult : IAsyncOperation, IAsyncCancellable, IEnumerator + public partial class AsyncResult : IAsyncOperation, IEnumerator { #region data @@ -955,6 +955,21 @@ internal static bool TryThrowException(AggregateException e) #endregion + #region IAsyncCancellable + + /// + public void Cancel() + { + ThrowIfDisposed(); + + if (TrySetFlag(_flagCancellationRequested)) + { + OnCancel(); + } + } + + #endregion + #region IAsyncResult /// @@ -1009,21 +1024,6 @@ public WaitHandle AsyncWaitHandle #endregion - #region IAsyncCancellable - - /// - public void Cancel() - { - ThrowIfDisposed(); - - if (TrySetFlag(_flagCancellationRequested)) - { - OnCancel(); - } - } - - #endregion - #region IEnumerator /// diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs index 7400407..7847cb1 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.cs @@ -181,25 +181,18 @@ public static IAsyncOperation WithCancellation(this IAsyncOperation op, Cancella { if (cancellationToken.CanBeCanceled && !op.IsCompleted) { - if (op is IAsyncCancellable c) + if (cancellationToken.IsCancellationRequested) { - if (cancellationToken.IsCancellationRequested) - { - c.Cancel(); - } - else - { - if (_cancelHandler == null) - { - _cancelHandler = args => (args as IAsyncCancellable).Cancel(); - } - - cancellationToken.Register(_cancelHandler, op, false); - } + op.Cancel(); } else { - throw new NotSupportedException(); + if (_cancelHandler == null) + { + _cancelHandler = args => (args as IAsyncCancellable).Cancel(); + } + + cancellationToken.Register(_cancelHandler, op, false); } } diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncCancellable.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncCancellable.cs index 28674ef..e4b2633 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncCancellable.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncCancellable.cs @@ -12,8 +12,11 @@ namespace UnityFx.Async public interface IAsyncCancellable { /// - /// Attempts to cancel the operation. When this method returns the operation can still be uncompleted. + /// Initiates cancellation of an asynchronous operation. There is no guarantee that this call will actually cancel + /// the operation or that the operation will be cancelled immidiately. /// + /// Thrown if the operation is disposed. + /// Thrown if cancellation is not supported by the implementation. void Cancel(); } } diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperation.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperation.cs index 94760cb..35b0f60 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperation.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperation.cs @@ -50,7 +50,7 @@ public enum AsyncOperationStatus /// Task /// /// - public interface IAsyncOperation : IAsyncOperationEvents, IAsyncResult, IDisposable + public interface IAsyncOperation : IAsyncOperationEvents, IAsyncCancellable, IAsyncResult, IDisposable { /// /// Gets the operation status identifier. diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs index dbcb4fe..4e9d1fb 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs @@ -45,10 +45,7 @@ internal ContinueWithResult(IAsyncOperation op, AsyncContinuationOptions options protected override void OnCancel() { - if (_op is IAsyncCancellable c) - { - c.Cancel(); - } + _op.Cancel(); } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/Promises/CatchResult{T,TException}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/CatchResult{T,TException}.cs index c2859d8..11a8ae9 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/CatchResult{T,TException}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/CatchResult{T,TException}.cs @@ -31,10 +31,7 @@ public CatchResult(IAsyncOperation op, Action errorCallback) protected override void OnCancel() { - if (_op is IAsyncCancellable c) - { - c.Cancel(); - } + _op.Cancel(); } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs index 58641ad..c38ad15 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs @@ -31,10 +31,7 @@ public FinallyResult(IAsyncOperation op, object action) protected override void OnCancel() { - if (_op is IAsyncCancellable c) - { - c.Cancel(); - } + _op.Cancel(); } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/Promises/RebindResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/RebindResult{T,U}.cs index e5d6920..ca4b9b8 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/RebindResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/RebindResult{T,U}.cs @@ -32,10 +32,7 @@ public RebindResult(IAsyncOperation op, object action) protected override void OnCancel() { - if (_op is IAsyncCancellable c) - { - c.Cancel(); - } + _op.Cancel(); } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/Promises/ThenResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/ThenResult{T,U}.cs index 3705682..d50e794 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/ThenResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/ThenResult{T,U}.cs @@ -75,15 +75,8 @@ protected virtual void InvokeSuccessCallback(IAsyncOperation op, bool completedS protected override void OnCancel() { - if (_op is IAsyncCancellable c) - { - c.Cancel(); - } - - if (_continuation is IAsyncCancellable c2) - { - c2.Cancel(); - } + _op.Cancel(); + _continuation?.Cancel(); } #endregion diff --git a/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs index a65daed..3bddaa6 100644 --- a/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs @@ -58,10 +58,7 @@ protected override void OnStarted() protected override void OnCancel() { - if (_op is IAsyncCancellable c) - { - c.Cancel(); - } + _op.Cancel(); } #endregion diff --git a/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs index e20898a..0a3b8be 100644 --- a/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs @@ -38,10 +38,7 @@ protected override void OnCancel() { foreach (var op in _ops) { - if (op is IAsyncCancellable c) - { - c.Cancel(); - } + op.Cancel(); } } diff --git a/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs index e22e968..a87ecb7 100644 --- a/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs @@ -34,10 +34,7 @@ protected override void OnCancel() { foreach (var op in _ops) { - if (op is IAsyncCancellable c) - { - c.Cancel(); - } + op.Cancel(); } } From 5692571181d3c0f072eb762c3ae31d0eab085439 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 23 Apr 2018 23:50:56 +0300 Subject: [PATCH 125/128] README update --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 07a39c2..c11ca8c 100644 --- a/README.md +++ b/README.md @@ -308,13 +308,17 @@ finally ``` ### Cancellation -All library operations can be cancelled using `AsyncResult.Cancel` method or with `WithCancellation` extension: +All library operations can be cancelled using `Cancel` method: ```csharp + op.Cancel(); +``` +Or with `WithCancellation` extension: DownloadTextAsync("http://www.google.com") .Then(text => ExtractFirstParagraph(text)) .WithCancellation(cancellationToken); +```csharp ``` -If the [token](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken) passed to `WithCancellation` is cancelled the target operation is cancelled as well (and that means cancelling all chained operations) as soon as possible. Cancellation might not be instant (depends on specific operation implementation). Also, please note that not all operations might support cancellation. +If the [token](https://docs.microsoft.com/en-us/dotnet/api/system.threading.cancellationtoken) passed to `WithCancellation` is cancelled the target operation is cancelled as well (and that means cancelling all chained operations) as soon as possible. Cancellation might not be instant (depends on specific operation implementation). Also, please note that not all operations might support cancellation; in this case `Cancel` will throw `NotSupportedException`. ### Synchronization context capturing The default behaviour of all library methods is to capture current [SynchronizationContext](https://docs.microsoft.com/en-us/dotnet/api/system.threading.synchronizationcontext) and try to schedule continuations on it. If there is no synchronization context attached to current thread, continuations are executed on a thread that initiated an operation completion. The same behaviour applies to `async` / `await` implementation unless explicitly overriden with `ConfigureAwait`: From 72204d17623a3d2727876067d93f188630a80fc7 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Tue, 24 Apr 2018 15:13:31 +0300 Subject: [PATCH 126/128] Add Done promise extension --- .../Extensions/AsyncExtensions.Promises.cs | 110 +++++++++++++++--- .../Continuations/Promises/DoneResult{T}.cs | 42 +++++++ 2 files changed, 139 insertions(+), 13 deletions(-) create mode 100644 src/UnityFx.Async/Implementation/Continuations/Promises/DoneResult{T}.cs diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs index c1d2f02..ab14c04 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs @@ -22,7 +22,7 @@ public static class AsyncExtensions /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the callback has completed. /// - /// + /// public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback) { if (successCallback == null) @@ -40,7 +40,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the callback has completed. /// - /// + /// public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback) { if (successCallback == null) @@ -59,7 +59,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Ac /// Returns a continuation operation that completes after both source operation and the operation returned by has completed. /// /// - /// + /// public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback) { if (successCallback == null) @@ -78,7 +78,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, FuncReturns a continuation operation that completes after both source operation and the operation returned by has completed. /// /// - /// + /// public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback) { if (successCallback == null) @@ -97,7 +97,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Fu /// Returns a continuation operation that completes after both source operation and the operation returned by has completed. /// /// - /// + /// public static IAsyncOperation Then(this IAsyncOperation op, Func> successCallback) { if (successCallback == null) @@ -117,7 +117,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Fu /// /// /// - /// + /// public static IAsyncOperation Then(this IAsyncOperation op, Func> successCallback) { if (successCallback == null) @@ -136,7 +136,7 @@ public static IAsyncOperation Then(this IAsyncO /// The callback to be executed when the operation has faulted/was canceled. /// Returns a continuation operation that completes after both source operation and the callback has completed. /// - /// + /// public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback, Action errorCallback) { if (successCallback == null) @@ -160,7 +160,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Action successCallba /// The callback to be executed when the operation has faulted/was canceled. /// Returns a continuation operation that completes after both source operation and the callback has completed. /// - /// + /// public static IAsyncOperation Then(this IAsyncOperation op, Action successCallback, Action errorCallback) { if (successCallback == null) @@ -184,7 +184,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, Ac /// The callback to be executed when the operation has faulted/was canceled. /// Returns a continuation operation that completes after both source operation and the operation returned by has completed. /// - /// + /// public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback, Action errorCallback) { if (successCallback == null) @@ -208,7 +208,7 @@ public static IAsyncOperation Then(this IAsyncOperation op, FuncThe callback to be executed when the operation has faulted/was canceled. /// Returns a continuation operation that completes after both source operation and the operation returned by has completed. /// - /// + /// public static IAsyncOperation Then(this IAsyncOperation op, Func successCallback, Action errorCallback) { if (successCallback == null) @@ -417,7 +417,7 @@ public static IAsyncOperation Rebind(this IAsyn /// The callback to be executed when the operation has faulted/was canceled. /// Returns a continuation operation that completes after both source operation and the callback has completed. /// - /// + /// public static IAsyncOperation Catch(this IAsyncOperation op, Action errorCallback) { if (errorCallback == null) @@ -435,7 +435,7 @@ public static IAsyncOperation Catch(this IAsyncOperation op, Action e /// The callback to be executed when the operation has faulted/was canceled. /// Returns a continuation operation that completes after both source operation and the callback has completed. /// - /// + /// public static IAsyncOperation Catch(this IAsyncOperation op, Action errorCallback) where TException : Exception { if (errorCallback == null) @@ -456,7 +456,7 @@ public static IAsyncOperation Catch(this IAsyncOperation op, Action< /// An operation to be continued. /// The callback to be executed when the operation has completed. /// Returns a continuation operation that completes after both source operation and the callback has completed. - /// + /// public static IAsyncOperation Finally(this IAsyncOperation op, Action action) { if (action == null) @@ -468,5 +468,89 @@ public static IAsyncOperation Finally(this IAsyncOperation op, Action action) } #endregion + + #region Done + + /// + /// Schedules a callback to be executed after the promise chain has completed. + /// + /// An operation to be continued. + /// The callback to be executed when the promise has resolved. + /// + /// + public static void Done(this IAsyncOperation op, Action successCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + op.AddContinuation(new DoneResult(successCallback, null)); + } + + /// + /// Schedules a callback to be executed after the promise chain has completed. + /// + /// An operation to be continued. + /// The callback to be executed when the promise has resolved. + /// The callback to be executed when the promise was rejected. + /// + /// + public static void Done(this IAsyncOperation op, Action successCallback, Action errorCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + if (errorCallback == null) + { + throw new ArgumentNullException(nameof(errorCallback)); + } + + op.AddContinuation(new DoneResult(successCallback, errorCallback)); + } + + /// + /// Schedules a callback to be executed after the promise chain has completed. + /// + /// An operation to be continued. + /// The callback to be executed when the promise has resolved. + /// + /// + public static void Done(this IAsyncOperation op, Action successCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + op.AddContinuation(new DoneResult(successCallback, null)); + } + + /// + /// Schedules a callback to be executed after the promise chain has completed. + /// + /// An operation to be continued. + /// The callback to be executed when the promise has resolved. + /// The callback to be executed when the promise was rejected. + /// + /// + public static void Done(this IAsyncOperation op, Action successCallback, Action errorCallback) + { + if (successCallback == null) + { + throw new ArgumentNullException(nameof(successCallback)); + } + + if (errorCallback == null) + { + throw new ArgumentNullException(nameof(errorCallback)); + } + + op.AddContinuation(new DoneResult(successCallback, errorCallback)); + } + + #endregion } } diff --git a/src/UnityFx.Async/Implementation/Continuations/Promises/DoneResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/DoneResult{T}.cs new file mode 100644 index 0000000..395b25d --- /dev/null +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/DoneResult{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.Promises +{ + internal sealed class DoneResult : IAsyncContinuation + { + #region data + + private readonly object _successCallback; + private readonly Action _errorCallback; + + #endregion + + public DoneResult(object successCallback, Action errorCallback) + { + _successCallback = successCallback; + _errorCallback = errorCallback; + } + + public void Invoke(IAsyncOperation op, bool inline) + { + if (op.IsCompletedSuccessfully) + { + if (_successCallback is Action a) + { + a(); + } + else if (_successCallback is Action a1) + { + a1((op as IAsyncOperation).Result); + } + } + else + { + _errorCallback?.Invoke(op.Exception.InnerException); + } + } + } +} From cecbd814a2e1137042eee727922677372f47929f Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Tue, 24 Apr 2018 15:23:37 +0300 Subject: [PATCH 127/128] CHANGELOG update --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4598fd1..7895d18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/); this proj ### Added - Added `AsyncContinuationOptions`. - Added `AsyncCreationOptions`. -- Added `Promise`-like extensions `Then`, `ThenAll`, `ThenAny`, `Rebind`, `Catch` and `Finally`. +- Added `Promise`-like extensions `Then`, `ThenAll`, `ThenAny`, `Rebind`, `Catch`, `Finally` and `Done`. - Added `Unwrap` extension methods. - Added `FromTask`/`FromObservable` helpers. - Added `FromAction` helpers. From b19f59f752b8786f4fd151cb0105a66d3aa8653a Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Tue, 24 Apr 2018 15:49:28 +0300 Subject: [PATCH 128/128] README update --- README.md | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c11ca8c..04df6b6 100644 --- a/README.md +++ b/README.md @@ -264,7 +264,7 @@ catch (Exception e) ``` ### Chaining asynchronous operations -Multiple asynchronous operations can be chained one after other using `Then` / `Rebind` / `ContinueWith` / `Catch` / `Finally`: +Multiple asynchronous operations can be chained one after other using `Then` / `Rebind` / `ContinueWith` / `Catch` / `Finally` / `Done`: ```csharp DownloadTextAsync("http://www.google.com") .Then(text => ExtractFirstParagraph(text)) @@ -282,13 +282,20 @@ DownloadTextAsync("http://www.google.com") .Then(text => ExtractFirstUrl(text)) .Rebind(url => new Url(url)); ``` -`ContinueWith` and `Finally` delegates get called independently of the antecedent operation result. `ContinueWith` also define overloads accepting `AsyncContinuationOptions` argument that allows to customize its behaviour: +`ContinueWith` and `Finally` delegates get called independently of the antecedent operation result. `ContinueWith` also define overloads accepting `AsyncContinuationOptions` argument that allows to customize its behaviour. Note that `ContinueWith` is analog to the corresponding [Task method](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.continuewith) and not a part of the JS promise pattern: ```csharp DownloadTextAsync("http://www.google.com") .ContinueWith(op => Debug.Log("1")) .ContinueWith(op => Debug.Log("2"), AsyncContinuationOptions.NotOnCanceled) .ContinueWith(op => Debug.Log("3"), AsyncContinuationOptions.OnlyOnFaulted); ``` +`Done` acts like a combination of `Catch` and `Finally`. It should always be the last element of the chain: +```csharp +DownloadTextAsync("http://www.google.com") + .Then(text => ExtractFirstUrl(text)) + .Done(url => Debug.Log("Done"), e => Debug.LogException(e)); +``` + That said with .NET 4.6 the recommented approach is using `async` / `await`: ```csharp try