From 0551d6aafd7689bef1b16cd8326173bdcdfeb08f Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 26 May 2018 14:29:19 +0300 Subject: [PATCH 01/67] Fixed AsyncUpdateSource implementation errors when removing listener while in OnCompleted/OnError/Dispose --- .../Tests/AsyncUpdateSourceTests.cs | 54 ++++++++++++++++++- .../Api/Core/AsyncUpdateSource.cs | 51 +++++++++++++----- 2 files changed, 92 insertions(+), 13 deletions(-) diff --git a/src/UnityFx.Async.Tests/Tests/AsyncUpdateSourceTests.cs b/src/UnityFx.Async.Tests/Tests/AsyncUpdateSourceTests.cs index b35302f..84000ca 100644 --- a/src/UnityFx.Async.Tests/Tests/AsyncUpdateSourceTests.cs +++ b/src/UnityFx.Async.Tests/Tests/AsyncUpdateSourceTests.cs @@ -109,7 +109,7 @@ public void RemoveListener_CanBeCalledFromUpdate() } [Fact] - public void Unsubscribe_CanBeCalledFromUpdate() + public void Unsubscribe_CanBeCalledFromOnNext() { // Arrange var updateSource = new AsyncUpdateSource(); @@ -125,5 +125,57 @@ public void Unsubscribe_CanBeCalledFromUpdate() // Assert observer.Received(1).OnNext(Arg.Any()); } + + [Fact] + public void Unsubscribe_CanBeCalledFromOnError() + { + // Arrange + var updateSource = new AsyncUpdateSource(); + var observer = Substitute.For>(); + var subscription = updateSource.Subscribe(observer); + var e = new Exception(); + + observer.When(x => x.OnError(e)).Do(x => subscription.Dispose()); + + // Act + updateSource.OnError(e); + + // Assert + observer.Received(1).OnError(e); + } + + [Fact] + public void Unsubscribe_CanBeCalledFromOnCompleted() + { + // Arrange + var updateSource = new AsyncUpdateSource(); + var observer = Substitute.For>(); + var subscription = updateSource.Subscribe(observer); + + observer.When(x => x.OnCompleted()).Do(x => subscription.Dispose()); + + // Act + updateSource.OnCompleted(); + + // Assert + observer.Received(1).OnCompleted(); + } + + [Fact] + public void Unsubscribe_CanBeCalledFromDispose() + { + // Arrange + var updateSource = new AsyncUpdateSource(); + var observer = Substitute.For>(); + var subscription = updateSource.Subscribe(observer); + + observer.When(x => x.OnCompleted()).Do(x => subscription.Dispose()); + + // Act + updateSource.Dispose(); + + // Assert + observer.Received(1).OnCompleted(); + } } } diff --git a/src/UnityFx.Async/Api/Core/AsyncUpdateSource.cs b/src/UnityFx.Async/Api/Core/AsyncUpdateSource.cs index cef0ddc..2604ba1 100644 --- a/src/UnityFx.Async/Api/Core/AsyncUpdateSource.cs +++ b/src/UnityFx.Async/Api/Core/AsyncUpdateSource.cs @@ -257,12 +257,21 @@ public void OnCompleted() if (_observers != null) { - foreach (var item in _observers) + _updating = true; + + try { - item.OnCompleted(); + foreach (var item in _observers) + { + item.OnCompleted(); + } + } + finally + { + _observersToRemove.Clear(); + _observers.Clear(); + _updating = false; } - - _observers.Clear(); } #endif @@ -280,12 +289,21 @@ public void OnError(Exception e) if (_observers != null) { - foreach (var item in _observers) + _updating = true; + + try { - item.OnError(e); + foreach (var item in _observers) + { + item.OnError(e); + } + } + finally + { + _observersToRemove.Clear(); + _observers.Clear(); + _updating = false; } - - _observers.Clear(); } #endif @@ -309,12 +327,21 @@ public void Dispose() if (_observers != null) { - foreach (var item in _observers) + _updating = true; + + try { - item.OnCompleted(); + foreach (var item in _observers) + { + item.OnCompleted(); + } + } + finally + { + _observersToRemove = null; + _observers = null; + _updating = false; } - - _observers = null; } #endif From 0d4cecafcd5c3138d783d5c35761c644551a8967 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 26 May 2018 14:32:43 +0300 Subject: [PATCH 02/67] CHANGELOG update --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bbde33..6c84b4a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/); this proj ----------------------- ## [0.9.3] - unreleased +### Fixed +- Fixed exception when removing listeners while in `AsyncUpdateSource.OnError` / `AsyncUpdateSource.OnCompleted` / `AsyncUpdateSource.Dispose`. + ----------------------- ## [0.9.2] - 2018.05.25 From c3497a3bcf02cc53465171baa5e3379aba854633 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sun, 27 May 2018 14:25:57 +0300 Subject: [PATCH 03/67] README update (Asset Store package) --- src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/README.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/README.txt b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/README.txt index 3eeffec..0422112 100644 --- a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/README.txt +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/README.txt @@ -5,6 +5,7 @@ LICENSE https://github.com/Arvtesh/UnityFx.Async/blob/master/LICENSE.md DOCUMENTATION +https://github.com/Arvtesh/UnityFx.Async https://arvtesh.github.io/UnityFx.Async SUPPORT From b415f0e1016aed157addbc72c281fbbf58673013 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Tue, 29 May 2018 12:21:06 +0300 Subject: [PATCH 04/67] Added update sources for LateUpdate and FixedUpdate --- .../UnityFx.Async/Scripts/AsyncUtility.cs | 147 ++++++++++++++---- 1 file changed, 119 insertions(+), 28 deletions(-) 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 d0eb614..932b922 100644 --- a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncUtility.cs +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncUtility.cs @@ -46,13 +46,37 @@ public static GameObject GetRootGo() } /// - /// Returns an instance of an . + /// Returns an instance of an for Update. /// - public static IAsyncUpdateSource GetDefaultUpdateSource() + public static IAsyncUpdateSource GetUpdateSource() { return GetCoroutineRunner().UpdateSource; } + /// + /// Returns an instance of an for LateUpdate. + /// + public static IAsyncUpdateSource GetLateUpdateSource() + { + return GetCoroutineRunner().LateUpdateSource; + } + + /// + /// Returns an instance of an for FixedUpdate. + /// + public static IAsyncUpdateSource GetFixedUpdateSource() + { + return GetCoroutineRunner().FixedUpdateSource; + } + + /// + /// Returns an instance of an for end of frame. + /// + public static IAsyncUpdateSource GetEndOfFrameUpdateSource() + { + return GetCoroutineRunner().EofUpdateSource; + } + /// /// Starts a coroutine. /// @@ -114,32 +138,6 @@ public static void StopAllCoroutines() } } - /// - /// Adds a new delegate that is called once per update cycle. - /// - /// The update callback to add. - public static void AddUpdateCallback(Action updateCallback) - { - GetCoroutineRunner().UpdateSource.AddListener(updateCallback); - } - - /// - /// Removes an existing update callback. - /// - /// The update callback to remove. - public static void RemoveUpdateCallback(Action updateCallback) - { - if (updateCallback != null) - { - var runner = TryGetCoroutineRunner(); - - if (runner) - { - runner.UpdateSource.RemoveListener(updateCallback); - } - } - } - /// /// Register a completion callback for the specified instance. /// @@ -215,6 +213,10 @@ private class CoroutineRunner : MonoBehaviour private Dictionary _ops; private List _opsToRemove; private AsyncUpdateSource _updateSource; + private AsyncUpdateSource _lateUpdateSource; + private AsyncUpdateSource _fixedUpdateSource; + private AsyncUpdateSource _eofUpdateSource; + private WaitForEndOfFrame _eof; #endregion @@ -233,6 +235,47 @@ public IAsyncUpdateSource UpdateSource } } + public IAsyncUpdateSource LateUpdateSource + { + get + { + if (_lateUpdateSource == null) + { + _lateUpdateSource = new AsyncUpdateSource(); + } + + return _lateUpdateSource; + } + } + + public IAsyncUpdateSource FixedUpdateSource + { + get + { + if (_fixedUpdateSource == null) + { + _fixedUpdateSource = new AsyncUpdateSource(); + } + + return _fixedUpdateSource; + } + } + + public IAsyncUpdateSource EofUpdateSource + { + get + { + if (_eofUpdateSource == null) + { + _eofUpdateSource = new AsyncUpdateSource(); + _eof = new WaitForEndOfFrame(); + StartCoroutine(EofEnumerator()); + } + + return _eofUpdateSource; + } + } + public void AddCompletionCallback(object op, Action cb) { if (_ops == null) @@ -302,6 +345,22 @@ private void Update() } } + private void LateUpdate() + { + if (_lateUpdateSource != null) + { + _lateUpdateSource.OnNext(Time.deltaTime); + } + } + + private void FixedUpdate() + { + if (_fixedUpdateSource != null) + { + _fixedUpdateSource.OnNext(Time.fixedDeltaTime); + } + } + private void OnDestroy() { if (_updateSource != null) @@ -309,6 +368,38 @@ private void OnDestroy() _updateSource.Dispose(); _updateSource = null; } + + if (_lateUpdateSource != null) + { + _lateUpdateSource.Dispose(); + _lateUpdateSource = null; + } + + if (_fixedUpdateSource != null) + { + _fixedUpdateSource.Dispose(); + _fixedUpdateSource = null; + } + + if (_eofUpdateSource != null) + { + _eofUpdateSource.Dispose(); + _eofUpdateSource = null; + } + } + + #endregion + + #region implementation + + private IEnumerator EofEnumerator() + { + yield return _eof; + + if (_eofUpdateSource != null) + { + _eofUpdateSource.OnNext(Time.deltaTime); + } } #endregion From 3195c5ac0a8eb565e3f971b975828867014ac19b Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Tue, 29 May 2018 12:35:09 +0300 Subject: [PATCH 05/67] CHANGELOG update --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c84b4a..a63d2a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/); this proj ----------------------- ## [0.9.3] - unreleased +### Added +- Added update sources for `LateUpdate`, `FixedUpdate` and end-of-frame updates. + ### Fixed - Fixed exception when removing listeners while in `AsyncUpdateSource.OnError` / `AsyncUpdateSource.OnCompleted` / `AsyncUpdateSource.Dispose`. From 981e596b07244a6ee297f0c5b77446ecc9702570 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Tue, 29 May 2018 12:53:36 +0300 Subject: [PATCH 06/67] Added Delay helpers and singleton implementation fixes --- .../UnityFx.Async/Scripts/AsyncUtility.cs | 53 +++++++++++++++---- 1 file changed, 44 insertions(+), 9 deletions(-) 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 932b922..96a00bb 100644 --- a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncUtility.cs +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncUtility.cs @@ -26,18 +26,23 @@ public static class AsyncUtility #region interface + /// + /// Name of a used by the library tools. + /// + public const string RootGoName = "UnityFx.Async"; + /// /// Returns a used by the library tools. /// public static GameObject GetRootGo() { - if (!_go) + if (ReferenceEquals(_go, null)) { - _go = GameObject.Find("UnityFx.Async"); + _go = GameObject.Find(RootGoName); - if (!_go) + if (ReferenceEquals(_go, null)) { - _go = new GameObject("UnityFx.Async"); + _go = new GameObject(RootGoName); GameObject.DontDestroyOnLoad(_go); } } @@ -77,6 +82,28 @@ public static IAsyncUpdateSource GetEndOfFrameUpdateSource() return GetCoroutineRunner().EofUpdateSource; } + /// + /// 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 IAsyncOperation Delay(int millisecondsDelay) + { + return AsyncResult.Delay(millisecondsDelay, GetCoroutineRunner().UpdateSource); + } + + /// + /// Creates an operation that completes after a time delay. + /// + /// 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 IAsyncOperation Delay(TimeSpan delay) + { + return AsyncResult.Delay(delay, GetCoroutineRunner().UpdateSource); + } + /// /// Starts a coroutine. /// @@ -425,14 +452,22 @@ private static CoroutineRunner TryGetCoroutineRunner() private static CoroutineRunner GetCoroutineRunner() { var go = GetRootGo(); - var runner = go.GetComponent(); - if (!runner) + if (go) { - runner = go.AddComponent(); - } + var runner = go.GetComponent(); - return runner; + if (!runner) + { + runner = go.AddComponent(); + } + + return runner; + } + else + { + throw new ObjectDisposedException(RootGoName); + } } #endregion From 91ed8f46e591a24bd9f1ecb3345268c152a6c057 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Tue, 29 May 2018 16:19:08 +0300 Subject: [PATCH 07/67] Changed IAsyncOperation.RemoveContinuation to never throw (even if the operation has been disposed) --- src/UnityFx.Async/Api/Core/AsyncResult.Events.cs | 6 ------ src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs | 2 -- 2 files changed, 8 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs index d38e7c2..f58ebef 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs @@ -77,8 +77,6 @@ public event AsyncCompletedEventHandler Completed } remove { - ThrowIfDisposed(); - if (value != null) { TryRemoveContinuationInternal(value); @@ -141,8 +139,6 @@ public bool TryAddContinuation(AsyncOperationCallback action, SynchronizationCon /// public bool RemoveContinuation(AsyncOperationCallback action) { - ThrowIfDisposed(); - if (action != null) { return TryRemoveContinuationInternal(action); @@ -214,8 +210,6 @@ public bool TryAddContinuation(IAsyncContinuation continuation, SynchronizationC /// public bool RemoveContinuation(IAsyncContinuation continuation) { - ThrowIfDisposed(); - if (continuation != null) { return TryRemoveContinuationInternal(continuation); diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs index dfcf542..ef73fe7 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs @@ -109,7 +109,6 @@ public interface IAsyncOperationEvents /// /// The callback to remove. Can be . /// Returns if was removed; otherwise. - /// Thrown is the operation has been disposed. /// /// bool RemoveContinuation(AsyncOperationCallback action); @@ -187,7 +186,6 @@ public interface IAsyncOperationEvents /// /// The continuation to remove. Can be . /// Returns if was removed; otherwise. - /// Thrown is the operation has been disposed. /// bool RemoveContinuation(IAsyncContinuation continuation); } From d6b5555b9eae1da8bfddd07db4ec7f582f353dba Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Tue, 29 May 2018 16:20:00 +0300 Subject: [PATCH 08/67] Minor performance optimizations of AsyncUtility methods --- src/UnityFx.Async/Api/Core/AsyncUpdateSource.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncUpdateSource.cs b/src/UnityFx.Async/Api/Core/AsyncUpdateSource.cs index 2604ba1..023d98c 100644 --- a/src/UnityFx.Async/Api/Core/AsyncUpdateSource.cs +++ b/src/UnityFx.Async/Api/Core/AsyncUpdateSource.cs @@ -190,7 +190,7 @@ public void OnNext(float frameTime) { _updating = true; - if (_updateCallbacks != null) + if (_updateCallbacks != null && _updateCallbacks.Count > 0) { foreach (var callback in _updateCallbacks) { @@ -205,7 +205,7 @@ public void OnNext(float frameTime) _updateCallbacksToRemove.Clear(); } - if (_updatables != null) + if (_updatables != null && _updatables.Count > 0) { foreach (var item in _updatables) { @@ -222,7 +222,7 @@ public void OnNext(float frameTime) #if !NET35 - if (_observers != null) + if (_observers != null && _observers.Count > 0) { foreach (var item in _observers) { @@ -255,7 +255,7 @@ public void OnCompleted() #if !NET35 - if (_observers != null) + if (_observers != null && _observers.Count > 0) { _updating = true; @@ -287,7 +287,7 @@ public void OnError(Exception e) #if !NET35 - if (_observers != null) + if (_observers != null && _observers.Count > 0)) { _updating = true; @@ -325,7 +325,7 @@ public void Dispose() #if !NET35 - if (_observers != null) + if (_observers != null && _observers.Count > 0) { _updating = true; From 16943f0893300f12e10253e336987bcc26d29edd Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Tue, 29 May 2018 19:29:37 +0300 Subject: [PATCH 09/67] Typo fix --- src/UnityFx.Async/Api/Core/AsyncUpdateSource.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncUpdateSource.cs b/src/UnityFx.Async/Api/Core/AsyncUpdateSource.cs index 023d98c..fa1721d 100644 --- a/src/UnityFx.Async/Api/Core/AsyncUpdateSource.cs +++ b/src/UnityFx.Async/Api/Core/AsyncUpdateSource.cs @@ -287,7 +287,7 @@ public void OnError(Exception e) #if !NET35 - if (_observers != null && _observers.Count > 0)) + if (_observers != null && _observers.Count > 0) { _updating = true; From 06eaf73cb997042a25cb511545a4c0328543ada3 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Tue, 29 May 2018 20:02:46 +0300 Subject: [PATCH 10/67] Push-based progress reporting pilot --- .../Api/Core/AsyncCompletionSource.cs | 7 +- .../Core/AsyncCompletionSource{TResult}.cs | 7 +- .../Api/Core/AsyncResult.Events.cs | 191 ++++++++++++++++-- src/UnityFx.Async/Api/Core/AsyncResult.cs | 15 ++ .../Api/Interfaces/IAsyncOperationEvents.cs | 108 +++++++++- .../{ => Callbacks}/AsyncContinuation.cs | 108 ++-------- .../Continuations/Callbacks/AsyncInvokable.cs | 118 +++++++++++ .../Continuations/Callbacks/AsyncProgress.cs | 47 +++++ 8 files changed, 493 insertions(+), 108 deletions(-) rename src/UnityFx.Async/Implementation/Continuations/{ => Callbacks}/AsyncContinuation.cs (55%) create mode 100644 src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncInvokable.cs create mode 100644 src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncProgress.cs diff --git a/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs b/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs index 5100035..b607c17 100644 --- a/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs +++ b/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs @@ -222,7 +222,12 @@ public bool TrySetProgress(float progress) if (Status == AsyncOperationStatus.Running) { - _progress = progress; + if (_progress != progress) + { + _progress = progress; + OnProgressChanged(); + } + return true; } diff --git a/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs b/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs index 246fcd7..9980b27 100644 --- a/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs @@ -223,7 +223,12 @@ public bool TrySetProgress(float progress) if (Status == AsyncOperationStatus.Running) { - _progress = progress; + if (_progress != progress) + { + _progress = progress; + OnProgressChanged(); + } + return true; } diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs index f58ebef..30dc680 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs @@ -47,6 +47,45 @@ internal void SetContinuationForAwait(Action continuation, SynchronizationContex #region IAsyncOperationEvents + /// + public event ProgressChangedEventHandler ProgressChanged + { + add + { + ThrowIfDisposed(); + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + +#if UNITYFX_NOT_THREAD_SAFE + + if (!TryAddContinuationInternal(value)) + { + value(this); + } + +#else + + var syncContext = SynchronizationContext.Current; + + if (!TryAddProgressCallbackInternal(value, syncContext)) + { + InvokeProgressChanged(value, syncContext); + } + +#endif + } + remove + { + if (value != null) + { + TryRemoveContinuationInternal(value); + } + } + } + /// public event AsyncCompletedEventHandler Completed { @@ -68,9 +107,11 @@ public event AsyncCompletedEventHandler Completed #else - if (!TryAddContinuationInternal(value, SynchronizationContext.Current)) + var syncContext = SynchronizationContext.Current; + + if (!TryAddContinuationInternal(value, syncContext)) { - InvokeContinuation(value, SynchronizationContext.Current); + InvokeContinuation(value, syncContext); } #endif @@ -218,16 +259,85 @@ public bool RemoveContinuation(IAsyncContinuation continuation) return false; } +#if !NET35 + + /// + public void AddProgressCallback(IProgress callback) + { + if (!TryAddProgressCallback(callback)) + { + InvokeProgressChanged(callback, SynchronizationContext.Current); + } + } + + /// + public bool TryAddProgressCallback(IProgress callback) + { + ThrowIfDisposed(); + + if (callback == null) + { + throw new ArgumentNullException(nameof(callback)); + } + +#if UNITYFX_NOT_THREAD_SAFE + + return TryAddContinuationInternal(continuation); + +#else + + return TryAddProgressCallbackInternal(callback, SynchronizationContext.Current); + +#endif + } + + /// + public void AddProgressCallback(IProgress callback, SynchronizationContext syncContext) + { + if (!TryAddProgressCallback(callback, syncContext)) + { + InvokeProgressChanged(callback, syncContext); + } + } + + /// + public bool TryAddProgressCallback(IProgress callback, SynchronizationContext syncContext) + { + ThrowIfDisposed(); + + if (callback == null) + { + throw new ArgumentNullException(nameof(callback)); + } + +#if UNITYFX_NOT_THREAD_SAFE + + return TryAddContinuationInternal(continuation); + +#else + + return TryAddProgressCallbackInternal(callback, syncContext); + +#endif + } + + /// + public bool RemoveProgressCallback(IProgress callback) + { + if (callback != null) + { + return TryRemoveContinuationInternal(callback); + } + + return false; + } + +#endif + #endregion #region implementation - /// - /// 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; @@ -240,6 +350,16 @@ private bool TryAddContinuationInternal(object continuation, SynchronizationCont return TryAddContinuationInternal(continuation); } + private bool TryAddProgressCallbackInternal(object callback, SynchronizationContext syncContext) + { + if (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) + { + callback = new AsyncProgress(this, syncContext, callback); + } + + return TryAddContinuationInternal(callback); + } + #if UNITYFX_NOT_THREAD_SAFE /// @@ -416,6 +536,29 @@ private bool TryRemoveContinuationInternal(object valueToRemove) return false; } + private void InvokeProgressChanged() + { + var continuation = _continuation; + + if (continuation != null) + { + if (continuation is IEnumerable continuationList) + { + lock (continuationList) + { + foreach (var item in continuationList) + { + InvokeProgressChanged(item); + } + } + } + else + { + InvokeProgressChanged(continuation); + } + } + } + private void InvokeContinuations() { #if UNITYFX_NOT_THREAD_SAFE @@ -428,12 +571,12 @@ private void InvokeContinuations() { foreach (var item in continuationList) { - AsyncContinuation.InvokeInline(this, item); + InvokeContinuationInline(item, false); } } else { - AsyncContinuation.InvokeInline(this, continuation); + InvokeContinuationInline(continuation, false); } } @@ -462,13 +605,37 @@ private void InvokeContinuations() #endif } + private void InvokeProgressChanged(object continuation) + { + if (continuation is AsyncProgress p) + { + p.Invoke(); + } + else + { + AsyncProgress.InvokeInline(this, continuation); + } + } + + private void InvokeProgressChanged(object continuation, SynchronizationContext syncContext) + { + if (syncContext == null || syncContext == SynchronizationContext.Current) + { + AsyncProgress.InvokeInline(this, continuation); + } + else + { + syncContext.Post(args => AsyncProgress.InvokeInline(this, args), continuation); + } + } + private void InvokeContinuation(object continuation) { var runContinuationsAsynchronously = (_flags & _flagRunContinuationsAsynchronously) != 0; if (runContinuationsAsynchronously) { - if (continuation is AsyncContinuation c) + if (continuation is AsyncInvokable c) { // NOTE: This is more effective than InvokeContinuationAsync(). c.InvokeAsync(); @@ -480,7 +647,7 @@ private void InvokeContinuation(object continuation) } else { - if (continuation is AsyncContinuation c) + if (continuation is AsyncInvokable c) { c.Invoke(); } diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 8deb5c3..8f3fb2d 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -521,11 +521,26 @@ protected internal void ThrowIfDisposed() /// Make sure that each method call returns a value greater or equal to the previous. It is important for /// progress reporting consistency. /// + /// + /// protected virtual float GetProgress() { return 0; } + /// + /// Called when the progress value has changed. Default implementation calls progress changed handlers. + /// + /// + /// It is user responsibility to call this method when the operation progress value changes. + /// + /// + /// + protected virtual void OnProgressChanged() + { + InvokeProgressChanged(); + } + /// /// Called when the operation state has changed. Default implementation does nothing. /// diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs index ef73fe7..abbe3db 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs @@ -22,6 +22,19 @@ namespace UnityFx.Async /// public interface IAsyncOperationEvents { + /// + /// Raised when the operation progress has changed. + /// + /// + /// The event handler is invoked on a thread that registered the continuation (if it has a attached). + /// If the operation is already completed the event handler is called synchronously. Throwing an exception from the callback registered + /// might cause unspecified behaviour. + /// + /// Thrown if the delegate being registered is . + /// Thrown is the operation has been disposed. + /// + event ProgressChangedEventHandler ProgressChanged; + /// /// Raised when the operation has completed. /// @@ -32,6 +45,7 @@ public interface IAsyncOperationEvents /// /// Thrown if the delegate being registered is . /// Thrown is the operation has been disposed. + /// /// /// /// @@ -146,7 +160,8 @@ public interface IAsyncOperationEvents bool TryAddContinuation(IAsyncContinuation continuation); /// - /// Adds a continuation to be executed after the operation has completed. If the operation is completed is invoked synchronously. + /// Adds a continuation to be executed after the operation has completed. If the operation is completed + /// is invoked on the specified. /// /// /// The is invoked on a specified. Throwing an exception from the callback might cause unspecified behaviour. @@ -186,7 +201,98 @@ public interface IAsyncOperationEvents /// /// The continuation to remove. Can be . /// Returns if was removed; otherwise. + /// + /// /// + /// bool RemoveContinuation(IAsyncContinuation continuation); + +#if !NET35 + + /// + /// Adds a callback to be executed each time progress value changes. If the operation is completed is invoked synchronously. + /// + /// + /// The is invoked on a thread that registered it (if it has a attached). + /// Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The callback to be executed when the operation progress value has changed. + /// Thrown if is . + /// Thrown is the operation has been disposed. + /// + /// + /// + /// + void AddProgressCallback(IProgress callback); + + /// + /// Adds a callback to be executed each time progress value changes. If the operation is completed + /// is invoked on the specified. + /// + /// + /// The is invoked on a specified. Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The callback to be executed when the operation progress value has changed. + /// 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 AddProgressCallback(IProgress callback, SynchronizationContext syncContext); + + /// + /// Attempts to add a progress callback to be executed each time progress value changes. If the operation is already completed + /// the method does nothing and just returns . + /// + /// + /// The is invoked on a thread that registered the continuation (if it has a attached). + /// Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The callback to be executed when the operation progress value has changed. + /// Returns if the callback was added; otherwise (the operation is completed). + /// Thrown if is . + /// Thrown is the operation has been disposed. + /// + /// + /// + /// + bool TryAddProgressCallback(IProgress callback); + + /// + /// Attempts to add a progress callback to be executed each time progress value changes. If the operation is already completed + /// the method does nothing and just returns . + /// + /// + /// The is invoked on a specified. Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The callback to be executed when the operation progress value has changed. + /// If not method attempts to marshal the callback 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 TryAddProgressCallback(IProgress callback, SynchronizationContext syncContext); + + /// + /// Removes an existing progress callback. + /// + /// The callback to remove. Can be . + /// Returns if was removed; otherwise. + /// + /// + /// + /// + bool RemoveProgressCallback(IProgress callback); + +#endif } } diff --git a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs b/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncContinuation.cs similarity index 55% rename from src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs rename to src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncContinuation.cs index 50fb284..a99b475 100644 --- a/src/UnityFx.Async/Implementation/Continuations/AsyncContinuation.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncContinuation.cs @@ -11,64 +11,13 @@ namespace UnityFx.Async { - internal class AsyncContinuation + internal class AsyncContinuation : AsyncInvokable { - #region data - - private static WaitCallback _waitCallback; - private static SendOrPostCallback _postCallback; - - private IAsyncOperation _op; - private SynchronizationContext _syncContext; - private object _continuation; - - #endregion - #region interface internal AsyncContinuation(IAsyncOperation op, SynchronizationContext syncContext, object continuation) + : base(op, syncContext, continuation) { - _op = op; - _syncContext = syncContext; - _continuation = continuation; - } - - 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, false); - } - else - { - InvokeOnSyncContext(_syncContext); - } } internal static bool CanInvoke(IAsyncOperation op, AsyncContinuationOptions options) @@ -86,15 +35,16 @@ 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 InvokeInline(IAsyncOperation op, object continuation, bool inline) { switch (continuation) { +#if !NET35 + case IProgress p: + p.Report(op.Progress); + break; +#endif + case IAsyncContinuation c: c.Invoke(op, inline); break; @@ -114,6 +64,10 @@ internal static void InvokeInline(IAsyncOperation op, object continuation, bool case AsyncCompletedEventHandler eh: eh.Invoke(op, new AsyncCompletedEventArgs(op.Exception, op.IsCanceled, op.AsyncState)); break; + + case ProgressChangedEventHandler ph: + ph.Invoke(op, new ProgressChangedEventArgs((int)(op.Progress * 100), op.AsyncState)); + break; } } @@ -159,43 +113,11 @@ internal static void InvokeTaskContinuation(IAsyncOperation op, TaskComple #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 void InvokeOnSyncContext(SynchronizationContext syncContext) - { - Debug.Assert(_syncContext != null); - - if (_postCallback == null) - { - _postCallback = PostCallback; - } - - syncContext.Post(_postCallback, this); - } + #region AsyncInvokable - private static void PostCallback(object args) + protected override void Invoke(IAsyncOperation op, object continuation) { - var c = args as AsyncContinuation; - InvokeInline(c._op, c._continuation, false); + InvokeInline(op, continuation, false); } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncInvokable.cs b/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncInvokable.cs new file mode 100644 index 0000000..6a78938 --- /dev/null +++ b/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncInvokable.cs @@ -0,0 +1,118 @@ +// Copyright (c) Alexander Bogarsukov. +// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Threading; + +namespace UnityFx.Async +{ + internal abstract class AsyncInvokable + { + #region data + + private static WaitCallback _waitCallback; + private static SendOrPostCallback _postCallback; + + private IAsyncOperation _op; + private SynchronizationContext _syncContext; + private object _callback; + + #endregion + + #region interface + + internal AsyncInvokable(IAsyncOperation op, SynchronizationContext syncContext, object callback) + { + _op = op; + _syncContext = syncContext; + _callback = callback; + } + + internal void Invoke() + { + if (_syncContext == null || _syncContext == SynchronizationContext.Current) + { + Invoke(_op, _callback); + } + else + { + InvokeOnSyncContext(_syncContext); + } + } + + internal void InvokeAsync() + { + var syncContext = _syncContext; + + if (syncContext != null) + { + InvokeOnSyncContext(syncContext); + } + else + { + syncContext = SynchronizationContext.Current; + + if (syncContext != null) + { + InvokeOnSyncContext(syncContext); + } + else + { + if (_waitCallback == null) + { + _waitCallback = PostCallback; + } + + ThreadPool.QueueUserWorkItem(_waitCallback, this); + } + } + } + + protected abstract void Invoke(IAsyncOperation op, object callback); + + #endregion + + #region Object + + public override bool Equals(object obj) + { + if (ReferenceEquals(obj, _callback)) + { + return true; + } + + return base.Equals(obj); + } + + public override int GetHashCode() + { + return _callback.GetHashCode(); + } + + #endregion + + #region implementation + + private void InvokeOnSyncContext(SynchronizationContext syncContext) + { + Debug.Assert(_syncContext != null); + + if (_postCallback == null) + { + _postCallback = PostCallback; + } + + syncContext.Post(_postCallback, this); + } + + private static void PostCallback(object args) + { + var c = args as AsyncInvokable; + c.Invoke(c._op, c._callback); + } + + #endregion + } +} diff --git a/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncProgress.cs b/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncProgress.cs new file mode 100644 index 0000000..f1b9f7a --- /dev/null +++ b/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncProgress.cs @@ -0,0 +1,47 @@ +// Copyright (c) Alexander Bogarsukov. +// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Threading; + +namespace UnityFx.Async +{ + internal class AsyncProgress : AsyncInvokable + { + #region interface + + internal AsyncProgress(IAsyncOperation op, SynchronizationContext syncContext, object callback) + : base(op, syncContext, callback) + { + } + + internal static void InvokeInline(IAsyncOperation op, object callback) + { + switch (callback) + { +#if !NET35 + case IProgress p: + p.Report(op.Progress); + break; +#endif + + case ProgressChangedEventHandler ph: + ph.Invoke(op, new ProgressChangedEventArgs((int)(op.Progress * 100), op.AsyncState)); + break; + } + } + + #endregion + + #region AsyncInvokable + + protected override void Invoke(IAsyncOperation op, object continuation) + { + InvokeInline(op, continuation); + } + + #endregion + } +} From b4be1ca5c2ce06c50ec6c83f2d0541a7402cfa4c Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 30 May 2018 10:58:19 +0300 Subject: [PATCH 11/67] Removed UNITYFX_NOT_THREAD_SAFE define --- .../Api/Core/AsyncResult.Events.cs | 211 ++---------------- src/UnityFx.Async/Api/Core/AsyncResult.cs | 105 --------- .../Api/Extensions/AsyncExtensions.Tasks.cs | 16 -- .../Api/Extensions/AsyncExtensions.Wait.cs | 94 -------- .../Callbacks/AsyncContinuation.cs | 12 +- 5 files changed, 27 insertions(+), 411 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs index 30dc680..062f451 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs @@ -16,16 +16,8 @@ partial class AsyncResult private static readonly object _continuationCompletionSentinel = new object(); -#if UNITYFX_NOT_THREAD_SAFE - - private object _continuation; - -#else - private volatile object _continuation; -#endif - #endregion #region internals @@ -59,23 +51,12 @@ public event ProgressChangedEventHandler ProgressChanged throw new ArgumentNullException(nameof(value)); } -#if UNITYFX_NOT_THREAD_SAFE - - if (!TryAddContinuationInternal(value)) - { - value(this); - } - -#else - var syncContext = SynchronizationContext.Current; if (!TryAddProgressCallbackInternal(value, syncContext)) { InvokeProgressChanged(value, syncContext); } - -#endif } remove { @@ -98,23 +79,12 @@ public event AsyncCompletedEventHandler Completed throw new ArgumentNullException(nameof(value)); } -#if UNITYFX_NOT_THREAD_SAFE - - if (!TryAddContinuationInternal(value)) - { - value(this); - } - -#else - var syncContext = SynchronizationContext.Current; if (!TryAddContinuationInternal(value, syncContext)) { InvokeContinuation(value, syncContext); } - -#endif } remove { @@ -144,15 +114,7 @@ public bool TryAddContinuation(AsyncOperationCallback action) throw new ArgumentNullException(nameof(action)); } -#if UNITYFX_NOT_THREAD_SAFE - - return TryAddContinuationInternal(action); - -#else - return TryAddContinuationInternal(action, SynchronizationContext.Current); - -#endif } /// @@ -207,15 +169,7 @@ public bool TryAddContinuation(IAsyncContinuation continuation) throw new ArgumentNullException(nameof(continuation)); } -#if UNITYFX_NOT_THREAD_SAFE - - return TryAddContinuationInternal(continuation); - -#else - return TryAddContinuationInternal(continuation, SynchronizationContext.Current); - -#endif } /// @@ -237,15 +191,7 @@ public bool TryAddContinuation(IAsyncContinuation continuation, SynchronizationC throw new ArgumentNullException(nameof(continuation)); } -#if UNITYFX_NOT_THREAD_SAFE - - return TryAddContinuationInternal(continuation); - -#else - return TryAddContinuationInternal(continuation, syncContext); - -#endif } /// @@ -280,15 +226,7 @@ public bool TryAddProgressCallback(IProgress callback) throw new ArgumentNullException(nameof(callback)); } -#if UNITYFX_NOT_THREAD_SAFE - - return TryAddContinuationInternal(continuation); - -#else - return TryAddProgressCallbackInternal(callback, SynchronizationContext.Current); - -#endif } /// @@ -310,15 +248,7 @@ public bool TryAddProgressCallback(IProgress callback, SynchronizationCon throw new ArgumentNullException(nameof(callback)); } -#if UNITYFX_NOT_THREAD_SAFE - - return TryAddContinuationInternal(continuation); - -#else - return TryAddProgressCallbackInternal(callback, syncContext); - -#endif } /// @@ -360,75 +290,6 @@ private bool TryAddProgressCallbackInternal(object callback, SynchronizationCont return TryAddContinuationInternal(callback); } -#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. @@ -481,11 +342,6 @@ private bool TryAddContinuationInternal(object valueToAdd) 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. @@ -559,29 +415,32 @@ private void InvokeProgressChanged() } } - private void InvokeContinuations() + private void InvokeProgressChanged(object callback) { -#if UNITYFX_NOT_THREAD_SAFE - - var continuation = _continuation; - - if (continuation != null) + if (callback is AsyncProgress p) { - if (continuation is IEnumerable continuationList) - { - foreach (var item in continuationList) - { - InvokeContinuationInline(item, false); - } - } - else - { - InvokeContinuationInline(continuation, false); - } + p.Invoke(); } + else + { + AsyncProgress.InvokeInline(this, callback); + } + } -#else + private void InvokeProgressChanged(object callback, SynchronizationContext syncContext) + { + if (syncContext == null || syncContext == SynchronizationContext.Current) + { + AsyncProgress.InvokeInline(this, callback); + } + else + { + syncContext.Post(args => AsyncProgress.InvokeInline(this, args), callback); + } + } + private void InvokeContinuations() + { var continuation = Interlocked.Exchange(ref _continuation, _continuationCompletionSentinel); if (continuation != null) @@ -601,32 +460,6 @@ private void InvokeContinuations() InvokeContinuation(continuation); } } - -#endif - } - - private void InvokeProgressChanged(object continuation) - { - if (continuation is AsyncProgress p) - { - p.Invoke(); - } - else - { - AsyncProgress.InvokeInline(this, continuation); - } - } - - private void InvokeProgressChanged(object continuation, SynchronizationContext syncContext) - { - if (syncContext == null || syncContext == SynchronizationContext.Current) - { - AsyncProgress.InvokeInline(this, continuation); - } - else - { - syncContext.Post(args => AsyncProgress.InvokeInline(this, args), continuation); - } } private void InvokeContinuation(object continuation) @@ -691,8 +524,6 @@ private void InvokeContinuationInline(object continuation, bool inline) AsyncContinuation.InvokeInline(this, continuation, inline); } -#endif - #endregion } } diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 8f3fb2d..87128b3 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -64,18 +64,9 @@ public partial class AsyncResult : IAsyncOperation, IEnumerator private readonly object _asyncState; private Exception _exception; - -#if UNITYFX_NOT_THREAD_SAFE - - private int _flags; - -#else - private EventWaitHandle _waitHandle; private volatile int _flags; -#endif - #endregion #region interface @@ -588,12 +579,6 @@ protected virtual void OnCancel() /// protected virtual void OnCompleted() { -#if UNITYFX_NOT_THREAD_SAFE - - InvokeContinuations(); - -#else - try { InvokeContinuations(); @@ -602,8 +587,6 @@ protected virtual void OnCompleted() { _waitHandle?.Set(); } - -#endif } /// @@ -621,8 +604,6 @@ protected virtual void Dispose(bool disposing) { _flags |= _flagDisposed; -#if !UNITYFX_NOT_THREAD_SAFE - if (_waitHandle != null) { #if NET35 @@ -632,8 +613,6 @@ protected virtual void Dispose(bool disposing) #endif _waitHandle = null; } - -#endif } } @@ -658,28 +637,6 @@ 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; @@ -705,8 +662,6 @@ internal bool TrySetStatus(int newStatus) } } while (true); - -#endif } /// @@ -724,22 +679,6 @@ 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; @@ -759,8 +698,6 @@ internal bool TrySetCompleted(int status, bool completedSynchronously) } } while (true); - -#endif } /// @@ -768,19 +705,6 @@ 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; @@ -796,8 +720,6 @@ internal bool TryReserveCompletion() } } while (true); - -#endif } /// @@ -805,19 +727,6 @@ internal bool TryReserveCompletion() /// 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; @@ -833,8 +742,6 @@ internal bool TrySetFlag(int newFlag) } } while (true); - -#endif } /// @@ -856,11 +763,7 @@ internal void SetCompleted(int status, bool completedSynchronously) } // 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); @@ -999,12 +902,6 @@ public WaitHandle AsyncWaitHandle { get { -#if UNITYFX_NOT_THREAD_SAFE - - throw new NotSupportedException(); - -#else - ThrowIfDisposed(); if (_waitHandle == null) @@ -1030,8 +927,6 @@ public WaitHandle AsyncWaitHandle } return _waitHandle; - -#endif } } diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs index fbfbf51..5984a6f 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs @@ -154,15 +154,7 @@ 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 } /// @@ -172,15 +164,7 @@ 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 } /// diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs index 94d5cd1..f1fd80e 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Wait.cs @@ -19,19 +19,11 @@ 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 - AsyncResult.ThrowIfNonSuccess(op); } @@ -47,12 +39,6 @@ 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) @@ -60,8 +46,6 @@ public static bool Wait(this IAsyncOperation op, int millisecondsTimeout) result = op.AsyncWaitHandle.WaitOne(millisecondsTimeout); } -#endif - if (result) { AsyncResult.ThrowIfNonSuccess(op); @@ -82,12 +66,6 @@ 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) @@ -95,8 +73,6 @@ public static bool Wait(this IAsyncOperation op, TimeSpan timeout) result = op.AsyncWaitHandle.WaitOne(timeout); } -#endif - if (result) { AsyncResult.ThrowIfNonSuccess(op); @@ -186,19 +162,11 @@ public static bool Wait(this IAsyncOperation op, TimeSpan timeout, CancellationT /// public static void Join(this IAsyncOperation op) { -#if UNITYFX_NOT_THREAD_SAFE - - SpinUntilCompleted(op); - -#else - if (!op.IsCompleted) { op.AsyncWaitHandle.WaitOne(); } -#endif - AsyncResult.ThrowIfNonSuccess(op); } @@ -215,12 +183,6 @@ 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) @@ -228,8 +190,6 @@ public static void Join(this IAsyncOperation op, int millisecondsTimeout) result = op.AsyncWaitHandle.WaitOne(millisecondsTimeout); } -#endif - if (result) { AsyncResult.ThrowIfNonSuccess(op); @@ -253,12 +213,6 @@ 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) @@ -266,8 +220,6 @@ public static void Join(this IAsyncOperation op, TimeSpan timeout) result = op.AsyncWaitHandle.WaitOne(timeout); } -#endif - if (result) { AsyncResult.ThrowIfNonSuccess(op); @@ -289,19 +241,11 @@ 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 - return op.Result; } @@ -319,12 +263,6 @@ 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) @@ -332,8 +270,6 @@ public static TResult Join(this IAsyncOperation op, int millis result = op.AsyncWaitHandle.WaitOne(millisecondsTimeout); } -#endif - if (!result) { throw new TimeoutException(); @@ -356,12 +292,6 @@ 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) @@ -369,8 +299,6 @@ public static TResult Join(this IAsyncOperation op, TimeSpan t result = op.AsyncWaitHandle.WaitOne(timeout); } -#endif - if (!result) { throw new TimeoutException(); @@ -516,12 +444,6 @@ public static TResult Join(this IAsyncOperation op, TimeSpan t private static void WaitInternal(IAsyncOperation op, CancellationToken cancellationToken) { -#if UNITYFX_NOT_THREAD_SAFE - - SpinUntilCompleted(op, cancellationToken); - -#else - if (!op.IsCompleted) { if (cancellationToken.CanBeCanceled) @@ -540,18 +462,10 @@ private static void WaitInternal(IAsyncOperation op, CancellationToken cancellat 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) @@ -578,17 +492,10 @@ private static bool WaitInternal(IAsyncOperation op, int millisecondsTimeout, Ca } 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) @@ -615,7 +522,6 @@ private static bool WaitInternal(IAsyncOperation op, TimeSpan timeout, Cancellat } return result; -#endif } #endif diff --git a/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncContinuation.cs b/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncContinuation.cs index a99b475..11d9aae 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncContinuation.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncContinuation.cs @@ -39,12 +39,6 @@ internal static void InvokeInline(IAsyncOperation op, object continuation, bool { switch (continuation) { -#if !NET35 - case IProgress p: - p.Report(op.Progress); - break; -#endif - case IAsyncContinuation c: c.Invoke(op, inline); break; @@ -65,6 +59,12 @@ internal static void InvokeInline(IAsyncOperation op, object continuation, bool eh.Invoke(op, new AsyncCompletedEventArgs(op.Exception, op.IsCanceled, op.AsyncState)); break; +#if !NET35 + case IProgress p: + p.Report(op.Progress); + break; +#endif + case ProgressChangedEventHandler ph: ph.Invoke(op, new ProgressChangedEventArgs((int)(op.Progress * 100), op.AsyncState)); break; From 885d97343c80ab91469325018e58a1cb33aa87b6 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 30 May 2018 11:03:16 +0300 Subject: [PATCH 12/67] Several renames --- .../Api/Core/AsyncResult.Events.cs | 52 +++++++++---------- src/UnityFx.Async/Api/Core/AsyncResult.cs | 14 ++--- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs index 062f451..87cf9c9 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs @@ -14,9 +14,9 @@ partial class AsyncResult { #region data - private static readonly object _continuationCompletionSentinel = new object(); + private static readonly object _callbackCompletionSentinel = new object(); - private volatile object _continuation; + private volatile object _callback; #endregion @@ -62,7 +62,7 @@ public event ProgressChangedEventHandler ProgressChanged { if (value != null) { - TryRemoveContinuationInternal(value); + TryRemoveCallback(value); } } } @@ -90,7 +90,7 @@ public event AsyncCompletedEventHandler Completed { if (value != null) { - TryRemoveContinuationInternal(value); + TryRemoveCallback(value); } } } @@ -144,7 +144,7 @@ public bool RemoveContinuation(AsyncOperationCallback action) { if (action != null) { - return TryRemoveContinuationInternal(action); + return TryRemoveCallback(action); } return false; @@ -199,7 +199,7 @@ public bool RemoveContinuation(IAsyncContinuation continuation) { if (continuation != null) { - return TryRemoveContinuationInternal(continuation); + return TryRemoveCallback(continuation); } return false; @@ -256,7 +256,7 @@ public bool RemoveProgressCallback(IProgress callback) { if (callback != null) { - return TryRemoveContinuationInternal(callback); + return TryRemoveCallback(callback); } return false; @@ -277,7 +277,7 @@ private bool TryAddContinuationInternal(object continuation, SynchronizationCont continuation = new AsyncContinuation(this, syncContext, continuation); } - return TryAddContinuationInternal(continuation); + return TryAddCallback(continuation); } private bool TryAddProgressCallbackInternal(object callback, SynchronizationContext syncContext) @@ -287,21 +287,21 @@ private bool TryAddProgressCallbackInternal(object callback, SynchronizationCont callback = new AsyncProgress(this, syncContext, callback); } - return TryAddContinuationInternal(callback); + return TryAddCallback(callback); } - private bool TryAddContinuationInternal(object valueToAdd) + private bool TryAddCallback(object callbackToAdd) { // NOTE: The code below is adapted from https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs. - var oldValue = _continuation; + var oldValue = _callback; // Quick return if the operation is completed. - if (oldValue != _continuationCompletionSentinel) + if (oldValue != _callbackCompletionSentinel) { // If no continuation is stored yet, try to store it as _continuation. if (oldValue == null) { - oldValue = Interlocked.CompareExchange(ref _continuation, valueToAdd, null); + oldValue = Interlocked.CompareExchange(ref _callback, callbackToAdd, null); // Quick return if exchange succeeded. if (oldValue == null) @@ -311,11 +311,11 @@ private bool TryAddContinuationInternal(object valueToAdd) } // Logic for the case where we were previously storing a single continuation. - if (oldValue != _continuationCompletionSentinel && !(oldValue is IList)) + if (oldValue != _callbackCompletionSentinel && !(oldValue is IList)) { var newList = new List() { oldValue }; - Interlocked.CompareExchange(ref _continuation, newList, oldValue); + Interlocked.CompareExchange(ref _callback, 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. @@ -324,15 +324,15 @@ private bool TryAddContinuationInternal(object valueToAdd) // 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) + if (_callback 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) + if (_callback != _callbackCompletionSentinel) { - list.Add(valueToAdd); + list.Add(callbackToAdd); return true; } } @@ -342,12 +342,12 @@ private bool TryAddContinuationInternal(object valueToAdd) return false; } - private bool TryRemoveContinuationInternal(object valueToRemove) + private bool TryRemoveCallback(object callbackToRemove) { // NOTE: The code below is adapted from https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs. - var value = _continuation; + var value = _callback; - if (value != _continuationCompletionSentinel) + if (value != _callbackCompletionSentinel) { var list = value as IList; @@ -355,7 +355,7 @@ private bool TryRemoveContinuationInternal(object valueToRemove) { // 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) + if (Interlocked.CompareExchange(ref _callback, new List(), callbackToRemove) == callbackToRemove) { return true; } @@ -375,9 +375,9 @@ private bool TryRemoveContinuationInternal(object valueToRemove) { // 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) + if (_callback != _callbackCompletionSentinel) { - var index = list.IndexOf(valueToRemove); + var index = list.IndexOf(callbackToRemove); if (index != -1) { @@ -394,7 +394,7 @@ private bool TryRemoveContinuationInternal(object valueToRemove) private void InvokeProgressChanged() { - var continuation = _continuation; + var continuation = _callback; if (continuation != null) { @@ -441,7 +441,7 @@ private void InvokeProgressChanged(object callback, SynchronizationContext syncC private void InvokeContinuations() { - var continuation = Interlocked.Exchange(ref _continuation, _continuationCompletionSentinel); + var continuation = Interlocked.Exchange(ref _callback, _callbackCompletionSentinel); if (continuation != null) { diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 87128b3..6f4e0ba 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -113,7 +113,7 @@ public AsyncResult(AsyncCreationOptions options) public AsyncResult(AsyncCallback asyncCallback, object asyncState) { _asyncState = asyncState; - _continuation = asyncCallback; + _callback = asyncCallback; } /// @@ -126,7 +126,7 @@ public AsyncResult(AsyncCallback asyncCallback, object asyncState, AsyncCreation : this((int)options << _optionsOffset) { _asyncState = asyncState; - _continuation = asyncCallback; + _callback = asyncCallback; } /// @@ -181,7 +181,7 @@ public AsyncResult(AsyncOperationStatus status, AsyncCallback asyncCallback, obj : this((int)status) { _asyncState = asyncState; - _continuation = asyncCallback; + _callback = asyncCallback; } /// @@ -195,7 +195,7 @@ public AsyncResult(AsyncOperationStatus status, AsyncCallback asyncCallback, obj : this((int)status | ((int)options << _optionsOffset)) { _asyncState = asyncState; - _continuation = asyncCallback; + _callback = asyncCallback; } /// @@ -217,7 +217,7 @@ internal AsyncResult(Exception exception, object asyncState) _flags = StatusFaulted | _flagCompletedSynchronously; } - _continuation = _continuationCompletionSentinel; + _callback = _callbackCompletionSentinel; _asyncState = asyncState; } @@ -245,7 +245,7 @@ internal AsyncResult(IEnumerable exceptions, object asyncState) _flags = StatusFaulted | _flagCompletedSynchronously; } - _continuation = _continuationCompletionSentinel; + _callback = _callbackCompletionSentinel; _asyncState = asyncState; } @@ -1035,7 +1035,7 @@ private AsyncResult(int flags) if (flags > StatusRunning) { - _continuation = _continuationCompletionSentinel; + _callback = _callbackCompletionSentinel; _flags = flags | _flagCompletedSynchronously; } else From b6363390a1aa7b480b15fef5b90417970d38ba67 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 30 May 2018 11:05:21 +0300 Subject: [PATCH 13/67] CHANGELOG update --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a63d2a7..aff0047 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/); this proj ## [0.9.3] - unreleased ### Added +- Added push-based progress reporting support. - Added update sources for `LateUpdate`, `FixedUpdate` and end-of-frame updates. ### Fixed @@ -16,13 +17,12 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/); this proj ## [0.9.2] - 2018.05.25 ### Added -- Added pull-based progress support (`IAsyncOperation.Progress`). +- Added pull-based progress reporting support. - Added new methods to `IAsyncUpdateSource`. - Added `AsyncUpdateSource` class as default `IAsyncUpdateSource` implementation. - `IAsyncOperation` now inherits `IObservable`. ### Changed -- Renamed `(Try)AddCompletionCallback`/`RemoveCompletionCallback` methods to `(Try)AddContinuation`/`RemoveContinuation`. - Changed `IAsyncOperation.Exception` type to `Exception`. - Changed `IAsyncOperationEvents.Completed` type to `AsyncCompletedEventHandler`. From ed30ed104b8ea20e8caaa8b8c87044f6cc0901dc Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 30 May 2018 11:09:28 +0300 Subject: [PATCH 14/67] Renamed methods XxxContinuation() to XxxCompletionCallback() for IAsyncOperationEvents --- ...xtureHelper.UseCases.CompletionCallback.cs | 2 +- .../Tests/CompletionCallbackTests.cs | 20 ++--- .../Tests/CompletionSourceTests.cs | 8 +- .../Api/Core/AsyncResult.Events.cs | 28 +++---- .../Api/Core/AsyncResultQueue{T}.cs | 8 +- .../Api/Core/AsyncResult{TResult}.cs | 2 +- .../Extensions/AsyncExtensions.Promises.cs | 8 +- .../Api/Extensions/AsyncExtensions.Tasks.cs | 6 +- .../Api/Interfaces/IAsyncOperationEvents.cs | 82 +++++++++---------- .../Continuations/ContinueWithResult{T,U}.cs | 4 +- .../Promises/CatchResult{T,TException}.cs | 2 +- .../Promises/FinallyResult{T}.cs | 6 +- .../Promises/RebindResult{T,U}.cs | 2 +- .../Promises/ThenAllResult{T,U}.cs | 2 +- .../Promises/ThenAnyResult{T,U}.cs | 2 +- .../Continuations/Promises/ThenResult{T,U}.cs | 10 +-- .../Continuations/UnwrapResult{T}.cs | 4 +- .../AsyncObservableSubscription{T}.cs | 2 +- .../Specialized/RetryResult{T}.cs | 2 +- .../Specialized/WhenAllResult{T}.cs | 2 +- .../Specialized/WhenAnyResult{T}.cs | 2 +- 21 files changed, 102 insertions(+), 102 deletions(-) diff --git a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/Samples/LoadTextureHelper.UseCases.CompletionCallback.cs b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/Samples/LoadTextureHelper.UseCases.CompletionCallback.cs index d534ec2..c117c76 100644 --- a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/Samples/LoadTextureHelper.UseCases.CompletionCallback.cs +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/Samples/LoadTextureHelper.UseCases.CompletionCallback.cs @@ -12,7 +12,7 @@ partial class LoadTextureHelper /// public void WaitForLoadOperationInCompletionCallback(string textureUrl) { - LoadTextureAsync(textureUrl).AddContinuation(op => + LoadTextureAsync(textureUrl).AddCompletionCallback(op => { if (op.IsCompletedSuccessfully) { diff --git a/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs b/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs index ea60e59..f846fbb 100644 --- a/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs +++ b/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs @@ -19,7 +19,7 @@ public void TryAddCompletionCallback_FailsIfOperationIsCompleted() op.SetCanceled(); // Act - var result = op.TryAddContinuation(_ => { }, null); + var result = op.TryAddCompletionCallback(_ => { }, null); // Assert Assert.False(result); @@ -32,7 +32,7 @@ public void TryAddCompletionCallback_FailsIfOperationIsCompletedSynchronously() var op = AsyncResult.CompletedOperation; // Act - var result = op.TryAddContinuation(_ => { }, null); + var result = op.TryAddCompletionCallback(_ => { }, null); // Assert Assert.False(result); @@ -45,7 +45,7 @@ public async Task TryAddCompletionCallback_ExecutesWhenOperationCompletes() var op = AsyncResult.Delay(1); var callbackCalled = false; - op.TryAddContinuation(_ => callbackCalled = true, null); + op.TryAddCompletionCallback(_ => callbackCalled = true, null); // Act await op; @@ -71,7 +71,7 @@ void TestMethod() { for (var i = 0; i < 10000; ++i) { - op.TryAddContinuation(d); + op.TryAddCompletionCallback(d); } } @@ -79,7 +79,7 @@ void TestMethod2() { for (var i = 0; i < 10000; ++i) { - op.RemoveContinuation(d); + op.RemoveCompletionCallback(d); } } @@ -103,7 +103,7 @@ public void TryAddContinuation_ExecutesWhenOperationCompletes() // Arrange var op = new AsyncCompletionSource(); var continuation = Substitute.For(); - op.TryAddContinuation(continuation); + op.TryAddCompletionCallback(continuation); // Act op.SetCompleted(); @@ -121,7 +121,7 @@ public void AddContinuation_ExecutesIfOperationIsCompletedSynchronously() var continuation = Substitute.For(); // Act - op.AddContinuation(continuation); + op.AddCompletionCallback(continuation); // Assert continuation.Received(1).Invoke(op, true); @@ -136,7 +136,7 @@ public void AddCompletionCallback_ExecutesIfOperationIsCompletedSynchronously() var callbackCalled = false; // Act - op.AddContinuation(_ => callbackCalled = true, null); + op.AddCompletionCallback(_ => callbackCalled = true, null); // Assert Assert.True(callbackCalled); @@ -152,8 +152,8 @@ public async Task TryAddCompletionCallback_ContinuationsAreRunOnCorrectSynchrono var tid = 0; var tidActual = 0; - op.TryAddContinuation(_ => { }, sc); - op2.TryAddContinuation(_ => tidActual = Thread.CurrentThread.ManagedThreadId, null); + op.TryAddCompletionCallback(_ => { }, sc); + op2.TryAddCompletionCallback(_ => tidActual = Thread.CurrentThread.ManagedThreadId, null); // Act await Task.Run(() => op.SetCompleted()); diff --git a/src/UnityFx.Async.Tests/Tests/CompletionSourceTests.cs b/src/UnityFx.Async.Tests/Tests/CompletionSourceTests.cs index 12744a8..832afbd 100644 --- a/src/UnityFx.Async.Tests/Tests/CompletionSourceTests.cs +++ b/src/UnityFx.Async.Tests/Tests/CompletionSourceTests.cs @@ -50,7 +50,7 @@ public void TrySetCanceled_RaisesCompletionCallbacks() var asyncCallbackCalled1 = false; var asyncCallbackCalled2 = false; var op = new AsyncCompletionSource(asyncResult => asyncCallbackCalled1 = true, null); - op.AddContinuation(asyncOp => asyncCallbackCalled2 = true, null); + op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, null); // Act op.TrySetCanceled(); @@ -165,7 +165,7 @@ public void TrySetException_RaisesCompletionCallbacks() var asyncCallbackCalled1 = false; var asyncCallbackCalled2 = false; var op = new AsyncCompletionSource(asyncResult => asyncCallbackCalled1 = true, null); - op.AddContinuation(asyncOp => asyncCallbackCalled2 = true, null); + op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, null); // Act op.TrySetException(e); @@ -270,7 +270,7 @@ public void TrySetCompleted_RaisesCompletionCallbacks() var asyncCallbackCalled1 = false; var asyncCallbackCalled2 = false; var op = new AsyncCompletionSource(asyncResult => asyncCallbackCalled1 = true, null); - op.AddContinuation(asyncOp => asyncCallbackCalled2 = true, null); + op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, null); // Act op.TrySetCompleted(); @@ -364,7 +364,7 @@ public void TrySetResult_RaisesCompletionCallbacks() var asyncCallbackCalled1 = false; var asyncCallbackCalled2 = false; var op = new AsyncCompletionSource(asyncResult => asyncCallbackCalled1 = true, null); - op.AddContinuation(asyncOp => asyncCallbackCalled2 = true, null); + op.AddCompletionCallback(asyncOp => asyncCallbackCalled2 = true, null); // Act op.TrySetResult(10); diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs index 87cf9c9..78fbd3d 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs @@ -96,16 +96,16 @@ public event AsyncCompletedEventHandler Completed } /// - public void AddContinuation(AsyncOperationCallback action) + public void AddCompletionCallback(AsyncOperationCallback action) { - if (!TryAddContinuation(action)) + if (!TryAddCompletionCallback(action)) { InvokeContinuation(action, SynchronizationContext.Current); } } /// - public bool TryAddContinuation(AsyncOperationCallback action) + public bool TryAddCompletionCallback(AsyncOperationCallback action) { ThrowIfDisposed(); @@ -118,16 +118,16 @@ public bool TryAddContinuation(AsyncOperationCallback action) } /// - public void AddContinuation(AsyncOperationCallback action, SynchronizationContext syncContext) + public void AddCompletionCallback(AsyncOperationCallback action, SynchronizationContext syncContext) { - if (!TryAddContinuation(action, syncContext)) + if (!TryAddCompletionCallback(action, syncContext)) { InvokeContinuation(action, syncContext); } } /// - public bool TryAddContinuation(AsyncOperationCallback action, SynchronizationContext syncContext) + public bool TryAddCompletionCallback(AsyncOperationCallback action, SynchronizationContext syncContext) { ThrowIfDisposed(); @@ -140,7 +140,7 @@ public bool TryAddContinuation(AsyncOperationCallback action, SynchronizationCon } /// - public bool RemoveContinuation(AsyncOperationCallback action) + public bool RemoveCompletionCallback(AsyncOperationCallback action) { if (action != null) { @@ -151,16 +151,16 @@ public bool RemoveContinuation(AsyncOperationCallback action) } /// - public void AddContinuation(IAsyncContinuation continuation) + public void AddCompletionCallback(IAsyncContinuation continuation) { - if (!TryAddContinuation(continuation)) + if (!TryAddCompletionCallback(continuation)) { InvokeContinuation(continuation, SynchronizationContext.Current); } } /// - public bool TryAddContinuation(IAsyncContinuation continuation) + public bool TryAddCompletionCallback(IAsyncContinuation continuation) { ThrowIfDisposed(); @@ -173,16 +173,16 @@ public bool TryAddContinuation(IAsyncContinuation continuation) } /// - public void AddContinuation(IAsyncContinuation continuation, SynchronizationContext syncContext) + public void AddCompletionCallback(IAsyncContinuation continuation, SynchronizationContext syncContext) { - if (!TryAddContinuation(continuation, syncContext)) + if (!TryAddCompletionCallback(continuation, syncContext)) { InvokeContinuation(continuation, syncContext); } } /// - public bool TryAddContinuation(IAsyncContinuation continuation, SynchronizationContext syncContext) + public bool TryAddCompletionCallback(IAsyncContinuation continuation, SynchronizationContext syncContext) { ThrowIfDisposed(); @@ -195,7 +195,7 @@ public bool TryAddContinuation(IAsyncContinuation continuation, SynchronizationC } /// - public bool RemoveContinuation(IAsyncContinuation continuation) + public bool RemoveCompletionCallback(IAsyncContinuation continuation) { if (continuation != null) { diff --git a/src/UnityFx.Async/Api/Core/AsyncResultQueue{T}.cs b/src/UnityFx.Async/Api/Core/AsyncResultQueue{T}.cs index b1972c6..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.TryAddContinuation(_completionCallback, _syncContext)) + if (op.TryAddCompletionCallback(_completionCallback, _syncContext)) { lock (_ops) { @@ -181,7 +181,7 @@ public T[] Release() for (var i = 0; i < result.Length; ++i) { var op = _ops[i]; - op.RemoveContinuation(_completionCallback); + op.RemoveCompletionCallback(_completionCallback); result[i] = op; } @@ -251,7 +251,7 @@ public bool Remove(T op) { if (_ops.Remove(op)) { - op.RemoveContinuation(_completionCallback); + op.RemoveCompletionCallback(_completionCallback); TryStart(null); return true; } @@ -285,7 +285,7 @@ public void Clear() { foreach (var op in _ops) { - op.RemoveContinuation(_completionCallback); + op.RemoveCompletionCallback(_completionCallback); } _ops.Clear(); diff --git a/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs b/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs index d606db9..6718bbc 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs @@ -279,7 +279,7 @@ public IDisposable Subscribe(IObserver observer) } var result = new AsyncObservableSubscription(this, observer); - AddContinuation(result, null); + AddCompletionCallback(result, null); return result; } diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs index ab14c04..b273425 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Promises.cs @@ -485,7 +485,7 @@ public static void Done(this IAsyncOperation op, Action successCallback) throw new ArgumentNullException(nameof(successCallback)); } - op.AddContinuation(new DoneResult(successCallback, null)); + op.AddCompletionCallback(new DoneResult(successCallback, null)); } /// @@ -508,7 +508,7 @@ public static void Done(this IAsyncOperation op, Action successCallback, Action< throw new ArgumentNullException(nameof(errorCallback)); } - op.AddContinuation(new DoneResult(successCallback, errorCallback)); + op.AddCompletionCallback(new DoneResult(successCallback, errorCallback)); } /// @@ -525,7 +525,7 @@ public static void Done(this IAsyncOperation op, Action(successCallback, null)); + op.AddCompletionCallback(new DoneResult(successCallback, null)); } /// @@ -548,7 +548,7 @@ public static void Done(this IAsyncOperation op, Action(successCallback, errorCallback)); + op.AddCompletionCallback(new DoneResult(successCallback, errorCallback)); } #endregion diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs index 5984a6f..b0f7a53 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs @@ -218,7 +218,7 @@ public static Task ToTask(this IAsyncOperation op) { var result = new TaskCompletionSource(); - if (!op.TryAddContinuation(asyncOp => AsyncContinuation.InvokeTaskContinuation(asyncOp, result), null)) + if (!op.TryAddCompletionCallback(asyncOp => AsyncContinuation.InvokeTaskContinuation(asyncOp, result), null)) { AsyncContinuation.InvokeTaskContinuation(op, result); } @@ -252,7 +252,7 @@ public static Task ToTask(this IAsyncOperation op) { var result = new TaskCompletionSource(); - if (!op.TryAddContinuation(asyncOp => AsyncContinuation.InvokeTaskContinuation(asyncOp as IAsyncOperation, result), null)) + if (!op.TryAddCompletionCallback(asyncOp => AsyncContinuation.InvokeTaskContinuation(asyncOp as IAsyncOperation, result), null)) { AsyncContinuation.InvokeTaskContinuation(op, result); } @@ -297,7 +297,7 @@ private static void SetAwaitContiniation(IAsyncOperation op, Action continuation { ar.SetContinuationForAwait(continuation, syncContext); } - else if (!op.TryAddContinuation(asyncOp => continuation(), syncContext)) + else if (!op.TryAddCompletionCallback(asyncOp => continuation(), syncContext)) { continuation(); } diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs index abbe3db..d720617 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs @@ -46,9 +46,9 @@ public interface IAsyncOperationEvents /// Thrown if the delegate being registered is . /// Thrown is the operation has been disposed. /// - /// - /// - /// + /// + /// + /// event AsyncCompletedEventHandler Completed; /// @@ -61,9 +61,9 @@ public interface IAsyncOperationEvents /// The callback to be executed when the operation has completed. /// Thrown if is . /// Thrown is the operation has been disposed. - /// - /// - void AddContinuation(AsyncOperationCallback action); + /// + /// + void AddCompletionCallback(AsyncOperationCallback action); /// /// Attempts to add a completion callback to be executed after the operation has finished. If the operation is already completed @@ -77,10 +77,10 @@ public interface IAsyncOperationEvents /// Returns if the callback was added; otherwise (the operation is completed). /// Thrown if is . /// Thrown is the operation has been disposed. - /// - /// - /// - bool TryAddContinuation(AsyncOperationCallback action); + /// + /// + /// + bool TryAddCompletionCallback(AsyncOperationCallback action); /// /// Adds a completion callback to be executed after the operation has completed. If the operation is completed is invoked @@ -95,9 +95,9 @@ public interface IAsyncOperationEvents /// /// Thrown if is . /// Thrown is the operation has been disposed. - /// - /// - void AddContinuation(AsyncOperationCallback action, SynchronizationContext syncContext); + /// + /// + 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 @@ -113,19 +113,19 @@ public interface IAsyncOperationEvents /// Returns if the callback was added; otherwise (the operation is completed). /// Thrown if is . /// Thrown is the operation has been disposed. - /// - /// - /// - bool TryAddContinuation(AsyncOperationCallback action, SynchronizationContext syncContext); + /// + /// + /// + bool TryAddCompletionCallback(AsyncOperationCallback action, SynchronizationContext syncContext); /// /// Removes an existing completion callback. /// /// The callback to remove. Can be . /// Returns if was removed; otherwise. - /// - /// - bool RemoveContinuation(AsyncOperationCallback action); + /// + /// + bool RemoveCompletionCallback(AsyncOperationCallback action); /// /// Adds a continuation to be executed after the operation has completed. If the operation is completed is invoked synchronously. @@ -137,10 +137,10 @@ public interface IAsyncOperationEvents /// The callback to be executed when the operation has completed. /// Thrown if is . /// Thrown is the operation has been disposed. - /// - /// - /// - void AddContinuation(IAsyncContinuation continuation); + /// + /// + /// + void AddCompletionCallback(IAsyncContinuation continuation); /// /// Attempts to add a continuation to be executed after the operation has finished. If the operation is already completed @@ -154,10 +154,10 @@ public interface IAsyncOperationEvents /// 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); + /// + /// + /// + bool TryAddCompletionCallback(IAsyncContinuation continuation); /// /// Adds a continuation to be executed after the operation has completed. If the operation is completed @@ -172,10 +172,10 @@ public interface IAsyncOperationEvents /// /// Thrown if is . /// Thrown is the operation has been disposed. - /// - /// - /// - void AddContinuation(IAsyncContinuation continuation, SynchronizationContext syncContext); + /// + /// + /// + void AddCompletionCallback(IAsyncContinuation continuation, SynchronizationContext syncContext); /// /// Attempts to add a continuation to be executed after the operation has finished. If the operation is already completed @@ -191,21 +191,21 @@ public interface IAsyncOperationEvents /// 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); + /// + /// + /// + bool TryAddCompletionCallback(IAsyncContinuation continuation, SynchronizationContext syncContext); /// /// Removes an existing continuation. /// /// The continuation to remove. Can be . /// Returns if was removed; otherwise. - /// - /// - /// - /// - bool RemoveContinuation(IAsyncContinuation continuation); + /// + /// + /// + /// + bool RemoveCompletionCallback(IAsyncContinuation continuation); #if !NET35 diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs index 38b084a..9a7eca6 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs @@ -30,11 +30,11 @@ internal ContinueWithResult(IAsyncOperation op, AsyncContinuationOptions options if ((options & AsyncContinuationOptions.ExecuteSynchronously) != 0) { - op.AddContinuation(this, null); + op.AddCompletionCallback(this, null); } else { - op.AddContinuation(this); + op.AddCompletionCallback(this); } } 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 d8ec374..cfb8fdf 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/CatchResult{T,TException}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/CatchResult{T,TException}.cs @@ -22,7 +22,7 @@ public CatchResult(IAsyncOperation op, Action errorCallback) _op = op; _errorCallback = errorCallback; - op.AddContinuation(this); + op.AddCompletionCallback(this); } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs index 1e40ca8..7058eda 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs @@ -22,7 +22,7 @@ public FinallyResult(IAsyncOperation op, object action) _op = op; _continuation = action; - op.AddContinuation(this); + op.AddCompletionCallback(this); } #endregion @@ -55,11 +55,11 @@ public void Invoke(IAsyncOperation op, bool inline) break; case Func> f1: - f1().AddContinuation(op2 => TryCopyCompletionState(op2, false), null); + f1().AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); break; case Func f2: - f2().AddContinuation(op2 => TryCopyCompletionState(op2, false), null); + f2().AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); break; default: 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 facfea0..2bdc310 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/RebindResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/RebindResult{T,U}.cs @@ -23,7 +23,7 @@ public RebindResult(IAsyncOperation op, object action) _op = op; _continuation = action; - op.AddContinuation(this); + op.AddCompletionCallback(this); } #endregion 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 8b9af59..eb37932 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/ThenAllResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/ThenAllResult{T,U}.cs @@ -48,7 +48,7 @@ protected override IAsyncOperation InvokeSuccessCallback(IAsyncOperation op, boo if (result != null) { - result.AddContinuation(op2 => TryCopyCompletionState(op2, false), null); + result.AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); } else { diff --git a/src/UnityFx.Async/Implementation/Continuations/Promises/ThenAnyResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/ThenAnyResult{T,U}.cs index b0bc49a..280019a 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/ThenAnyResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/ThenAnyResult{T,U}.cs @@ -48,7 +48,7 @@ protected override IAsyncOperation InvokeSuccessCallback(IAsyncOperation op, boo if (result != null) { - result.AddContinuation( + result.AddCompletionCallback( op2 => { if (IsCancellationRequested) 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 5546e06..40dbdf8 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/ThenResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/ThenResult{T,U}.cs @@ -26,7 +26,7 @@ public ThenResult(IAsyncOperation op, object successCallback, Action _successCallback = successCallback; _errorCallback = errorCallback; - op.AddContinuation(this); + op.AddCompletionCallback(this); } protected virtual IAsyncOperation InvokeSuccessCallback(IAsyncOperation op, bool completedSynchronously, object continuation) @@ -47,22 +47,22 @@ protected virtual IAsyncOperation InvokeSuccessCallback(IAsyncOperation op, bool case Func> f3: result = f3(); - result.AddContinuation(op2 => TryCopyCompletionState(op2, false), null); + result.AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); break; case Func f1: result = f1(); - result.AddContinuation(op2 => TryCopyCompletionState(op2, false), null); + result.AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); break; case Func> f4: result = f4((op as IAsyncOperation).Result); - result.AddContinuation(op2 => TryCopyCompletionState(op2, false), null); + result.AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); break; case Func f2: result = f2((op as IAsyncOperation).Result); - result.AddContinuation(op2 => TryCopyCompletionState(op2, false), null); + result.AddCompletionCallback(op2 => TryCopyCompletionState(op2, false), null); break; default: diff --git a/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs index 7f80bb0..082a2fc 100644 --- a/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs @@ -27,7 +27,7 @@ private enum State public UnwrapResult(IAsyncOperation outerOp) : base(AsyncOperationStatus.Running) { - outerOp.AddContinuation(this); + outerOp.AddCompletionCallback(this); _op = outerOp; } @@ -119,7 +119,7 @@ private void ProcessInnerOperation(IAsyncOperation innerOp, bool completedSynchr } else { - innerOp.AddContinuation(this); + innerOp.AddCompletionCallback(this); _op = innerOp; } } diff --git a/src/UnityFx.Async/Implementation/Observable/AsyncObservableSubscription{T}.cs b/src/UnityFx.Async/Implementation/Observable/AsyncObservableSubscription{T}.cs index f389120..8c4822b 100644 --- a/src/UnityFx.Async/Implementation/Observable/AsyncObservableSubscription{T}.cs +++ b/src/UnityFx.Async/Implementation/Observable/AsyncObservableSubscription{T}.cs @@ -47,7 +47,7 @@ public void Invoke(IAsyncOperation op, bool inline) public void Dispose() { - _op.RemoveContinuation(this); + _op.RemoveCompletionCallback(this); } #endregion diff --git a/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs index 3bddaa6..842e049 100644 --- a/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs @@ -112,7 +112,7 @@ private void StartOperation() throw new InvalidOperationException("Invalid delegate type."); } - if (!_op.TryAddContinuation(this)) + if (!_op.TryAddCompletionCallback(this)) { if (_op.IsCompletedSuccessfully) { diff --git a/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs index 6da9b67..953d2fd 100644 --- a/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs @@ -26,7 +26,7 @@ public WhenAllResult(IAsyncOperation[] ops) foreach (var op in ops) { - op.AddContinuation(this, null); + op.AddCompletionCallback(this, null); } } diff --git a/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs index a87ecb7..340c3c5 100644 --- a/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs @@ -22,7 +22,7 @@ public WhenAnyResult(T[] ops) foreach (var op in ops) { - op.AddContinuation(this, null); + op.AddCompletionCallback(this, null); } } From b00b66b9eb01ef7bcffd98a8ff2790477806330c Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 30 May 2018 11:44:41 +0300 Subject: [PATCH 15/67] Progress reporting implementation refactoring --- .../Api/Core/AsyncCompletionSource.cs | 11 +--- .../Core/AsyncCompletionSource{TResult}.cs | 11 +--- .../Api/Core/AsyncResult.Events.cs | 12 ++-- src/UnityFx.Async/Api/Core/AsyncResult.cs | 66 +++++++++++-------- 4 files changed, 52 insertions(+), 48 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs b/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs index b607c17..087a5a3 100644 --- a/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs +++ b/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs @@ -220,15 +220,10 @@ public bool TrySetProgress(float progress) ThrowIfDisposed(); - if (Status == AsyncOperationStatus.Running) + if (_progress != progress) { - if (_progress != progress) - { - _progress = progress; - OnProgressChanged(); - } - - return true; + _progress = progress; + return TryReportProgress(); } return false; diff --git a/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs b/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs index 9980b27..4cc1f87 100644 --- a/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs @@ -221,15 +221,10 @@ public bool TrySetProgress(float progress) ThrowIfDisposed(); - if (Status == AsyncOperationStatus.Running) + if (_progress != progress) { - if (_progress != progress) - { - _progress = progress; - OnProgressChanged(); - } - - return true; + _progress = progress; + return TryReportProgress(); } return false; diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs index 78fbd3d..17ebe81 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs @@ -394,15 +394,15 @@ private bool TryRemoveCallback(object callbackToRemove) private void InvokeProgressChanged() { - var continuation = _callback; + var callback = _callback; - if (continuation != null) + if (callback != null) { - if (continuation is IEnumerable continuationList) + if (callback is IEnumerable callbackList) { - lock (continuationList) + lock (callbackList) { - foreach (var item in continuationList) + foreach (var item in callbackList) { InvokeProgressChanged(item); } @@ -410,7 +410,7 @@ private void InvokeProgressChanged() } else { - InvokeProgressChanged(continuation); + InvokeProgressChanged(callback); } } } diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 6f4e0ba..1266439 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -466,6 +466,24 @@ protected internal bool TrySetCompleted(bool completedSynchronously) return false; } + /// + /// Reports changes in operation progress value. + /// + /// Returns if the attemp was successfull; otherwise. + protected internal bool TryReportProgress() + { + var status = _flags & _statusMask; + + if (status == StatusRunning) + { + OnProgressChanged(); + InvokeProgressChanged(); + return true; + } + + return false; + } + /// /// Throws exception if the operation has failed or canceled. /// @@ -514,22 +532,20 @@ protected internal void ThrowIfDisposed() /// /// /// + /// protected virtual float GetProgress() { return 0; } /// - /// Called when the progress value has changed. Default implementation calls progress changed handlers. + /// Called when the progress value has changed. Default implementation does nothing. /// - /// - /// It is user responsibility to call this method when the operation progress value changes. - /// /// /// + /// protected virtual void OnProgressChanged() { - InvokeProgressChanged(); } /// @@ -569,7 +585,7 @@ protected virtual void OnCancel() } /// - /// Called when the operation is completed. Default implementation invokes completion handlers registered. + /// Called when the operation is completed. Default implementation does nothing. /// /// /// @@ -579,14 +595,6 @@ protected virtual void OnCancel() /// protected virtual void OnCompleted() { - try - { - InvokeContinuations(); - } - finally - { - _waitHandle?.Set(); - } } /// @@ -692,8 +700,7 @@ internal bool TrySetCompleted(int status, bool completedSynchronously) if (Interlocked.CompareExchange(ref _flags, newFlags, flags) == flags) { - OnStatusChanged((AsyncOperationStatus)status); - OnCompleted(); + NotifyCompleted((AsyncOperationStatus)status); return true; } } @@ -766,8 +773,7 @@ internal void SetCompleted(int status, bool completedSynchronously) Interlocked.Exchange(ref _flags, oldFlags | newFlags); // Invoke completion callbacks. - OnStatusChanged((AsyncOperationStatus)status); - OnCompleted(); + NotifyCompleted((AsyncOperationStatus)status); } /// @@ -885,8 +891,6 @@ public float Progress /// public void Cancel() { - ThrowIfDisposed(); - if (TrySetFlag(_flagCancellationRequested)) { OnCancel(); @@ -992,9 +996,6 @@ public override string ToString() #region implementation - /// - /// Gets a string representing the operation state. For debugger only. - /// private string DebuggerDisplay { get @@ -1019,9 +1020,6 @@ private string DebuggerDisplay } } - /// - /// Initializes a new instance of the class. For internal use only. - /// private AsyncResult(int flags) { if (flags == StatusFaulted) @@ -1044,6 +1042,22 @@ private AsyncResult(int flags) } } + private void NotifyCompleted(AsyncOperationStatus status) + { + try + { + OnProgressChanged(); + OnStatusChanged(status); + OnCompleted(); + + InvokeContinuations(); + } + finally + { + _waitHandle?.Set(); + } + } + #endregion } } From a6a35060e334f5944e4e7da997adbe5d1c396881 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 30 May 2018 12:09:23 +0300 Subject: [PATCH 16/67] Added progress report API with AsyncOperationCallback delegate --- .../Api/Core/AsyncResult.Events.cs | 57 ++++++++- .../Api/Interfaces/IAsyncOperationEvents.cs | 115 +++++++++++++++++- .../Continuations/Callbacks/AsyncProgress.cs | 4 + 3 files changed, 169 insertions(+), 7 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs index 17ebe81..d13dd22 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs @@ -205,6 +205,61 @@ public bool RemoveCompletionCallback(IAsyncContinuation continuation) return false; } + /// + public void AddProgressCallback(AsyncOperationCallback action) + { + if (!TryAddProgressCallback(action)) + { + InvokeProgressChanged(action, SynchronizationContext.Current); + } + } + + /// + public bool TryAddProgressCallback(AsyncOperationCallback action) + { + ThrowIfDisposed(); + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return TryAddProgressCallbackInternal(action, SynchronizationContext.Current); + } + + /// + public void AddProgressCallback(AsyncOperationCallback action, SynchronizationContext syncContext) + { + if (!TryAddProgressCallback(action, syncContext)) + { + InvokeProgressChanged(action, syncContext); + } + } + + /// + public bool TryAddProgressCallback(AsyncOperationCallback action, SynchronizationContext syncContext) + { + ThrowIfDisposed(); + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return TryAddProgressCallbackInternal(action, syncContext); + } + + /// + public bool RemoveProgressCallback(AsyncOperationCallback action) + { + if (action != null) + { + return TryRemoveCallback(action); + } + + return false; + } + #if !NET35 /// @@ -282,7 +337,7 @@ private bool TryAddContinuationInternal(object continuation, SynchronizationCont private bool TryAddProgressCallbackInternal(object callback, SynchronizationContext syncContext) { - if (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) + if ((syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) || callback is AsyncOperationCallback) { callback = new AsyncProgress(this, syncContext, callback); } diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs index d720617..07a004d 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs @@ -26,26 +26,33 @@ public interface IAsyncOperationEvents /// Raised when the operation progress has changed. /// /// - /// The event handler is invoked on a thread that registered the continuation (if it has a attached). - /// If the operation is already completed the event handler is called synchronously. Throwing an exception from the callback registered + /// The event handler is invoked on a thread that registered it (if it has a attached). + /// If the operation is already completed the event handler is called synchronously. Throwing an exception from the event handler /// might cause unspecified behaviour. /// /// Thrown if the delegate being registered is . /// Thrown is the operation has been disposed. /// + /// + /// + /// + /// + /// event ProgressChangedEventHandler ProgressChanged; /// /// Raised when the operation has completed. /// /// - /// The event handler is invoked on a thread that registered the continuation (if it has a attached). - /// If the operation is already completed the event handler is called synchronously. Throwing an exception from the callback registered + /// The event handler is invoked on a thread that registered it (if it has a attached). + /// If the operation is already completed the event handler is called synchronously. Throwing an exception from the event handler /// might cause unspecified behaviour. /// /// Thrown if the delegate being registered is . /// Thrown is the operation has been disposed. /// + /// + /// /// /// /// @@ -61,7 +68,9 @@ public interface IAsyncOperationEvents /// The callback to be executed when the operation has completed. /// Thrown if is . /// Thrown is the operation has been disposed. + /// /// + /// /// void AddCompletionCallback(AsyncOperationCallback action); @@ -78,6 +87,7 @@ public interface IAsyncOperationEvents /// Thrown if is . /// Thrown is the operation has been disposed. /// + /// /// /// bool TryAddCompletionCallback(AsyncOperationCallback action); @@ -95,6 +105,8 @@ public interface IAsyncOperationEvents /// /// Thrown if is . /// Thrown is the operation has been disposed. + /// + /// /// /// void AddCompletionCallback(AsyncOperationCallback action, SynchronizationContext syncContext); @@ -113,6 +125,7 @@ public interface IAsyncOperationEvents /// Returns if the callback was added; otherwise (the operation is completed). /// Thrown if is . /// Thrown is the operation has been disposed. + /// /// /// /// @@ -124,7 +137,9 @@ public interface IAsyncOperationEvents /// The callback to remove. Can be . /// Returns if was removed; otherwise. /// + /// /// + /// bool RemoveCompletionCallback(AsyncOperationCallback action); /// @@ -137,8 +152,9 @@ public interface IAsyncOperationEvents /// The callback to be executed when the operation has completed. /// Thrown if is . /// Thrown is the operation has been disposed. - /// /// + /// + /// /// void AddCompletionCallback(IAsyncContinuation continuation); @@ -155,6 +171,7 @@ public interface IAsyncOperationEvents /// Thrown if is . /// Thrown is the operation has been disposed. /// + /// /// /// bool TryAddCompletionCallback(IAsyncContinuation continuation); @@ -172,8 +189,9 @@ public interface IAsyncOperationEvents /// /// Thrown if is . /// Thrown is the operation has been disposed. - /// /// + /// + /// /// void AddCompletionCallback(IAsyncContinuation continuation, SynchronizationContext syncContext); @@ -191,6 +209,7 @@ public interface IAsyncOperationEvents /// Returns if the callback was added; otherwise (the operation is completed). /// Thrown if is . /// Thrown is the operation has been disposed. + /// /// /// /// @@ -207,6 +226,90 @@ public interface IAsyncOperationEvents /// bool RemoveCompletionCallback(IAsyncContinuation continuation); + /// + /// Adds a callback to be executed when the operation progress has changed. If the operation is already completed the is called synchronously. + /// + /// + /// The is invoked on a thread that registered the callback (if it has a attached). + /// Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The callback to be executed when the operation progress has changed. + /// Thrown if is . + /// Thrown is the operation has been disposed. + /// + /// + /// + /// + void AddProgressCallback(AsyncOperationCallback action); + + /// + /// Attempts to add a callback to be executed when the operation progress has changed. If the operation is already completed + /// the method does nothing and just returns . + /// + /// + /// The is invoked on a thread that registered the callback (if it has a attached). + /// Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The callback to be executed when the operation progress has changed. + /// Returns if the callback was added; otherwise (the operation is completed). + /// Thrown if is . + /// Thrown is the operation has been disposed. + /// + /// + /// + /// + bool TryAddProgressCallback(AsyncOperationCallback action); + + /// + /// Adds a callback to be executed when the operation progress has changed. If the operation is completed is invoked + /// on the specified. + /// + /// + /// The is invoked on a specified. Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The callback to be executed when the operation progress has changed. + /// If not method attempts to marshal the continuation to the synchronization context. + /// Otherwise the callback is invoked on a thread that initiated the operation. + /// + /// Thrown if is . + /// Thrown is the operation has been disposed. + /// + /// + /// + /// + void AddProgressCallback(AsyncOperationCallback action, SynchronizationContext syncContext); + + /// + /// Attempts to add a callback to be executed when the operation progress has changed. If the operation is already completed + /// the method does nothing and just returns . + /// + /// + /// The is invoked on a specified. Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The callback to be executed when the operation progress has changed. + /// If not method attempts to marshal the callback to the synchronization context. + /// Otherwise the callback is invoked on a thread that initiated the operation. + /// + /// Returns if the callback was added; otherwise (the operation is completed). + /// Thrown if is . + /// Thrown is the operation has been disposed. + /// + /// + /// + /// + bool TryAddProgressCallback(AsyncOperationCallback action, SynchronizationContext syncContext); + + /// + /// Removes an existing progress callback. + /// + /// The callback to remove. Can be . + /// Returns if was removed; otherwise. + /// + /// + /// + /// + bool RemoveProgressCallback(AsyncOperationCallback action); + #if !NET35 /// diff --git a/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncProgress.cs b/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncProgress.cs index f1b9f7a..5a4c8e9 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncProgress.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncProgress.cs @@ -27,6 +27,10 @@ internal static void InvokeInline(IAsyncOperation op, object callback) break; #endif + case AsyncOperationCallback ac: + ac.Invoke(op); + break; + case ProgressChangedEventHandler ph: ph.Invoke(op, new ProgressChangedEventArgs((int)(op.Progress * 100), op.AsyncState)); break; From 2759eca9a45f0b200e3f0d1aa71a0a45b24f8aed Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 30 May 2018 15:36:10 +0300 Subject: [PATCH 17/67] Added progress-related tests --- .../Tests/CompletionSourceTests.cs | 2 - .../Tests/ProgressCallbackTests.cs | 95 +++++++++++++++++++ .../Api/Core/AsyncCompletionSource.cs | 5 +- .../Core/AsyncCompletionSource{TResult}.cs | 5 +- src/UnityFx.Async/Api/Core/AsyncResult.cs | 9 ++ 5 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 src/UnityFx.Async.Tests/Tests/ProgressCallbackTests.cs diff --git a/src/UnityFx.Async.Tests/Tests/CompletionSourceTests.cs b/src/UnityFx.Async.Tests/Tests/CompletionSourceTests.cs index 832afbd..0016c9e 100644 --- a/src/UnityFx.Async.Tests/Tests/CompletionSourceTests.cs +++ b/src/UnityFx.Async.Tests/Tests/CompletionSourceTests.cs @@ -2,8 +2,6 @@ // 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 diff --git a/src/UnityFx.Async.Tests/Tests/ProgressCallbackTests.cs b/src/UnityFx.Async.Tests/Tests/ProgressCallbackTests.cs new file mode 100644 index 0000000..fa98856 --- /dev/null +++ b/src/UnityFx.Async.Tests/Tests/ProgressCallbackTests.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 Xunit; + +namespace UnityFx.Async +{ + public class ProgressSourceTests + { + #region SetProgress/TrySetProgress + + [Theory] + [InlineData(AsyncOperationStatus.Created)] + [InlineData(AsyncOperationStatus.Scheduled)] + [InlineData(AsyncOperationStatus.RanToCompletion)] + [InlineData(AsyncOperationStatus.Faulted)] + [InlineData(AsyncOperationStatus.Canceled)] + public void SetProgress_ThrowsIfOperationIsNotRunning(AsyncOperationStatus status) + { + // Arrange + var op = new AsyncCompletionSource(status); + + // Act/Assert + Assert.Throws(() => op.SetProgress(0.1f)); + } + + [Theory] + [InlineData(AsyncOperationStatus.Created, false)] + [InlineData(AsyncOperationStatus.Scheduled, false)] + [InlineData(AsyncOperationStatus.Running, true)] + [InlineData(AsyncOperationStatus.RanToCompletion, false)] + [InlineData(AsyncOperationStatus.Faulted, false)] + [InlineData(AsyncOperationStatus.Canceled, false)] + public void TrySetProgress_ReturnsCorrentValue(AsyncOperationStatus status, bool expectedResult) + { + // Arrange + var op = new AsyncCompletionSource(status); + + // Act + var result = op.TrySetProgress(1); + + // Assert + Assert.Equal(expectedResult, result); + } + + [Fact] + public void SetCompleted_RaisesProgressCallbacks() + { + // Arrange + var asyncCallbackCalled = false; + var progress = 0f; + var op = new AsyncCompletionSource(); + + op.AddProgressCallback( + asyncOp => + { + asyncCallbackCalled = true; + progress = asyncOp.Progress; + }, + null); + + // Act + op.SetCompleted(); + + // Assert + Assert.True(asyncCallbackCalled); + Assert.Equal(1, progress); + } + + [Fact] + public void SetCompleted_RaisesProgressChanged() + { + // Arrange + var asyncCallbackCalled = false; + var progress = 0; + var op = new AsyncCompletionSource(); + + op.ProgressChanged += (sender, args) => + { + asyncCallbackCalled = true; + progress = args.ProgressPercentage; + }; + + // Act + op.SetCompleted(); + + // Assert + Assert.True(asyncCallbackCalled); + Assert.Equal(100, progress); + } + + #endregion + } +} diff --git a/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs b/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs index 087a5a3..c880a80 100644 --- a/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs +++ b/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs @@ -220,10 +220,11 @@ public bool TrySetProgress(float progress) ThrowIfDisposed(); - if (_progress != progress) + if (Status == AsyncOperationStatus.Running && _progress != progress) { _progress = progress; - return TryReportProgress(); + ReportProgress(); + return true; } return false; diff --git a/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs b/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs index 4cc1f87..5308549 100644 --- a/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs @@ -221,10 +221,11 @@ public bool TrySetProgress(float progress) ThrowIfDisposed(); - if (_progress != progress) + if (Status == AsyncOperationStatus.Running && _progress != progress) { _progress = progress; - return TryReportProgress(); + ReportProgress(); + return true; } return false; diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 1266439..66de968 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -804,6 +804,15 @@ internal bool TryCopyCompletionState(IAsyncOperation patternOp, bool completedSy return false; } + /// + /// Unconditionally reports the operatino progress. + /// + internal void ReportProgress() + { + OnProgressChanged(); + InvokeProgressChanged(); + } + /// /// Throws if the specified operation is faulted/canceled. /// From 15c11babb230ecb057e1e029534f1fc65b238249 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 30 May 2018 15:36:56 +0300 Subject: [PATCH 18/67] NOTES update --- src/UnityFx.Async/NOTES.txt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/UnityFx.Async/NOTES.txt b/src/UnityFx.Async/NOTES.txt index 9e8abcb..6c4488b 100644 --- a/src/UnityFx.Async/NOTES.txt +++ b/src/UnityFx.Async/NOTES.txt @@ -1,7 +1,5 @@ TODO: -1) IProgress support: - - progress reporting should support standard IProgress interface; -2) Optimize number of allocations needed for adding continuations: +1) Optimize number of allocations needed for adding continuations: - most common case is a 1-2 continuations per operation; - it's a very rare case when more than 4 continuations per operation is needed, so this case is not a subject to any optimizations currently; - one possible optimization is removing internal List that is allocated for cases when more that 1 continuation is needed, From e07ca7ee2745ef4e6a92e6c93289827d15516371 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 31 May 2018 18:56:39 +0300 Subject: [PATCH 19/67] Added AsyncCallbackCollection --- .../Api/Core/AsyncResult.Events.cs | 26 +- .../Callbacks/AsyncCallbackCollection.cs | 308 ++++++++++++++++++ 2 files changed, 321 insertions(+), 13 deletions(-) create mode 100644 src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncCallbackCollection.cs diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs index d13dd22..ec78ec1 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs @@ -353,7 +353,7 @@ private bool TryAddCallback(object callbackToAdd) // Quick return if the operation is completed. if (oldValue != _callbackCompletionSentinel) { - // If no continuation is stored yet, try to store it as _continuation. + // If no continuation is stored yet, try to store it as _callback. if (oldValue == null) { oldValue = Interlocked.CompareExchange(ref _callback, callbackToAdd, null); @@ -365,7 +365,7 @@ private bool TryAddCallback(object callbackToAdd) } } - // Logic for the case where we were previously storing a single continuation. + // Logic for the case where we were previously storing a single callback. if (oldValue != _callbackCompletionSentinel && !(oldValue is IList)) { var newList = new List() { oldValue }; @@ -378,13 +378,13 @@ private bool TryAddCallback(object callbackToAdd) // 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. + // as we will not be queuing up the callback. if (_callback 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 so, then fall through and return false without queuing the callback. if (_callback != _callbackCompletionSentinel) { list.Add(callbackToAdd); @@ -416,14 +416,14 @@ private bool TryRemoveCallback(object callbackToRemove) } 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. + // If we fail it means that either TryAddContinuation won the race condition and _callback is now a List + // that contains the element we want to remove. Or it set the _callbackCompletionSentinel. // 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 is null it means _callbackCompletionSentinel has been set already and there is nothing else to do. if (list != null) { lock (list) @@ -449,15 +449,15 @@ private bool TryRemoveCallback(object callbackToRemove) private void InvokeProgressChanged() { - var callback = _callback; + var value = _callback; - if (callback != null) + if (value != null) { - if (callback is IEnumerable callbackList) + if (value is IEnumerable callbacks) { - lock (callbackList) + lock (callbacks) { - foreach (var item in callbackList) + foreach (var item in callbacks) { InvokeProgressChanged(item); } @@ -465,7 +465,7 @@ private void InvokeProgressChanged() } else { - InvokeProgressChanged(callback); + InvokeProgressChanged(value); } } } diff --git a/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncCallbackCollection.cs b/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncCallbackCollection.cs new file mode 100644 index 0000000..99c1ab2 --- /dev/null +++ b/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncCallbackCollection.cs @@ -0,0 +1,308 @@ +// 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.Diagnostics; +using System.Threading; + +namespace UnityFx.Async +{ + internal class AsyncCallbackCollection + { + #region data + + private struct CallbackData + { + public readonly object Callback; + public readonly SynchronizationContext SyncContext; + + public CallbackData(object callback, SynchronizationContext syncContext) + { + Callback = callback; + SyncContext = syncContext; + } + } + + private readonly IAsyncOperation _op; + + private List _callbacks; + + private CallbackData _callback1; + private CallbackData _callback2; + private CallbackData _callback3; + private CallbackData _callback4; + + #endregion + + #region interface + + public AsyncCallbackCollection(IAsyncOperation op) + { + _op = op; + } + + public AsyncCallbackCollection(IAsyncOperation op, object callback, SynchronizationContext syncContext) + { + _op = op; + _callback1 = new CallbackData(callback, syncContext); + } + + public void Add(object callback, SynchronizationContext syncContext) + { + lock (this) + { + var newCallback = new CallbackData(callback, syncContext); + + if (_callback1.Callback == null) + { + _callback1 = newCallback; + return; + } + + if (_callback2.Callback == null) + { + _callback2 = newCallback; + return; + } + + if (_callback3.Callback == null) + { + _callback3 = newCallback; + return; + } + + if (_callback4.Callback == null) + { + _callback4 = newCallback; + return; + } + + if (_callbacks == null) + { + _callbacks = new List() { newCallback }; + } + else + { + _callbacks.Add(newCallback); + } + } + } + + public void Remove(object callback) + { + lock (this) + { + if (_callback1.Callback == callback) + { + _callback1 = default(CallbackData); + return; + } + + if (_callback2.Callback == callback) + { + _callback2 = default(CallbackData); + return; + } + + if (_callback3.Callback == callback) + { + _callback3 = default(CallbackData); + return; + } + + if (_callback4.Callback == callback) + { + _callback4 = default(CallbackData); + return; + } + + if (_callbacks != null) + { + var count = _callbacks.Count; + + for (var i = 0; i < count; i++) + { + if (_callbacks[i].Callback == callback) + { + _callbacks.RemoveAt(i); + return; + } + } + } + } + } + + public void Invoke() + { + lock (this) + { + if (_callback1.Callback != null) + { + Invoke(_callback1); + } + + if (_callback2.Callback != null) + { + Invoke(_callback2); + } + + if (_callback3.Callback != null) + { + Invoke(_callback3); + } + + if (_callback4.Callback != null) + { + Invoke(_callback4); + } + + if (_callbacks != null) + { + foreach (var item in _callbacks) + { + Invoke(item); + } + } + } + } + + public void InvokeAsync() + { + lock (this) + { + if (_callback1.Callback != null) + { + InvokeAsync(_callback1); + } + + if (_callback2.Callback != null) + { + InvokeAsync(_callback2); + } + + if (_callback3.Callback != null) + { + InvokeAsync(_callback3); + } + + if (_callback4.Callback != null) + { + InvokeAsync(_callback4); + } + + if (_callbacks != null) + { + foreach (var item in _callbacks) + { + InvokeAsync(item); + } + } + } + } + + public void InvokeProgressChanged() + { + lock (this) + { + if (_callback1.Callback != null) + { + InvokeProgressChanged(_callback1); + } + + if (_callback2.Callback != null) + { + InvokeProgressChanged(_callback2); + } + + if (_callback3.Callback != null) + { + InvokeProgressChanged(_callback3); + } + + if (_callback4.Callback != null) + { + InvokeProgressChanged(_callback4); + } + + if (_callbacks != null) + { + foreach (var item in _callbacks) + { + InvokeProgressChanged(item); + } + } + } + } + + #endregion + + #region implementation + + private void Invoke(CallbackData callbackData) + { + var syncContext = callbackData.SyncContext; + + if (syncContext == null || syncContext == SynchronizationContext.Current) + { + InvokeInline(callbackData.Callback); + } + else + { + syncContext.Post(InvokeInline, callbackData.Callback); + } + } + + private void InvokeAsync(CallbackData callbackData) + { + var syncContext = callbackData.SyncContext; + + if (syncContext != null) + { + syncContext.Post(InvokeInline, callbackData.Callback); + } + else + { + syncContext = SynchronizationContext.Current; + + if (syncContext != null) + { + syncContext.Post(InvokeInline, callbackData.Callback); + } + else + { + ThreadPool.QueueUserWorkItem(InvokeInline, callbackData.Callback); + } + } + } + + private void InvokeProgressChanged(CallbackData callbackData) + { + var syncContext = callbackData.SyncContext; + + if (syncContext == null || syncContext == SynchronizationContext.Current) + { + InvokeProgressChangedInline(callbackData.Callback); + } + else + { + syncContext.Post(InvokeProgressChangedInline, callbackData.Callback); + } + } + + private void InvokeInline(object callback) + { + Debug.Assert(callback != null); + AsyncContinuation.InvokeInline(_op, callback, false); + } + + private void InvokeProgressChangedInline(object callback) + { + Debug.Assert(callback != null); + AsyncProgress.InvokeInline(_op, callback); + } + + #endregion + } +} From bbc279afc8fbfa15cf1e174ab8fcc505294da819 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 31 May 2018 20:29:56 +0300 Subject: [PATCH 20/67] Added events implementation using new AsyncCallbackCollection (TODO: progress reporting) --- .../Tests/CompletionCallbackTests.cs | 2 +- .../Api/Core/AsyncResult.Events.cs | 141 ++++------ src/UnityFx.Async/Api/Core/AsyncResult.cs | 2 +- .../Callbacks/AsyncCallbackCollection.cs | 248 +++++++++--------- 4 files changed, 177 insertions(+), 216 deletions(-) diff --git a/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs b/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs index f846fbb..151ef10 100644 --- a/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs +++ b/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs @@ -64,7 +64,7 @@ public async Task TryAddCompletionCallback_IsThreadSafe() void CompletionCallback(IAsyncOperation o) { - ++counter; + Interlocked.Increment(ref counter); } void TestMethod() diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs index ec78ec1..99a41dc 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs @@ -83,7 +83,7 @@ public event AsyncCompletedEventHandler Completed if (!TryAddContinuationInternal(value, syncContext)) { - InvokeContinuation(value, syncContext); + InvokeCompletionCallback(value, syncContext); } } remove @@ -100,7 +100,7 @@ public void AddCompletionCallback(AsyncOperationCallback action) { if (!TryAddCompletionCallback(action)) { - InvokeContinuation(action, SynchronizationContext.Current); + InvokeCompletionCallback(action, SynchronizationContext.Current); } } @@ -122,7 +122,7 @@ public void AddCompletionCallback(AsyncOperationCallback action, Synchronization { if (!TryAddCompletionCallback(action, syncContext)) { - InvokeContinuation(action, syncContext); + InvokeCompletionCallback(action, syncContext); } } @@ -155,7 +155,7 @@ public void AddCompletionCallback(IAsyncContinuation continuation) { if (!TryAddCompletionCallback(continuation)) { - InvokeContinuation(continuation, SynchronizationContext.Current); + InvokeCompletionCallback(continuation, SynchronizationContext.Current); } } @@ -177,7 +177,7 @@ public void AddCompletionCallback(IAsyncContinuation continuation, Synchronizati { if (!TryAddCompletionCallback(continuation, syncContext)) { - InvokeContinuation(continuation, syncContext); + InvokeCompletionCallback(continuation, syncContext); } } @@ -325,27 +325,15 @@ public bool RemoveProgressCallback(IProgress callback) private bool TryAddContinuationInternal(object continuation, SynchronizationContext syncContext) { - var runContinuationsAsynchronously = (_flags & _flagRunContinuationsAsynchronously) != 0; - - if ((syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) || runContinuationsAsynchronously) - { - continuation = new AsyncContinuation(this, syncContext, continuation); - } - - return TryAddCallback(continuation); + return TryAddCallback(continuation, syncContext); } private bool TryAddProgressCallbackInternal(object callback, SynchronizationContext syncContext) { - if ((syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) || callback is AsyncOperationCallback) - { - callback = new AsyncProgress(this, syncContext, callback); - } - - return TryAddCallback(callback); + return TryAddCallback(callback, syncContext); } - private bool TryAddCallback(object callbackToAdd) + private bool TryAddCallback(object callbackToAdd, SynchronizationContext syncContext) { // NOTE: The code below is adapted from https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs. var oldValue = _callback; @@ -353,10 +341,18 @@ private bool TryAddCallback(object callbackToAdd) // Quick return if the operation is completed. if (oldValue != _callbackCompletionSentinel) { - // If no continuation is stored yet, try to store it as _callback. + // If no callback is stored yet, try to store it as _callback. if (oldValue == null) { - oldValue = Interlocked.CompareExchange(ref _callback, callbackToAdd, null); + if (syncContext == null) + { + oldValue = Interlocked.CompareExchange(ref _callback, callbackToAdd, null); + } + else + { + var newList = new AsyncCallbackCollection(this, callbackToAdd, syncContext); + oldValue = Interlocked.CompareExchange(ref _callback, newList, null); + } // Quick return if exchange succeeded. if (oldValue == null) @@ -366,20 +362,19 @@ private bool TryAddCallback(object callbackToAdd) } // Logic for the case where we were previously storing a single callback. - if (oldValue != _callbackCompletionSentinel && !(oldValue is IList)) + if (oldValue != _callbackCompletionSentinel && !(oldValue is AsyncCallbackCollection)) { - var newList = new List() { oldValue }; - + var newList = new AsyncCallbackCollection(this, oldValue, null); Interlocked.CompareExchange(ref _callback, 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, + // If list is null, it can only mean that _callbackCompletionSentinel has been exchanged + // into _callback. Thus, the task has completed and we should return false from this method, // as we will not be queuing up the callback. - if (_callback is IList list) + if (_callback is AsyncCallbackCollection list) { lock (list) { @@ -387,7 +382,7 @@ private bool TryAddCallback(object callbackToAdd) // If so, then fall through and return false without queuing the callback. if (_callback != _callbackCompletionSentinel) { - list.Add(callbackToAdd); + list.Add(callbackToAdd, syncContext); return true; } } @@ -404,13 +399,13 @@ private bool TryRemoveCallback(object callbackToRemove) if (value != _callbackCompletionSentinel) { - var list = value as IList; + var list = value as AsyncCallbackCollection; 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 _callback, new List(), callbackToRemove) == callbackToRemove) + // Note we cannot go back to a null state, since it will mess up the TryAddCallback() logic. + if (Interlocked.CompareExchange(ref _callback, new AsyncCallbackCollection(this), callbackToRemove) == callbackToRemove) { return true; } @@ -419,7 +414,7 @@ private bool TryRemoveCallback(object callbackToRemove) // If we fail it means that either TryAddContinuation won the race condition and _callback is now a List // that contains the element we want to remove. Or it set the _callbackCompletionSentinel. // So we should try to get a list one more time. - list = value as IList; + list = _callback as AsyncCallbackCollection; } } @@ -429,16 +424,10 @@ private bool TryRemoveCallback(object callbackToRemove) 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. + // list. In that case, just return; we don't want to be manipulating the callback list as it is being processed. if (_callback != _callbackCompletionSentinel) { - var index = list.IndexOf(callbackToRemove); - - if (index != -1) - { - list.RemoveAt(index); - return true; - } + return list.Remove(callbackToRemove); } } } @@ -453,35 +442,20 @@ private void InvokeProgressChanged() if (value != null) { - if (value is IEnumerable callbacks) + if (value is AsyncCallbackCollection callbackList) { - lock (callbacks) + lock (callbackList) { - foreach (var item in callbacks) - { - InvokeProgressChanged(item); - } + callbackList.InvokeProgressChanged(); } } else { - InvokeProgressChanged(value); + AsyncProgress.InvokeInline(this, value); } } } - private void InvokeProgressChanged(object callback) - { - if (callback is AsyncProgress p) - { - p.Invoke(); - } - else - { - AsyncProgress.InvokeInline(this, callback); - } - } - private void InvokeProgressChanged(object callback, SynchronizationContext syncContext) { if (syncContext == null || syncContext == SynchronizationContext.Current) @@ -494,63 +468,64 @@ private void InvokeProgressChanged(object callback, SynchronizationContext syncC } } - private void InvokeContinuations() + private void InvokeCallbacks() { - var continuation = Interlocked.Exchange(ref _callback, _callbackCompletionSentinel); + var value = Interlocked.Exchange(ref _callback, _callbackCompletionSentinel); - if (continuation != null) + if (value != null) { - if (continuation is IEnumerable continuationList) + if (value is AsyncCallbackCollection callbackList) { - lock (continuationList) + lock (callbackList) { - foreach (var item in continuationList) - { - InvokeContinuation(item); - } + callbackList.Invoke(); } } else { - InvokeContinuation(continuation); + InvokeCallback(value); } } } - private void InvokeContinuation(object continuation) + private void InvokeCallback(object value) { - var runContinuationsAsynchronously = (_flags & _flagRunContinuationsAsynchronously) != 0; - - if (runContinuationsAsynchronously) + if ((_flags & _flagRunContinuationsAsynchronously) != 0) { - if (continuation is AsyncInvokable c) + if (value is AsyncCallbackCollection callbackList) { // NOTE: This is more effective than InvokeContinuationAsync(). - c.InvokeAsync(); + lock (callbackList) + { + callbackList.InvokeAsync(); + } } else { - InvokeContinuationAsync(continuation, SynchronizationContext.Current, false); + InvokeCallbackAsync(value, SynchronizationContext.Current, false); } } else { - if (continuation is AsyncInvokable c) + if (value is AsyncCallbackCollection callbackList) { - c.Invoke(); + lock (callbackList) + { + callbackList.Invoke(); + } } else { - InvokeContinuationInline(continuation, false); + InvokeContinuationInline(value, false); } } } - private void InvokeContinuation(object continuation, SynchronizationContext syncContext) + private void InvokeCompletionCallback(object continuation, SynchronizationContext syncContext) { if ((_flags & _flagRunContinuationsAsynchronously) != 0) { - InvokeContinuationAsync(continuation, syncContext, true); + InvokeCallbackAsync(continuation, syncContext, true); } else if (syncContext == null || syncContext == SynchronizationContext.Current) { @@ -562,7 +537,7 @@ private void InvokeContinuation(object continuation, SynchronizationContext sync } } - private void InvokeContinuationAsync(object continuation, SynchronizationContext syncContext, bool inline) + private void InvokeCallbackAsync(object continuation, SynchronizationContext syncContext, bool inline) { if (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) { diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 66de968..7c4c45e 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -1059,7 +1059,7 @@ private void NotifyCompleted(AsyncOperationStatus status) OnStatusChanged(status); OnCompleted(); - InvokeContinuations(); + InvokeCallbacks(); } finally { diff --git a/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncCallbackCollection.cs b/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncCallbackCollection.cs index 99c1ab2..23f9c1d 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncCallbackCollection.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncCallbackCollection.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.Diagnostics; using System.Threading; @@ -27,13 +26,13 @@ public CallbackData(object callback, SynchronizationContext syncContext) private readonly IAsyncOperation _op; - private List _callbacks; - private CallbackData _callback1; private CallbackData _callback2; private CallbackData _callback3; private CallbackData _callback4; + private List _callbacks; + #endregion #region interface @@ -51,187 +50,174 @@ public AsyncCallbackCollection(IAsyncOperation op, object callback, Synchronizat public void Add(object callback, SynchronizationContext syncContext) { - lock (this) - { - var newCallback = new CallbackData(callback, syncContext); + var newCallback = new CallbackData(callback, syncContext); - if (_callback1.Callback == null) - { - _callback1 = newCallback; - return; - } + if (_callback1.Callback == null) + { + _callback1 = newCallback; + return; + } - if (_callback2.Callback == null) - { - _callback2 = newCallback; - return; - } + if (_callback2.Callback == null) + { + _callback2 = newCallback; + return; + } - if (_callback3.Callback == null) - { - _callback3 = newCallback; - return; - } + if (_callback3.Callback == null) + { + _callback3 = newCallback; + return; + } - if (_callback4.Callback == null) - { - _callback4 = newCallback; - return; - } + if (_callback4.Callback == null) + { + _callback4 = newCallback; + return; + } - if (_callbacks == null) - { - _callbacks = new List() { newCallback }; - } - else - { - _callbacks.Add(newCallback); - } + if (_callbacks == null) + { + _callbacks = new List() { newCallback }; + } + else + { + _callbacks.Add(newCallback); } } - public void Remove(object callback) + public bool Remove(object callback) { - lock (this) + if (_callback1.Callback == callback) { - if (_callback1.Callback == callback) - { - _callback1 = default(CallbackData); - return; - } + _callback1 = default(CallbackData); + return true; + } - if (_callback2.Callback == callback) - { - _callback2 = default(CallbackData); - return; - } + if (_callback2.Callback == callback) + { + _callback2 = default(CallbackData); + return true; + } - if (_callback3.Callback == callback) - { - _callback3 = default(CallbackData); - return; - } + if (_callback3.Callback == callback) + { + _callback3 = default(CallbackData); + return true; + } - if (_callback4.Callback == callback) - { - _callback4 = default(CallbackData); - return; - } + if (_callback4.Callback == callback) + { + _callback4 = default(CallbackData); + return true; + } - if (_callbacks != null) - { - var count = _callbacks.Count; + if (_callbacks != null) + { + var count = _callbacks.Count; - for (var i = 0; i < count; i++) + for (var i = 0; i < count; i++) + { + if (_callbacks[i].Callback == callback) { - if (_callbacks[i].Callback == callback) - { - _callbacks.RemoveAt(i); - return; - } + _callbacks.RemoveAt(i); + return true; } } } + + return false; } public void Invoke() { - lock (this) + if (_callback1.Callback != null) { - if (_callback1.Callback != null) - { - Invoke(_callback1); - } + Invoke(_callback1); + } - if (_callback2.Callback != null) - { - Invoke(_callback2); - } + if (_callback2.Callback != null) + { + Invoke(_callback2); + } - if (_callback3.Callback != null) - { - Invoke(_callback3); - } + if (_callback3.Callback != null) + { + Invoke(_callback3); + } - if (_callback4.Callback != null) - { - Invoke(_callback4); - } + if (_callback4.Callback != null) + { + Invoke(_callback4); + } - if (_callbacks != null) + if (_callbacks != null) + { + foreach (var item in _callbacks) { - foreach (var item in _callbacks) - { - Invoke(item); - } + Invoke(item); } } } public void InvokeAsync() { - lock (this) + if (_callback1.Callback != null) { - if (_callback1.Callback != null) - { - InvokeAsync(_callback1); - } + InvokeAsync(_callback1); + } - if (_callback2.Callback != null) - { - InvokeAsync(_callback2); - } + if (_callback2.Callback != null) + { + InvokeAsync(_callback2); + } - if (_callback3.Callback != null) - { - InvokeAsync(_callback3); - } + if (_callback3.Callback != null) + { + InvokeAsync(_callback3); + } - if (_callback4.Callback != null) - { - InvokeAsync(_callback4); - } + if (_callback4.Callback != null) + { + InvokeAsync(_callback4); + } - if (_callbacks != null) + if (_callbacks != null) + { + foreach (var item in _callbacks) { - foreach (var item in _callbacks) - { - InvokeAsync(item); - } + InvokeAsync(item); } } } public void InvokeProgressChanged() { - lock (this) + if (_callback1.Callback != null) { - if (_callback1.Callback != null) - { - InvokeProgressChanged(_callback1); - } + InvokeProgressChanged(_callback1); + } - if (_callback2.Callback != null) - { - InvokeProgressChanged(_callback2); - } + if (_callback2.Callback != null) + { + InvokeProgressChanged(_callback2); + } - if (_callback3.Callback != null) - { - InvokeProgressChanged(_callback3); - } + if (_callback3.Callback != null) + { + InvokeProgressChanged(_callback3); + } - if (_callback4.Callback != null) - { - InvokeProgressChanged(_callback4); - } + if (_callback4.Callback != null) + { + InvokeProgressChanged(_callback4); + } - if (_callbacks != null) + if (_callbacks != null) + { + foreach (var item in _callbacks) { - foreach (var item in _callbacks) - { - InvokeProgressChanged(item); - } + InvokeProgressChanged(item); } } } From 6b9aec5fe5eb0bdfcec16751f2fef3a22b42599d Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 31 May 2018 22:22:05 +0300 Subject: [PATCH 21/67] Added progress callbacks support --- .../Api/Core/AsyncResult.Events.cs | 60 ++++--- .../Callbacks/AsyncCallbackCollection.cs | 170 ++++++++++-------- 2 files changed, 133 insertions(+), 97 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs index 99a41dc..696d436 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs @@ -29,7 +29,7 @@ internal void SetContinuationForAwait(Action continuation, SynchronizationContex { ThrowIfDisposed(); - if (!TryAddContinuationInternal(continuation, syncContext)) + if (!TryAddCallback(continuation, syncContext, true)) { continuation(); } @@ -53,7 +53,7 @@ public event ProgressChangedEventHandler ProgressChanged var syncContext = SynchronizationContext.Current; - if (!TryAddProgressCallbackInternal(value, syncContext)) + if (!TryAddCallback(value, syncContext, false)) { InvokeProgressChanged(value, syncContext); } @@ -81,7 +81,7 @@ public event AsyncCompletedEventHandler Completed var syncContext = SynchronizationContext.Current; - if (!TryAddContinuationInternal(value, syncContext)) + if (!TryAddCallback(value, syncContext, true)) { InvokeCompletionCallback(value, syncContext); } @@ -114,7 +114,7 @@ public bool TryAddCompletionCallback(AsyncOperationCallback action) throw new ArgumentNullException(nameof(action)); } - return TryAddContinuationInternal(action, SynchronizationContext.Current); + return TryAddCallback(action, SynchronizationContext.Current, true); } /// @@ -136,7 +136,7 @@ public bool TryAddCompletionCallback(AsyncOperationCallback action, Synchronizat throw new ArgumentNullException(nameof(action)); } - return TryAddContinuationInternal(action, syncContext); + return TryAddCallback(action, syncContext, true); } /// @@ -169,7 +169,7 @@ public bool TryAddCompletionCallback(IAsyncContinuation continuation) throw new ArgumentNullException(nameof(continuation)); } - return TryAddContinuationInternal(continuation, SynchronizationContext.Current); + return TryAddCallback(continuation, SynchronizationContext.Current, true); } /// @@ -191,7 +191,7 @@ public bool TryAddCompletionCallback(IAsyncContinuation continuation, Synchroniz throw new ArgumentNullException(nameof(continuation)); } - return TryAddContinuationInternal(continuation, syncContext); + return TryAddCallback(continuation, syncContext, true); } /// @@ -224,7 +224,7 @@ public bool TryAddProgressCallback(AsyncOperationCallback action) throw new ArgumentNullException(nameof(action)); } - return TryAddProgressCallbackInternal(action, SynchronizationContext.Current); + return TryAddCallback(action, SynchronizationContext.Current, false); } /// @@ -246,7 +246,7 @@ public bool TryAddProgressCallback(AsyncOperationCallback action, Synchronizatio throw new ArgumentNullException(nameof(action)); } - return TryAddProgressCallbackInternal(action, syncContext); + return TryAddCallback(action, syncContext, false); } /// @@ -281,7 +281,7 @@ public bool TryAddProgressCallback(IProgress callback) throw new ArgumentNullException(nameof(callback)); } - return TryAddProgressCallbackInternal(callback, SynchronizationContext.Current); + return TryAddCallback(callback, SynchronizationContext.Current, false); } /// @@ -303,7 +303,7 @@ public bool TryAddProgressCallback(IProgress callback, SynchronizationCon throw new ArgumentNullException(nameof(callback)); } - return TryAddProgressCallbackInternal(callback, syncContext); + return TryAddCallback(callback, syncContext, false); } /// @@ -323,17 +323,7 @@ public bool RemoveProgressCallback(IProgress callback) #region implementation - private bool TryAddContinuationInternal(object continuation, SynchronizationContext syncContext) - { - return TryAddCallback(continuation, syncContext); - } - - private bool TryAddProgressCallbackInternal(object callback, SynchronizationContext syncContext) - { - return TryAddCallback(callback, syncContext); - } - - private bool TryAddCallback(object callbackToAdd, SynchronizationContext syncContext) + private bool TryAddCallback(object callbackToAdd, SynchronizationContext syncContext, bool completionCallback) { // NOTE: The code below is adapted from https://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs. var oldValue = _callback; @@ -344,16 +334,24 @@ private bool TryAddCallback(object callbackToAdd, SynchronizationContext syncCon // If no callback is stored yet, try to store it as _callback. if (oldValue == null) { - if (syncContext == null) + var newValue = callbackToAdd; + + if (completionCallback) { - oldValue = Interlocked.CompareExchange(ref _callback, callbackToAdd, null); + if (syncContext != null) + { + newValue = new AsyncCallbackCollection(this, callbackToAdd, syncContext); + } } else { - var newList = new AsyncCallbackCollection(this, callbackToAdd, syncContext); - oldValue = Interlocked.CompareExchange(ref _callback, newList, null); + var newList = new AsyncCallbackCollection(this); + newList.AddProgressCallback(callbackToAdd, syncContext); + newValue = newList; } + oldValue = Interlocked.CompareExchange(ref _callback, newValue, null); + // Quick return if exchange succeeded. if (oldValue == null) { @@ -382,7 +380,15 @@ private bool TryAddCallback(object callbackToAdd, SynchronizationContext syncCon // If so, then fall through and return false without queuing the callback. if (_callback != _callbackCompletionSentinel) { - list.Add(callbackToAdd, syncContext); + if (completionCallback) + { + list.AddCompletionCallback(callbackToAdd, syncContext); + } + else + { + list.AddProgressCallback(callbackToAdd, syncContext); + } + return true; } } diff --git a/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncCallbackCollection.cs b/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncCallbackCollection.cs index 23f9c1d..b45c1e5 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncCallbackCollection.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncCallbackCollection.cs @@ -26,12 +26,13 @@ public CallbackData(object callback, SynchronizationContext syncContext) private readonly IAsyncOperation _op; - private CallbackData _callback1; - private CallbackData _callback2; - private CallbackData _callback3; - private CallbackData _callback4; + private CallbackData _completionCallback1; + private CallbackData _completionCallback2; + private CallbackData _completionCallback3; + private CallbackData _progressCallback1; - private List _callbacks; + private List _progressCallbacks; + private List _completionCallbacks; #endregion @@ -45,82 +46,110 @@ public AsyncCallbackCollection(IAsyncOperation op) public AsyncCallbackCollection(IAsyncOperation op, object callback, SynchronizationContext syncContext) { _op = op; - _callback1 = new CallbackData(callback, syncContext); + _completionCallback1 = new CallbackData(callback, syncContext); } - public void Add(object callback, SynchronizationContext syncContext) + public void AddCompletionCallback(object callback, SynchronizationContext syncContext) { var newCallback = new CallbackData(callback, syncContext); - if (_callback1.Callback == null) + if (_completionCallback1.Callback == null) { - _callback1 = newCallback; + _completionCallback1 = newCallback; return; } - if (_callback2.Callback == null) + if (_completionCallback2.Callback == null) { - _callback2 = newCallback; + _completionCallback2 = newCallback; return; } - if (_callback3.Callback == null) + if (_completionCallback3.Callback == null) { - _callback3 = newCallback; + _completionCallback3 = newCallback; return; } - if (_callback4.Callback == null) + if (_completionCallbacks == null) { - _callback4 = newCallback; + _completionCallbacks = new List() { newCallback }; + } + else + { + _completionCallbacks.Add(newCallback); + } + } + + public void AddProgressCallback(object callback, SynchronizationContext syncContext) + { + var newCallback = new CallbackData(callback, syncContext); + + if (_progressCallback1.Callback == null) + { + _progressCallback1 = newCallback; return; } - if (_callbacks == null) + if (_progressCallbacks == null) { - _callbacks = new List() { newCallback }; + _progressCallbacks = new List() { newCallback }; } else { - _callbacks.Add(newCallback); + _progressCallbacks.Add(newCallback); } } public bool Remove(object callback) { - if (_callback1.Callback == callback) + if (_completionCallback1.Callback == callback) { - _callback1 = default(CallbackData); + _completionCallback1 = default(CallbackData); return true; } - if (_callback2.Callback == callback) + if (_completionCallback2.Callback == callback) { - _callback2 = default(CallbackData); + _completionCallback2 = default(CallbackData); return true; } - if (_callback3.Callback == callback) + if (_completionCallback3.Callback == callback) { - _callback3 = default(CallbackData); + _completionCallback3 = default(CallbackData); return true; } - if (_callback4.Callback == callback) + if (_progressCallback1.Callback == callback) { - _callback4 = default(CallbackData); + _progressCallback1 = default(CallbackData); return true; } - if (_callbacks != null) + if (_completionCallbacks != null) { - var count = _callbacks.Count; + var count = _completionCallbacks.Count; - for (var i = 0; i < count; i++) + for (var i = 0; i < count; ++i) { - if (_callbacks[i].Callback == callback) + if (_completionCallbacks[i].Callback == callback) { - _callbacks.RemoveAt(i); + _completionCallbacks.RemoveAt(i); + return true; + } + } + } + + if (_progressCallbacks != null) + { + var count = _progressCallbacks.Count; + + for (var i = 0; i < count; ++i) + { + if (_progressCallbacks[i].Callback == callback) + { + _progressCallbacks.RemoveAt(i); return true; } } @@ -131,29 +160,37 @@ public bool Remove(object callback) public void Invoke() { - if (_callback1.Callback != null) + if (_progressCallback1.Callback != null) + { + Invoke(_progressCallback1); + } + + if (_progressCallbacks != null) { - Invoke(_callback1); + foreach (var item in _progressCallbacks) + { + Invoke(item); + } } - if (_callback2.Callback != null) + if (_completionCallback1.Callback != null) { - Invoke(_callback2); + Invoke(_completionCallback1); } - if (_callback3.Callback != null) + if (_completionCallback2.Callback != null) { - Invoke(_callback3); + Invoke(_completionCallback2); } - if (_callback4.Callback != null) + if (_completionCallback3.Callback != null) { - Invoke(_callback4); + Invoke(_completionCallback3); } - if (_callbacks != null) + if (_completionCallbacks != null) { - foreach (var item in _callbacks) + foreach (var item in _completionCallbacks) { Invoke(item); } @@ -162,29 +199,37 @@ public void Invoke() public void InvokeAsync() { - if (_callback1.Callback != null) + if (_progressCallback1.Callback != null) { - InvokeAsync(_callback1); + InvokeAsync(_progressCallback1); + } + + if (_progressCallbacks != null) + { + foreach (var item in _progressCallbacks) + { + InvokeAsync(item); + } } - if (_callback2.Callback != null) + if (_completionCallback1.Callback != null) { - InvokeAsync(_callback2); + InvokeAsync(_completionCallback1); } - if (_callback3.Callback != null) + if (_completionCallback2.Callback != null) { - InvokeAsync(_callback3); + InvokeAsync(_completionCallback2); } - if (_callback4.Callback != null) + if (_completionCallback3.Callback != null) { - InvokeAsync(_callback4); + InvokeAsync(_completionCallback3); } - if (_callbacks != null) + if (_completionCallbacks != null) { - foreach (var item in _callbacks) + foreach (var item in _completionCallbacks) { InvokeAsync(item); } @@ -193,29 +238,14 @@ public void InvokeAsync() public void InvokeProgressChanged() { - if (_callback1.Callback != null) - { - InvokeProgressChanged(_callback1); - } - - if (_callback2.Callback != null) - { - InvokeProgressChanged(_callback2); - } - - if (_callback3.Callback != null) - { - InvokeProgressChanged(_callback3); - } - - if (_callback4.Callback != null) + if (_progressCallback1.Callback != null) { - InvokeProgressChanged(_callback4); + InvokeProgressChanged(_progressCallback1); } - if (_callbacks != null) + if (_progressCallbacks != null) { - foreach (var item in _callbacks) + foreach (var item in _progressCallbacks) { InvokeProgressChanged(item); } From 2ca6bc9aef973af6e8a104d1f13236432ec150d3 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 31 May 2018 22:49:55 +0300 Subject: [PATCH 22/67] Removed obsolete code --- .../Api/Core/AsyncResult.Events.cs | 30 ++--- src/UnityFx.Async/Api/Core/AsyncResult.cs | 4 +- .../Api/Extensions/AsyncExtensions.Tasks.cs | 44 +++++- .../AsyncCallbackCollection.cs | 75 +++++++++-- .../Callbacks/AsyncContinuation.cs | 125 ------------------ .../Continuations/Callbacks/AsyncInvokable.cs | 118 ----------------- .../Continuations/Callbacks/AsyncProgress.cs | 51 ------- .../Continuations/ContinueWithResult{T,U}.cs | 21 ++- 8 files changed, 138 insertions(+), 330 deletions(-) rename src/UnityFx.Async/Implementation/Continuations/{Callbacks => }/AsyncCallbackCollection.cs (78%) delete mode 100644 src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncContinuation.cs delete mode 100644 src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncInvokable.cs delete mode 100644 src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncProgress.cs diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs index 696d436..e666e86 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs @@ -55,7 +55,7 @@ public event ProgressChangedEventHandler ProgressChanged if (!TryAddCallback(value, syncContext, false)) { - InvokeProgressChanged(value, syncContext); + AsyncCallbackCollection.InvokeProgressCallback(this, value, syncContext); } } remove @@ -210,7 +210,7 @@ public void AddProgressCallback(AsyncOperationCallback action) { if (!TryAddProgressCallback(action)) { - InvokeProgressChanged(action, SynchronizationContext.Current); + AsyncCallbackCollection.InvokeProgressCallback(this, action, SynchronizationContext.Current); } } @@ -232,7 +232,7 @@ public void AddProgressCallback(AsyncOperationCallback action, SynchronizationCo { if (!TryAddProgressCallback(action, syncContext)) { - InvokeProgressChanged(action, syncContext); + AsyncCallbackCollection.InvokeProgressCallback(this, action, syncContext); } } @@ -267,7 +267,7 @@ public void AddProgressCallback(IProgress callback) { if (!TryAddProgressCallback(callback)) { - InvokeProgressChanged(callback, SynchronizationContext.Current); + AsyncCallbackCollection.InvokeProgressCallback(this, callback, SynchronizationContext.Current); } } @@ -289,7 +289,7 @@ public void AddProgressCallback(IProgress callback, SynchronizationContex { if (!TryAddProgressCallback(callback, syncContext)) { - InvokeProgressChanged(callback, syncContext); + AsyncCallbackCollection.InvokeProgressCallback(this, callback, syncContext); } } @@ -442,7 +442,7 @@ private bool TryRemoveCallback(object callbackToRemove) return false; } - private void InvokeProgressChanged() + private void InvokeProgressCallbacks() { var value = _callback; @@ -452,28 +452,16 @@ private void InvokeProgressChanged() { lock (callbackList) { - callbackList.InvokeProgressChanged(); + callbackList.InvokeProgressCallbacks(); } } else { - AsyncProgress.InvokeInline(this, value); + AsyncCallbackCollection.InvokeProgressCallback(this, value); } } } - private void InvokeProgressChanged(object callback, SynchronizationContext syncContext) - { - if (syncContext == null || syncContext == SynchronizationContext.Current) - { - AsyncProgress.InvokeInline(this, callback); - } - else - { - syncContext.Post(args => AsyncProgress.InvokeInline(this, args), callback); - } - } - private void InvokeCallbacks() { var value = Interlocked.Exchange(ref _callback, _callbackCompletionSentinel); @@ -557,7 +545,7 @@ private void InvokeCallbackAsync(object continuation, SynchronizationContext syn private void InvokeContinuationInline(object continuation, bool inline) { - AsyncContinuation.InvokeInline(this, continuation, inline); + AsyncCallbackCollection.InvokeCompletionCallback(this, continuation, inline); } #endregion diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 7c4c45e..59642f5 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -477,7 +477,7 @@ protected internal bool TryReportProgress() if (status == StatusRunning) { OnProgressChanged(); - InvokeProgressChanged(); + InvokeProgressCallbacks(); return true; } @@ -810,7 +810,7 @@ internal bool TryCopyCompletionState(IAsyncOperation patternOp, bool completedSy internal void ReportProgress() { OnProgressChanged(); - InvokeProgressChanged(); + InvokeProgressCallbacks(); } /// diff --git a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs index b0f7a53..d88d5d2 100644 --- a/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs +++ b/src/UnityFx.Async/Api/Extensions/AsyncExtensions.Tasks.cs @@ -218,9 +218,9 @@ public static Task ToTask(this IAsyncOperation op) { var result = new TaskCompletionSource(); - if (!op.TryAddCompletionCallback(asyncOp => AsyncContinuation.InvokeTaskContinuation(asyncOp, result), null)) + if (!op.TryAddCompletionCallback(asyncOp => InvokeTaskContinuation(asyncOp, result), null)) { - AsyncContinuation.InvokeTaskContinuation(op, result); + InvokeTaskContinuation(op, result); } return result.Task; @@ -252,9 +252,9 @@ 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 => InvokeTaskContinuation(asyncOp as IAsyncOperation, result), null)) { - AsyncContinuation.InvokeTaskContinuation(op, result); + InvokeTaskContinuation(op, result); } return result.Task; @@ -303,6 +303,42 @@ private static void SetAwaitContiniation(IAsyncOperation op, Action continuation } } + private static void InvokeTaskContinuation(IAsyncOperation op, TaskCompletionSource tcs) + { + var status = op.Status; + + if (status == AsyncOperationStatus.RanToCompletion) + { + tcs.TrySetResult(null); + } + else if (status == AsyncOperationStatus.Faulted) + { + tcs.TrySetException(op.Exception); + } + else if (status == AsyncOperationStatus.Canceled) + { + tcs.TrySetCanceled(); + } + } + + private static void InvokeTaskContinuation(IAsyncOperation op, TaskCompletionSource tcs) + { + var status = op.Status; + + if (status == AsyncOperationStatus.RanToCompletion) + { + tcs.TrySetResult(op.Result); + } + else if (status == AsyncOperationStatus.Faulted) + { + tcs.TrySetException(op.Exception); + } + else if (status == AsyncOperationStatus.Canceled) + { + tcs.TrySetCanceled(); + } + } + #endregion } diff --git a/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncCallbackCollection.cs b/src/UnityFx.Async/Implementation/Continuations/AsyncCallbackCollection.cs similarity index 78% rename from src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncCallbackCollection.cs rename to src/UnityFx.Async/Implementation/Continuations/AsyncCallbackCollection.cs index b45c1e5..e6f3788 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncCallbackCollection.cs +++ b/src/UnityFx.Async/Implementation/Continuations/AsyncCallbackCollection.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Diagnostics; using System.Threading; @@ -162,14 +163,14 @@ public void Invoke() { if (_progressCallback1.Callback != null) { - Invoke(_progressCallback1); + InvokeProgressCallback(_progressCallback1); } if (_progressCallbacks != null) { foreach (var item in _progressCallbacks) { - Invoke(item); + InvokeProgressCallback(item); } } @@ -236,22 +237,80 @@ public void InvokeAsync() } } - public void InvokeProgressChanged() + public void InvokeProgressCallbacks() { if (_progressCallback1.Callback != null) { - InvokeProgressChanged(_progressCallback1); + InvokeProgressCallback(_progressCallback1); } if (_progressCallbacks != null) { foreach (var item in _progressCallbacks) { - InvokeProgressChanged(item); + InvokeProgressCallback(item); } } } + public static void InvokeCompletionCallback(IAsyncOperation op, object continuation, bool inline) + { + switch (continuation) + { + case IAsyncContinuation c: + c.Invoke(op, inline); + break; + + case AsyncOperationCallback aoc: + aoc.Invoke(op); + break; + + case Action a: + a.Invoke(); + break; + + case AsyncCallback ac: + ac.Invoke(op); + break; + + case AsyncCompletedEventHandler eh: + eh.Invoke(op, new AsyncCompletedEventArgs(op.Exception, op.IsCanceled, op.AsyncState)); + break; + } + } + + public static void InvokeProgressCallback(IAsyncOperation op, object callback) + { + switch (callback) + { +#if !NET35 + case IProgress p: + p.Report(op.Progress); + break; +#endif + + case AsyncOperationCallback ac: + ac.Invoke(op); + break; + + case ProgressChangedEventHandler ph: + ph.Invoke(op, new ProgressChangedEventArgs((int)(op.Progress * 100), op.AsyncState)); + break; + } + } + + public static void InvokeProgressCallback(IAsyncOperation op, object callback, SynchronizationContext syncContext) + { + if (syncContext == null || syncContext == SynchronizationContext.Current) + { + InvokeProgressCallback(op, callback); + } + else + { + syncContext.Post(args => InvokeProgressCallback(op, args), callback); + } + } + #endregion #region implementation @@ -293,7 +352,7 @@ private void InvokeAsync(CallbackData callbackData) } } - private void InvokeProgressChanged(CallbackData callbackData) + private void InvokeProgressCallback(CallbackData callbackData) { var syncContext = callbackData.SyncContext; @@ -310,13 +369,13 @@ private void InvokeProgressChanged(CallbackData callbackData) private void InvokeInline(object callback) { Debug.Assert(callback != null); - AsyncContinuation.InvokeInline(_op, callback, false); + InvokeCompletionCallback(_op, callback, false); } private void InvokeProgressChangedInline(object callback) { Debug.Assert(callback != null); - AsyncProgress.InvokeInline(_op, callback); + InvokeProgressCallback(_op, callback); } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncContinuation.cs b/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncContinuation.cs deleted file mode 100644 index 11d9aae..0000000 --- a/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncContinuation.cs +++ /dev/null @@ -1,125 +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.ComponentModel; -using System.Diagnostics; -using System.Threading; -#if !NET35 -using System.Threading.Tasks; -#endif - -namespace UnityFx.Async -{ - internal class AsyncContinuation : AsyncInvokable - { - #region interface - - internal AsyncContinuation(IAsyncOperation op, SynchronizationContext syncContext, object continuation) - : base(op, syncContext, continuation) - { - } - - 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 InvokeInline(IAsyncOperation op, object continuation, bool inline) - { - switch (continuation) - { - case IAsyncContinuation c: - c.Invoke(op, inline); - break; - - case AsyncOperationCallback aoc: - aoc.Invoke(op); - break; - - case Action a: - a.Invoke(); - break; - - case AsyncCallback ac: - ac.Invoke(op); - break; - - case AsyncCompletedEventHandler eh: - eh.Invoke(op, new AsyncCompletedEventArgs(op.Exception, op.IsCanceled, op.AsyncState)); - break; - -#if !NET35 - case IProgress p: - p.Report(op.Progress); - break; -#endif - - case ProgressChangedEventHandler ph: - ph.Invoke(op, new ProgressChangedEventArgs((int)(op.Progress * 100), op.AsyncState)); - break; - } - } - -#if !NET35 - - internal static void InvokeTaskContinuation(IAsyncOperation op, TaskCompletionSource tcs) - { - var status = op.Status; - - if (status == AsyncOperationStatus.RanToCompletion) - { - tcs.TrySetResult(null); - } - else if (status == AsyncOperationStatus.Faulted) - { - tcs.TrySetException(op.Exception); - } - else if (status == AsyncOperationStatus.Canceled) - { - tcs.TrySetCanceled(); - } - } - - internal static void InvokeTaskContinuation(IAsyncOperation op, TaskCompletionSource tcs) - { - var status = op.Status; - - if (status == AsyncOperationStatus.RanToCompletion) - { - tcs.TrySetResult(op.Result); - } - else if (status == AsyncOperationStatus.Faulted) - { - tcs.TrySetException(op.Exception); - } - else if (status == AsyncOperationStatus.Canceled) - { - tcs.TrySetCanceled(); - } - } - -#endif - - #endregion - - #region AsyncInvokable - - protected override void Invoke(IAsyncOperation op, object continuation) - { - InvokeInline(op, continuation, false); - } - - #endregion - } -} diff --git a/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncInvokable.cs b/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncInvokable.cs deleted file mode 100644 index 6a78938..0000000 --- a/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncInvokable.cs +++ /dev/null @@ -1,118 +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.ComponentModel; -using System.Diagnostics; -using System.Threading; - -namespace UnityFx.Async -{ - internal abstract class AsyncInvokable - { - #region data - - private static WaitCallback _waitCallback; - private static SendOrPostCallback _postCallback; - - private IAsyncOperation _op; - private SynchronizationContext _syncContext; - private object _callback; - - #endregion - - #region interface - - internal AsyncInvokable(IAsyncOperation op, SynchronizationContext syncContext, object callback) - { - _op = op; - _syncContext = syncContext; - _callback = callback; - } - - internal void Invoke() - { - if (_syncContext == null || _syncContext == SynchronizationContext.Current) - { - Invoke(_op, _callback); - } - else - { - InvokeOnSyncContext(_syncContext); - } - } - - internal void InvokeAsync() - { - var syncContext = _syncContext; - - if (syncContext != null) - { - InvokeOnSyncContext(syncContext); - } - else - { - syncContext = SynchronizationContext.Current; - - if (syncContext != null) - { - InvokeOnSyncContext(syncContext); - } - else - { - if (_waitCallback == null) - { - _waitCallback = PostCallback; - } - - ThreadPool.QueueUserWorkItem(_waitCallback, this); - } - } - } - - protected abstract void Invoke(IAsyncOperation op, object callback); - - #endregion - - #region Object - - public override bool Equals(object obj) - { - if (ReferenceEquals(obj, _callback)) - { - return true; - } - - return base.Equals(obj); - } - - public override int GetHashCode() - { - return _callback.GetHashCode(); - } - - #endregion - - #region implementation - - private void InvokeOnSyncContext(SynchronizationContext syncContext) - { - Debug.Assert(_syncContext != null); - - if (_postCallback == null) - { - _postCallback = PostCallback; - } - - syncContext.Post(_postCallback, this); - } - - private static void PostCallback(object args) - { - var c = args as AsyncInvokable; - c.Invoke(c._op, c._callback); - } - - #endregion - } -} diff --git a/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncProgress.cs b/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncProgress.cs deleted file mode 100644 index 5a4c8e9..0000000 --- a/src/UnityFx.Async/Implementation/Continuations/Callbacks/AsyncProgress.cs +++ /dev/null @@ -1,51 +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.ComponentModel; -using System.Diagnostics; -using System.Threading; - -namespace UnityFx.Async -{ - internal class AsyncProgress : AsyncInvokable - { - #region interface - - internal AsyncProgress(IAsyncOperation op, SynchronizationContext syncContext, object callback) - : base(op, syncContext, callback) - { - } - - internal static void InvokeInline(IAsyncOperation op, object callback) - { - switch (callback) - { -#if !NET35 - case IProgress p: - p.Report(op.Progress); - break; -#endif - - case AsyncOperationCallback ac: - ac.Invoke(op); - break; - - case ProgressChangedEventHandler ph: - ph.Invoke(op, new ProgressChangedEventArgs((int)(op.Progress * 100), op.AsyncState)); - break; - } - } - - #endregion - - #region AsyncInvokable - - protected override void Invoke(IAsyncOperation op, object continuation) - { - InvokeInline(op, continuation); - } - - #endregion - } -} diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs index 9a7eca6..675f3ec 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs @@ -58,7 +58,7 @@ protected override void OnCancel() public void Invoke(IAsyncOperation op, bool inline) { - if (AsyncContinuation.CanInvoke(op, _options)) + if (CanInvoke()) { try { @@ -117,5 +117,24 @@ public void Invoke(IAsyncOperation op, bool inline) } #endregion + + #region implementation + + private bool CanInvoke() + { + if (_op.IsCompletedSuccessfully) + { + return (_options & AsyncContinuationOptions.NotOnRanToCompletion) == 0; + } + + if (_op.IsFaulted) + { + return (_options & AsyncContinuationOptions.NotOnFaulted) == 0; + } + + return (_options & AsyncContinuationOptions.NotOnCanceled) == 0; + } + + #endregion } } From 3b19ee20694365b2a1c3f695eac7a9ffa9cb8784 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 31 May 2018 23:16:07 +0300 Subject: [PATCH 23/67] Events code refactoring --- .../Api/Core/AsyncResult.Events.cs | 60 ++-------- .../Continuations/AsyncCallbackCollection.cs | 104 +++++++----------- 2 files changed, 45 insertions(+), 119 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs index e666e86..6544392 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs @@ -468,49 +468,22 @@ private void InvokeCallbacks() if (value != null) { - if (value is AsyncCallbackCollection callbackList) - { - lock (callbackList) - { - callbackList.Invoke(); - } - } - else - { - InvokeCallback(value); - } - } - } + var invokeAsync = (_flags & _flagRunContinuationsAsynchronously) != 0; - private void InvokeCallback(object value) - { - if ((_flags & _flagRunContinuationsAsynchronously) != 0) - { if (value is AsyncCallbackCollection callbackList) { - // NOTE: This is more effective than InvokeContinuationAsync(). lock (callbackList) { - callbackList.InvokeAsync(); + callbackList.Invoke(invokeAsync); } } - else + else if (invokeAsync) { - InvokeCallbackAsync(value, SynchronizationContext.Current, false); - } - } - else - { - if (value is AsyncCallbackCollection callbackList) - { - lock (callbackList) - { - callbackList.Invoke(); - } + AsyncCallbackCollection.InvokeCompletionCallbackAsync(this, value, SynchronizationContext.Current, false); } else { - InvokeContinuationInline(value, false); + AsyncCallbackCollection.InvokeCompletionCallback(this, value, false); } } } @@ -519,35 +492,18 @@ private void InvokeCompletionCallback(object continuation, SynchronizationContex { if ((_flags & _flagRunContinuationsAsynchronously) != 0) { - InvokeCallbackAsync(continuation, syncContext, true); + AsyncCallbackCollection.InvokeCompletionCallbackAsync(this, continuation, syncContext, true); } else if (syncContext == null || syncContext == SynchronizationContext.Current) { - InvokeContinuationInline(continuation, true); + AsyncCallbackCollection.InvokeCompletionCallback(this, continuation, true); } else { - syncContext.Post(args => InvokeContinuationInline(args, true), continuation); + syncContext.Post(args => AsyncCallbackCollection.InvokeCompletionCallback(this, args, true), continuation); } } - private void InvokeCallbackAsync(object continuation, SynchronizationContext syncContext, bool inline) - { - if (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) - { - syncContext.Post(args => InvokeContinuationInline(args, inline), continuation); - } - else - { - ThreadPool.QueueUserWorkItem(args => InvokeContinuationInline(args, inline), continuation); - } - } - - private void InvokeContinuationInline(object continuation, bool inline) - { - AsyncCallbackCollection.InvokeCompletionCallback(this, continuation, inline); - } - #endregion } } diff --git a/src/UnityFx.Async/Implementation/Continuations/AsyncCallbackCollection.cs b/src/UnityFx.Async/Implementation/Continuations/AsyncCallbackCollection.cs index e6f3788..0d13f9b 100644 --- a/src/UnityFx.Async/Implementation/Continuations/AsyncCallbackCollection.cs +++ b/src/UnityFx.Async/Implementation/Continuations/AsyncCallbackCollection.cs @@ -159,7 +159,7 @@ public bool Remove(object callback) return false; } - public void Invoke() + public void Invoke(bool invokeAsync) { if (_progressCallback1.Callback != null) { @@ -176,63 +176,24 @@ public void Invoke() if (_completionCallback1.Callback != null) { - Invoke(_completionCallback1); + Invoke(_completionCallback1, invokeAsync); } if (_completionCallback2.Callback != null) { - Invoke(_completionCallback2); + Invoke(_completionCallback2, invokeAsync); } if (_completionCallback3.Callback != null) { - Invoke(_completionCallback3); + Invoke(_completionCallback3, invokeAsync); } if (_completionCallbacks != null) { foreach (var item in _completionCallbacks) { - Invoke(item); - } - } - } - - public void InvokeAsync() - { - if (_progressCallback1.Callback != null) - { - InvokeAsync(_progressCallback1); - } - - if (_progressCallbacks != null) - { - foreach (var item in _progressCallbacks) - { - InvokeAsync(item); - } - } - - if (_completionCallback1.Callback != null) - { - InvokeAsync(_completionCallback1); - } - - if (_completionCallback2.Callback != null) - { - InvokeAsync(_completionCallback2); - } - - if (_completionCallback3.Callback != null) - { - InvokeAsync(_completionCallback3); - } - - if (_completionCallbacks != null) - { - foreach (var item in _completionCallbacks) - { - InvokeAsync(item); + Invoke(item, invokeAsync); } } } @@ -279,6 +240,18 @@ public static void InvokeCompletionCallback(IAsyncOperation op, object continuat } } + public static void InvokeCompletionCallbackAsync(IAsyncOperation op, object continuation, SynchronizationContext syncContext, bool inline) + { + if (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) + { + syncContext.Post(args => InvokeCompletionCallback(op, args, inline), continuation); + } + else + { + ThreadPool.QueueUserWorkItem(args => InvokeCompletionCallback(op, args, inline), continuation); + } + } + public static void InvokeProgressCallback(IAsyncOperation op, object callback) { switch (callback) @@ -315,41 +288,38 @@ public static void InvokeProgressCallback(IAsyncOperation op, object callback, S #region implementation - private void Invoke(CallbackData callbackData) - { - var syncContext = callbackData.SyncContext; - - if (syncContext == null || syncContext == SynchronizationContext.Current) - { - InvokeInline(callbackData.Callback); - } - else - { - syncContext.Post(InvokeInline, callbackData.Callback); - } - } - - private void InvokeAsync(CallbackData callbackData) + private void Invoke(CallbackData callbackData, bool invokeAsync) { var syncContext = callbackData.SyncContext; - if (syncContext != null) + if (invokeAsync) { - syncContext.Post(InvokeInline, callbackData.Callback); - } - else - { - syncContext = SynchronizationContext.Current; - if (syncContext != null) { syncContext.Post(InvokeInline, callbackData.Callback); } else { - ThreadPool.QueueUserWorkItem(InvokeInline, callbackData.Callback); + syncContext = SynchronizationContext.Current; + + if (syncContext != null) + { + syncContext.Post(InvokeInline, callbackData.Callback); + } + else + { + ThreadPool.QueueUserWorkItem(InvokeInline, callbackData.Callback); + } } } + else if (syncContext == null || syncContext == SynchronizationContext.Current) + { + InvokeInline(callbackData.Callback); + } + else + { + syncContext.Post(InvokeInline, callbackData.Callback); + } } private void InvokeProgressCallback(CallbackData callbackData) From c35d1be3cce679f01bba1a40231a3391c3dd8c69 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 1 Jun 2018 12:43:39 +0300 Subject: [PATCH 24/67] Added CallbackUtility class --- .../Api/Core/AsyncResult.Events.cs | 42 ++++----- ...ackCollection.cs => CallbackCollection.cs} | 80 +--------------- .../Continuations/CallbackUtility.cs | 93 +++++++++++++++++++ 3 files changed, 119 insertions(+), 96 deletions(-) rename src/UnityFx.Async/Implementation/Continuations/{AsyncCallbackCollection.cs => CallbackCollection.cs} (73%) create mode 100644 src/UnityFx.Async/Implementation/Continuations/CallbackUtility.cs diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs index 6544392..378f69f 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs @@ -55,7 +55,7 @@ public event ProgressChangedEventHandler ProgressChanged if (!TryAddCallback(value, syncContext, false)) { - AsyncCallbackCollection.InvokeProgressCallback(this, value, syncContext); + CallbackUtility.InvokeProgressCallback(this, value, syncContext); } } remove @@ -210,7 +210,7 @@ public void AddProgressCallback(AsyncOperationCallback action) { if (!TryAddProgressCallback(action)) { - AsyncCallbackCollection.InvokeProgressCallback(this, action, SynchronizationContext.Current); + CallbackUtility.InvokeProgressCallback(this, action, SynchronizationContext.Current); } } @@ -232,7 +232,7 @@ public void AddProgressCallback(AsyncOperationCallback action, SynchronizationCo { if (!TryAddProgressCallback(action, syncContext)) { - AsyncCallbackCollection.InvokeProgressCallback(this, action, syncContext); + CallbackUtility.InvokeProgressCallback(this, action, syncContext); } } @@ -267,7 +267,7 @@ public void AddProgressCallback(IProgress callback) { if (!TryAddProgressCallback(callback)) { - AsyncCallbackCollection.InvokeProgressCallback(this, callback, SynchronizationContext.Current); + CallbackUtility.InvokeProgressCallback(this, callback, SynchronizationContext.Current); } } @@ -289,7 +289,7 @@ public void AddProgressCallback(IProgress callback, SynchronizationContex { if (!TryAddProgressCallback(callback, syncContext)) { - AsyncCallbackCollection.InvokeProgressCallback(this, callback, syncContext); + CallbackUtility.InvokeProgressCallback(this, callback, syncContext); } } @@ -340,12 +340,12 @@ private bool TryAddCallback(object callbackToAdd, SynchronizationContext syncCon { if (syncContext != null) { - newValue = new AsyncCallbackCollection(this, callbackToAdd, syncContext); + newValue = new CallbackCollection(this, callbackToAdd, syncContext); } } else { - var newList = new AsyncCallbackCollection(this); + var newList = new CallbackCollection(this); newList.AddProgressCallback(callbackToAdd, syncContext); newValue = newList; } @@ -360,9 +360,9 @@ private bool TryAddCallback(object callbackToAdd, SynchronizationContext syncCon } // Logic for the case where we were previously storing a single callback. - if (oldValue != _callbackCompletionSentinel && !(oldValue is AsyncCallbackCollection)) + if (oldValue != _callbackCompletionSentinel && !(oldValue is CallbackCollection)) { - var newList = new AsyncCallbackCollection(this, oldValue, null); + var newList = new CallbackCollection(this, oldValue, null); Interlocked.CompareExchange(ref _callback, newList, oldValue); // We might be racing against another thread converting the single into a list, @@ -372,7 +372,7 @@ private bool TryAddCallback(object callbackToAdd, SynchronizationContext syncCon // If list is null, it can only mean that _callbackCompletionSentinel has been exchanged // into _callback. Thus, the task has completed and we should return false from this method, // as we will not be queuing up the callback. - if (_callback is AsyncCallbackCollection list) + if (_callback is CallbackCollection list) { lock (list) { @@ -405,13 +405,13 @@ private bool TryRemoveCallback(object callbackToRemove) if (value != _callbackCompletionSentinel) { - var list = value as AsyncCallbackCollection; + var list = value as CallbackCollection; 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 TryAddCallback() logic. - if (Interlocked.CompareExchange(ref _callback, new AsyncCallbackCollection(this), callbackToRemove) == callbackToRemove) + if (Interlocked.CompareExchange(ref _callback, new CallbackCollection(this), callbackToRemove) == callbackToRemove) { return true; } @@ -420,7 +420,7 @@ private bool TryRemoveCallback(object callbackToRemove) // If we fail it means that either TryAddContinuation won the race condition and _callback is now a List // that contains the element we want to remove. Or it set the _callbackCompletionSentinel. // So we should try to get a list one more time. - list = _callback as AsyncCallbackCollection; + list = _callback as CallbackCollection; } } @@ -448,7 +448,7 @@ private void InvokeProgressCallbacks() if (value != null) { - if (value is AsyncCallbackCollection callbackList) + if (value is CallbackCollection callbackList) { lock (callbackList) { @@ -457,7 +457,7 @@ private void InvokeProgressCallbacks() } else { - AsyncCallbackCollection.InvokeProgressCallback(this, value); + CallbackUtility.InvokeProgressCallback(this, value); } } } @@ -470,7 +470,7 @@ private void InvokeCallbacks() { var invokeAsync = (_flags & _flagRunContinuationsAsynchronously) != 0; - if (value is AsyncCallbackCollection callbackList) + if (value is CallbackCollection callbackList) { lock (callbackList) { @@ -479,11 +479,11 @@ private void InvokeCallbacks() } else if (invokeAsync) { - AsyncCallbackCollection.InvokeCompletionCallbackAsync(this, value, SynchronizationContext.Current, false); + CallbackUtility.InvokeCompletionCallbackAsync(this, value, SynchronizationContext.Current, false); } else { - AsyncCallbackCollection.InvokeCompletionCallback(this, value, false); + CallbackUtility.InvokeCompletionCallback(this, value, false); } } } @@ -492,15 +492,15 @@ private void InvokeCompletionCallback(object continuation, SynchronizationContex { if ((_flags & _flagRunContinuationsAsynchronously) != 0) { - AsyncCallbackCollection.InvokeCompletionCallbackAsync(this, continuation, syncContext, true); + CallbackUtility.InvokeCompletionCallbackAsync(this, continuation, syncContext, true); } else if (syncContext == null || syncContext == SynchronizationContext.Current) { - AsyncCallbackCollection.InvokeCompletionCallback(this, continuation, true); + CallbackUtility.InvokeCompletionCallback(this, continuation, true); } else { - syncContext.Post(args => AsyncCallbackCollection.InvokeCompletionCallback(this, args, true), continuation); + syncContext.Post(args => CallbackUtility.InvokeCompletionCallback(this, args, true), continuation); } } diff --git a/src/UnityFx.Async/Implementation/Continuations/AsyncCallbackCollection.cs b/src/UnityFx.Async/Implementation/Continuations/CallbackCollection.cs similarity index 73% rename from src/UnityFx.Async/Implementation/Continuations/AsyncCallbackCollection.cs rename to src/UnityFx.Async/Implementation/Continuations/CallbackCollection.cs index 0d13f9b..6a8cb64 100644 --- a/src/UnityFx.Async/Implementation/Continuations/AsyncCallbackCollection.cs +++ b/src/UnityFx.Async/Implementation/Continuations/CallbackCollection.cs @@ -9,7 +9,7 @@ namespace UnityFx.Async { - internal class AsyncCallbackCollection + internal class CallbackCollection { #region data @@ -39,12 +39,12 @@ public CallbackData(object callback, SynchronizationContext syncContext) #region interface - public AsyncCallbackCollection(IAsyncOperation op) + public CallbackCollection(IAsyncOperation op) { _op = op; } - public AsyncCallbackCollection(IAsyncOperation op, object callback, SynchronizationContext syncContext) + public CallbackCollection(IAsyncOperation op, object callback, SynchronizationContext syncContext) { _op = op; _completionCallback1 = new CallbackData(callback, syncContext); @@ -214,76 +214,6 @@ public void InvokeProgressCallbacks() } } - public static void InvokeCompletionCallback(IAsyncOperation op, object continuation, bool inline) - { - switch (continuation) - { - case IAsyncContinuation c: - c.Invoke(op, inline); - break; - - case AsyncOperationCallback aoc: - aoc.Invoke(op); - break; - - case Action a: - a.Invoke(); - break; - - case AsyncCallback ac: - ac.Invoke(op); - break; - - case AsyncCompletedEventHandler eh: - eh.Invoke(op, new AsyncCompletedEventArgs(op.Exception, op.IsCanceled, op.AsyncState)); - break; - } - } - - public static void InvokeCompletionCallbackAsync(IAsyncOperation op, object continuation, SynchronizationContext syncContext, bool inline) - { - if (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) - { - syncContext.Post(args => InvokeCompletionCallback(op, args, inline), continuation); - } - else - { - ThreadPool.QueueUserWorkItem(args => InvokeCompletionCallback(op, args, inline), continuation); - } - } - - public static void InvokeProgressCallback(IAsyncOperation op, object callback) - { - switch (callback) - { -#if !NET35 - case IProgress p: - p.Report(op.Progress); - break; -#endif - - case AsyncOperationCallback ac: - ac.Invoke(op); - break; - - case ProgressChangedEventHandler ph: - ph.Invoke(op, new ProgressChangedEventArgs((int)(op.Progress * 100), op.AsyncState)); - break; - } - } - - public static void InvokeProgressCallback(IAsyncOperation op, object callback, SynchronizationContext syncContext) - { - if (syncContext == null || syncContext == SynchronizationContext.Current) - { - InvokeProgressCallback(op, callback); - } - else - { - syncContext.Post(args => InvokeProgressCallback(op, args), callback); - } - } - #endregion #region implementation @@ -339,13 +269,13 @@ private void InvokeProgressCallback(CallbackData callbackData) private void InvokeInline(object callback) { Debug.Assert(callback != null); - InvokeCompletionCallback(_op, callback, false); + CallbackUtility.InvokeCompletionCallback(_op, callback, false); } private void InvokeProgressChangedInline(object callback) { Debug.Assert(callback != null); - InvokeProgressCallback(_op, callback); + CallbackUtility.InvokeProgressCallback(_op, callback); } #endregion diff --git a/src/UnityFx.Async/Implementation/Continuations/CallbackUtility.cs b/src/UnityFx.Async/Implementation/Continuations/CallbackUtility.cs new file mode 100644 index 0000000..cf54888 --- /dev/null +++ b/src/UnityFx.Async/Implementation/Continuations/CallbackUtility.cs @@ -0,0 +1,93 @@ +// Copyright (c) Alexander Bogarsukov. +// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Threading; + +namespace UnityFx.Async +{ + internal static class CallbackUtility + { + #region data + #endregion + + #region interface + + public static void InvokeCompletionCallback(IAsyncOperation op, object continuation, bool inline) + { + switch (continuation) + { + case IAsyncContinuation c: + c.Invoke(op, inline); + break; + + case AsyncOperationCallback aoc: + aoc.Invoke(op); + break; + + case Action a: + a.Invoke(); + break; + + case AsyncCallback ac: + ac.Invoke(op); + break; + + case AsyncCompletedEventHandler eh: + eh.Invoke(op, new AsyncCompletedEventArgs(op.Exception, op.IsCanceled, op.AsyncState)); + break; + } + } + + public static void InvokeCompletionCallbackAsync(IAsyncOperation op, object continuation, SynchronizationContext syncContext, bool inline) + { + if (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) + { + syncContext.Post(args => InvokeCompletionCallback(op, args, inline), continuation); + } + else + { + ThreadPool.QueueUserWorkItem(args => InvokeCompletionCallback(op, args, inline), continuation); + } + } + + public static void InvokeProgressCallback(IAsyncOperation op, object callback) + { + switch (callback) + { +#if !NET35 + case IProgress p: + p.Report(op.Progress); + break; +#endif + + case AsyncOperationCallback ac: + ac.Invoke(op); + break; + + case ProgressChangedEventHandler ph: + ph.Invoke(op, new ProgressChangedEventArgs((int)(op.Progress * 100), op.AsyncState)); + break; + } + } + + public static void InvokeProgressCallback(IAsyncOperation op, object callback, SynchronizationContext syncContext) + { + if (syncContext == null || syncContext == SynchronizationContext.Current) + { + InvokeProgressCallback(op, callback); + } + else + { + syncContext.Post(args => InvokeProgressCallback(op, args), callback); + } + } + + #endregion + + #region implementation + #endregion + } +} From c49881149df4dc7d14e9f41e765ee95481528647 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 1 Jun 2018 12:49:50 +0300 Subject: [PATCH 25/67] Minor events code refactoring --- .../Api/Core/AsyncResult.Events.cs | 22 ++++++++++++++----- .../Continuations/CallbackUtility.cs | 12 ---------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs index 378f69f..c67af3d 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs @@ -55,7 +55,7 @@ public event ProgressChangedEventHandler ProgressChanged if (!TryAddCallback(value, syncContext, false)) { - CallbackUtility.InvokeProgressCallback(this, value, syncContext); + InvokeProgressCallback(value, syncContext); } } remove @@ -210,7 +210,7 @@ public void AddProgressCallback(AsyncOperationCallback action) { if (!TryAddProgressCallback(action)) { - CallbackUtility.InvokeProgressCallback(this, action, SynchronizationContext.Current); + InvokeProgressCallback(action, SynchronizationContext.Current); } } @@ -232,7 +232,7 @@ public void AddProgressCallback(AsyncOperationCallback action, SynchronizationCo { if (!TryAddProgressCallback(action, syncContext)) { - CallbackUtility.InvokeProgressCallback(this, action, syncContext); + InvokeProgressCallback(action, syncContext); } } @@ -267,7 +267,7 @@ public void AddProgressCallback(IProgress callback) { if (!TryAddProgressCallback(callback)) { - CallbackUtility.InvokeProgressCallback(this, callback, SynchronizationContext.Current); + InvokeProgressCallback(callback, SynchronizationContext.Current); } } @@ -289,7 +289,7 @@ public void AddProgressCallback(IProgress callback, SynchronizationContex { if (!TryAddProgressCallback(callback, syncContext)) { - CallbackUtility.InvokeProgressCallback(this, callback, syncContext); + InvokeProgressCallback(callback, syncContext); } } @@ -488,6 +488,18 @@ private void InvokeCallbacks() } } + private void InvokeProgressCallback(object callback, SynchronizationContext syncContext) + { + if (syncContext == null || syncContext == SynchronizationContext.Current) + { + CallbackUtility.InvokeProgressCallback(this, callback); + } + else + { + syncContext.Post(args => CallbackUtility.InvokeProgressCallback(this, args), callback); + } + } + private void InvokeCompletionCallback(object continuation, SynchronizationContext syncContext) { if ((_flags & _flagRunContinuationsAsynchronously) != 0) diff --git a/src/UnityFx.Async/Implementation/Continuations/CallbackUtility.cs b/src/UnityFx.Async/Implementation/Continuations/CallbackUtility.cs index cf54888..d4a8640 100644 --- a/src/UnityFx.Async/Implementation/Continuations/CallbackUtility.cs +++ b/src/UnityFx.Async/Implementation/Continuations/CallbackUtility.cs @@ -73,18 +73,6 @@ public static void InvokeProgressCallback(IAsyncOperation op, object callback) } } - public static void InvokeProgressCallback(IAsyncOperation op, object callback, SynchronizationContext syncContext) - { - if (syncContext == null || syncContext == SynchronizationContext.Current) - { - InvokeProgressCallback(op, callback); - } - else - { - syncContext.Post(args => InvokeProgressCallback(op, args), callback); - } - } - #endregion #region implementation From 0365f1f42e5b61287915af24eafd7088625afde4 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 1 Jun 2018 13:14:53 +0300 Subject: [PATCH 26/67] CHANGELOG update --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aff0047..ad45882 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/); this proj - Added push-based progress reporting support. - Added update sources for `LateUpdate`, `FixedUpdate` and end-of-frame updates. +### Changed +- Significantly reduced number of memory allocations when adding continuations (implemention optimizations). + ### Fixed - Fixed exception when removing listeners while in `AsyncUpdateSource.OnError` / `AsyncUpdateSource.OnCompleted` / `AsyncUpdateSource.Dispose`. From 8905822f93acaea31c84ea2735a7f098b3d46622 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 1 Jun 2018 18:42:58 +0300 Subject: [PATCH 27/67] Added progress reporting tests --- .../Tests/ProgressCallbackTests.cs | 71 +++++++++++++++---- 1 file changed, 59 insertions(+), 12 deletions(-) diff --git a/src/UnityFx.Async.Tests/Tests/ProgressCallbackTests.cs b/src/UnityFx.Async.Tests/Tests/ProgressCallbackTests.cs index fa98856..fed71cc 100644 --- a/src/UnityFx.Async.Tests/Tests/ProgressCallbackTests.cs +++ b/src/UnityFx.Async.Tests/Tests/ProgressCallbackTests.cs @@ -2,6 +2,8 @@ // Licensed under the MIT license. See the LICENSE.md file in the project root for more information. using System; +using System.ComponentModel; +using NSubstitute; using Xunit; namespace UnityFx.Async @@ -44,28 +46,57 @@ public void TrySetProgress_ReturnsCorrentValue(AsyncOperationStatus status, bool Assert.Equal(expectedResult, result); } + [Theory] + [InlineData(AsyncOperationStatus.Created, 0)] + [InlineData(AsyncOperationStatus.Scheduled, 0)] + [InlineData(AsyncOperationStatus.RanToCompletion, 1)] + [InlineData(AsyncOperationStatus.Faulted, 0)] + [InlineData(AsyncOperationStatus.Canceled, 0)] + public void Progress_ReturnsCorrentValue(AsyncOperationStatus status, float expectedValue) + { + // Arrange + var progress = 0.3f; + var op = new AsyncCompletionSource(status); + + // Act + op.TrySetProgress(progress); + + // Assert + Assert.Equal(expectedValue, op.Progress); + } + + [Fact] + public void SetProgress_SetsCorrectValue() + { + // Arrange + var progress = 0.7f; + var op = new AsyncCompletionSource(AsyncOperationStatus.Running); + + // Act + op.SetProgress(progress); + + // Assert + Assert.Equal(progress, op.Progress); + } + [Fact] - public void SetCompleted_RaisesProgressCallbacks() + public void SetProgress_RaisesProgressChanged() { // Arrange var asyncCallbackCalled = false; - var progress = 0f; - var op = new AsyncCompletionSource(); + var op = new AsyncCompletionSource(AsyncOperationStatus.Running); - op.AddProgressCallback( - asyncOp => - { - asyncCallbackCalled = true; - progress = asyncOp.Progress; - }, - null); + op.ProgressChanged += (sender, args) => + { + asyncCallbackCalled = true; + }; // Act - op.SetCompleted(); + op.SetProgress(0.8f); // Assert Assert.True(asyncCallbackCalled); - Assert.Equal(1, progress); + } [Fact] @@ -73,8 +104,11 @@ public void SetCompleted_RaisesProgressChanged() { // Arrange var asyncCallbackCalled = false; + var asyncCallbackCalled2 = false; var progress = 0; + var progress2 = 0f; var op = new AsyncCompletionSource(); + var p = Substitute.For>(); op.ProgressChanged += (sender, args) => { @@ -82,12 +116,25 @@ public void SetCompleted_RaisesProgressChanged() progress = args.ProgressPercentage; }; + op.AddProgressCallback( + asyncOp => + { + asyncCallbackCalled2 = true; + progress2 = asyncOp.Progress; + }, + null); + + op.AddProgressCallback(p, null); + // Act op.SetCompleted(); // Assert Assert.True(asyncCallbackCalled); + Assert.True(asyncCallbackCalled2); Assert.Equal(100, progress); + Assert.Equal(1, progress2); + p.Received(1).Report(1); } #endregion From b65eeaf0106e3cce9cd204a439fc40459df7ed68 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 1 Jun 2018 18:53:12 +0300 Subject: [PATCH 28/67] README update --- README.md | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4317350..1680caa 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ The table below summarizes differences berween *UnityFx.Async* and other popular | Supports progress reporting | ✔️ | ✔️ | ✔️ | | 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+ | +| Minimum number of allocations per continuation | ~1 | 5+ | 2+ | ## Getting Started ### Prerequisites @@ -166,9 +166,9 @@ 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 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). +The type of the operation should reflect its result type. In this case we create a special kind of operation - a completion source, that incapsulates 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). -While operation is running its progress can be set as: +While operation is running its progress can be set like this: ```csharp op.SetProgress(progressValue); ``` @@ -334,9 +334,21 @@ DownloadTextAsync("http://www.google.com") 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`. ### Progress reporting -Library operations support progress reporting via exposing `IAsyncOperation.Progress` property: +Library operations support progress reporting via exposing `IAsyncOperation.Progress` property and progress reporting events: ```csharp var progress = op.Progess; // gets an operation progress as a float value in range [0, 1] + +// subscribe to progress changed event +op.ProgressChanged += (sender, args) => +{ + Debug.Log("Progress = " + args.ProgressPercentage); +} + +// add progress changed delegate +op.AddProgressCallback(op => +{ + Debug.Log("Progress = " + op.Progress); +}); ``` There is `AsyncResult.GetProgress()` virtual method that is called when a progress values is requested. Finally there are producer-side methods like `AsyncCompletionSource.TrySetProgress()` that can set the progress value. @@ -355,7 +367,7 @@ Completion callbacks are basicly low-level continuations. Just like continuation ```csharp var op = DownloadTextAsync("http://www.google.com"); op.Completed += o => Debug.Log("1"); -op.AddContinuation(o => Debug.Log("2")); +op.AddCompletionCallback(o => Debug.Log("2")); ``` 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. @@ -369,7 +381,7 @@ class MyContinuation : IAsyncContinuation // ... var op = DownloadTextAsync("http://www.google.com"); -op.AddContinuation(new MyContinuation()); +op.AddCompletionCallback(new MyContinuation()); ``` ### Disposing of operations From bf7d438214286b607d1c67c449b072d6dc8e9498 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 1 Jun 2018 19:04:14 +0300 Subject: [PATCH 29/67] Added progress reporting to internal operation implementations --- .../Implementation/Specialized/RetryResult{T}.cs | 1 + .../Specialized/UpdatableDelayResult.cs | 16 +++++++++++++++- .../Specialized/WhenAllResult{T}.cs | 4 ++++ src/UnityFx.Async/NOTES.txt | 5 ----- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs index 842e049..ba79ceb 100644 --- a/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs @@ -82,6 +82,7 @@ public void Invoke(IAsyncOperation op, bool inline) } else if (_millisecondsRetryDelay > 0) { + ReportProgress(); BeginWait(_millisecondsRetryDelay); } else diff --git a/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs b/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs index a49b2e5..e6c04d7 100644 --- a/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs +++ b/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs @@ -9,8 +9,12 @@ internal sealed class UpdatableDelayResult : AsyncResult, IAsyncUpdatable { #region data + private const float _progressEventTimeout = 0.1f; + private readonly IAsyncUpdateSource _updateService; - private float _timeToWait; + private readonly float _timeToWait; + + private float _progressEventTimer; private float _timer; #endregion @@ -57,6 +61,16 @@ public void Update(float frameTime) { TrySetCompleted(false); } + else + { + _progressEventTimer += frameTime; + + if (_progressEventTimer >= _progressEventTimeout) + { + _progressEventTimer = 0; + ReportProgress(); + } + } } #endregion diff --git a/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs index 953d2fd..c8301b0 100644 --- a/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs @@ -112,6 +112,10 @@ public void Invoke(IAsyncOperation asyncOp, bool inline) TrySetResult(results.ToArray(), false); } } + else + { + ReportProgress(); + } } #endregion diff --git a/src/UnityFx.Async/NOTES.txt b/src/UnityFx.Async/NOTES.txt index 6c4488b..ff959d7 100644 --- a/src/UnityFx.Async/NOTES.txt +++ b/src/UnityFx.Async/NOTES.txt @@ -1,6 +1 @@ TODO: -1) Optimize number of allocations needed for adding continuations: - - most common case is a 1-2 continuations per operation; - - it's a very rare case when more than 4 continuations per operation is needed, so this case is not a subject to any optimizations currently; - - one possible optimization is removing internal List that is allocated for cases when more that 1 continuation is needed, - AsyncContinuation can be modified to store a reference to the next continuation object; From 9e9efe84e8a8e120194ac9c0f7e607150daea2b3 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 1 Jun 2018 19:13:07 +0300 Subject: [PATCH 30/67] A few renames --- src/UnityFx.Async.Tests/Tests/DelayTests.cs | 14 ++++++++++++++ .../{ToObservableTests.cs => ObservableTests.cs} | 4 +--- src/UnityFx.Async.Tests/Tests/RetryTests.cs | 14 ++++++++++++++ 3 files changed, 29 insertions(+), 3 deletions(-) create mode 100644 src/UnityFx.Async.Tests/Tests/DelayTests.cs rename src/UnityFx.Async.Tests/Tests/{ToObservableTests.cs => ObservableTests.cs} (95%) create mode 100644 src/UnityFx.Async.Tests/Tests/RetryTests.cs diff --git a/src/UnityFx.Async.Tests/Tests/DelayTests.cs b/src/UnityFx.Async.Tests/Tests/DelayTests.cs new file mode 100644 index 0000000..90658bf --- /dev/null +++ b/src/UnityFx.Async.Tests/Tests/DelayTests.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 DelayTests + { + } +} diff --git a/src/UnityFx.Async.Tests/Tests/ToObservableTests.cs b/src/UnityFx.Async.Tests/Tests/ObservableTests.cs similarity index 95% rename from src/UnityFx.Async.Tests/Tests/ToObservableTests.cs rename to src/UnityFx.Async.Tests/Tests/ObservableTests.cs index 469a81d..87f3a9f 100644 --- a/src/UnityFx.Async.Tests/Tests/ToObservableTests.cs +++ b/src/UnityFx.Async.Tests/Tests/ObservableTests.cs @@ -2,13 +2,11 @@ // 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 ToObservableTests + public class ObservableTests { [Fact] public void ToObservable_OnNextIsCalled() diff --git a/src/UnityFx.Async.Tests/Tests/RetryTests.cs b/src/UnityFx.Async.Tests/Tests/RetryTests.cs new file mode 100644 index 0000000..8796210 --- /dev/null +++ b/src/UnityFx.Async.Tests/Tests/RetryTests.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 RetryTests + { + } +} From 80c7abbc3cf40efc591894f2180289016e0aed56 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 4 Jun 2018 12:06:55 +0300 Subject: [PATCH 31/67] Changed debugger display for the AsyncResult to show operation progress value --- src/UnityFx.Async/Api/Core/AsyncResult.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 59642f5..e69c91e 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -1010,13 +1010,19 @@ private string DebuggerDisplay get { var result = ToString(); - var state = Status.ToString(); + var status = Status; + var state = status.ToString(); if (IsFaulted && _exception != null) { state += " (" + _exception.GetType().Name + ')'; } + if (status == AsyncOperationStatus.Running) + { + state += ", Progress = " + GetProgress().ToString("N2"); + } + result += ", Status = "; result += state; From 1e3d6be71f6046e89239b9b64545e7856fd44d65 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 4 Jun 2018 12:38:38 +0300 Subject: [PATCH 32/67] Removed 'inline' argument from signature of the IAsyncContinuation.Invoke() --- .../Tests/CompletionCallbackTests.cs | 6 ++---- .../Api/Core/AsyncResult.Events.cs | 10 +++++----- .../Api/Interfaces/IAsyncContinuation.cs | 5 +---- .../Continuations/CallbackCollection.cs | 2 +- .../Continuations/CallbackUtility.cs | 10 +++++----- .../Continuations/ContinueWithResult{T,U}.cs | 10 +++++----- .../Promises/CatchResult{T,TException}.cs | 10 +++++----- .../Continuations/Promises/DoneResult{T}.cs | 2 +- .../Continuations/Promises/FinallyResult{T}.cs | 8 ++++---- .../Promises/RebindResult{T,U}.cs | 12 ++++++------ .../Promises/ThenAllResult{T,U}.cs | 4 ++-- .../Promises/ThenAnyResult{T,U}.cs | 4 ++-- .../Continuations/Promises/ThenResult{T,U}.cs | 18 +++++++++--------- .../Continuations/UnwrapResult{T}.cs | 14 +++++++------- .../AsyncObservableSubscription{T}.cs | 2 +- .../Specialized/RetryResult{T}.cs | 2 +- .../Specialized/WhenAllResult{T}.cs | 2 +- .../Specialized/WhenAnyResult{T}.cs | 4 ++-- 18 files changed, 60 insertions(+), 65 deletions(-) diff --git a/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs b/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs index 151ef10..d610dc5 100644 --- a/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs +++ b/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs @@ -109,8 +109,7 @@ public void TryAddContinuation_ExecutesWhenOperationCompletes() op.SetCompleted(); // Assert - continuation.Received(1).Invoke(op, false); - continuation.Received(0).Invoke(op, true); + continuation.Received(1).Invoke(op); } [Fact] @@ -124,8 +123,7 @@ public void AddContinuation_ExecutesIfOperationIsCompletedSynchronously() op.AddCompletionCallback(continuation); // Assert - continuation.Received(1).Invoke(op, true); - continuation.Received(0).Invoke(op, false); + continuation.Received(1).Invoke(op); } [Fact] diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs index c67af3d..416997d 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs @@ -479,11 +479,11 @@ private void InvokeCallbacks() } else if (invokeAsync) { - CallbackUtility.InvokeCompletionCallbackAsync(this, value, SynchronizationContext.Current, false); + CallbackUtility.InvokeCompletionCallbackAsync(this, value, SynchronizationContext.Current); } else { - CallbackUtility.InvokeCompletionCallback(this, value, false); + CallbackUtility.InvokeCompletionCallback(this, value); } } } @@ -504,15 +504,15 @@ private void InvokeCompletionCallback(object continuation, SynchronizationContex { if ((_flags & _flagRunContinuationsAsynchronously) != 0) { - CallbackUtility.InvokeCompletionCallbackAsync(this, continuation, syncContext, true); + CallbackUtility.InvokeCompletionCallbackAsync(this, continuation, syncContext); } else if (syncContext == null || syncContext == SynchronizationContext.Current) { - CallbackUtility.InvokeCompletionCallback(this, continuation, true); + CallbackUtility.InvokeCompletionCallback(this, continuation); } else { - syncContext.Post(args => CallbackUtility.InvokeCompletionCallback(this, args, true), continuation); + syncContext.Post(args => CallbackUtility.InvokeCompletionCallback(this, args), continuation); } } diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs index 4000a1a..198467f 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.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 { @@ -22,8 +21,6 @@ public interface IAsyncContinuation /// Starts the continuation. /// /// The completed antecedent operation. - /// Inline call flag: means the continuation was called without actually being - /// added to the continuation list (the operation was already completed at the time). - void Invoke(IAsyncOperation op, bool inline); + void Invoke(IAsyncOperation op); } } diff --git a/src/UnityFx.Async/Implementation/Continuations/CallbackCollection.cs b/src/UnityFx.Async/Implementation/Continuations/CallbackCollection.cs index 6a8cb64..a4fbf1d 100644 --- a/src/UnityFx.Async/Implementation/Continuations/CallbackCollection.cs +++ b/src/UnityFx.Async/Implementation/Continuations/CallbackCollection.cs @@ -269,7 +269,7 @@ private void InvokeProgressCallback(CallbackData callbackData) private void InvokeInline(object callback) { Debug.Assert(callback != null); - CallbackUtility.InvokeCompletionCallback(_op, callback, false); + CallbackUtility.InvokeCompletionCallback(_op, callback); } private void InvokeProgressChangedInline(object callback) diff --git a/src/UnityFx.Async/Implementation/Continuations/CallbackUtility.cs b/src/UnityFx.Async/Implementation/Continuations/CallbackUtility.cs index d4a8640..6ba1013 100644 --- a/src/UnityFx.Async/Implementation/Continuations/CallbackUtility.cs +++ b/src/UnityFx.Async/Implementation/Continuations/CallbackUtility.cs @@ -15,12 +15,12 @@ internal static class CallbackUtility #region interface - public static void InvokeCompletionCallback(IAsyncOperation op, object continuation, bool inline) + public static void InvokeCompletionCallback(IAsyncOperation op, object continuation) { switch (continuation) { case IAsyncContinuation c: - c.Invoke(op, inline); + c.Invoke(op); break; case AsyncOperationCallback aoc: @@ -41,15 +41,15 @@ public static void InvokeCompletionCallback(IAsyncOperation op, object continuat } } - public static void InvokeCompletionCallbackAsync(IAsyncOperation op, object continuation, SynchronizationContext syncContext, bool inline) + public static void InvokeCompletionCallbackAsync(IAsyncOperation op, object continuation, SynchronizationContext syncContext) { if (syncContext != null && syncContext.GetType() != typeof(SynchronizationContext)) { - syncContext.Post(args => InvokeCompletionCallback(op, args, inline), continuation); + syncContext.Post(args => InvokeCompletionCallback(op, args), continuation); } else { - ThreadPool.QueueUserWorkItem(args => InvokeCompletionCallback(op, args, inline), continuation); + ThreadPool.QueueUserWorkItem(args => InvokeCompletionCallback(op, args), continuation); } } diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs index 675f3ec..4e5dc3d 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs @@ -56,7 +56,7 @@ protected override void OnCancel() #region IAsyncContinuation - public void Invoke(IAsyncOperation op, bool inline) + public void Invoke(IAsyncOperation op) { if (CanInvoke()) { @@ -99,20 +99,20 @@ public void Invoke(IAsyncOperation op, bool inline) break; default: - TrySetCanceled(inline); + TrySetCanceled(false); return; } - TrySetResult(result, inline); + TrySetResult(result, false); } catch (Exception e) { - TrySetException(e, inline); + TrySetException(e, false); } } else { - TrySetCanceled(inline); + TrySetCanceled(false); } } 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 cfb8fdf..7e40241 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/CatchResult{T,TException}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/CatchResult{T,TException}.cs @@ -43,26 +43,26 @@ protected override void OnCancel() #region IAsyncContinuation - public void Invoke(IAsyncOperation op, bool inline) + public void Invoke(IAsyncOperation op) { if (op.IsCompletedSuccessfully) { - TrySetCompleted(inline); + TrySetCompleted(false); } else if (!(op.Exception is TException)) { - TrySetException(op.Exception, inline); + TrySetException(op.Exception, false); } else { try { _errorCallback.Invoke(op.Exception as TException); - TrySetCompleted(inline); + TrySetCompleted(false); } catch (Exception e) { - TrySetException(e, inline); + TrySetException(e, false); } } } diff --git a/src/UnityFx.Async/Implementation/Continuations/Promises/DoneResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/DoneResult{T}.cs index e612f4a..9814fe8 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/DoneResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/DoneResult{T}.cs @@ -26,7 +26,7 @@ public DoneResult(object successCallback, Action errorCallback) #region IAsyncContinuation - public void Invoke(IAsyncOperation op, bool inline) + public void Invoke(IAsyncOperation op) { if (op.IsCompletedSuccessfully) { diff --git a/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs index 7058eda..358d9f8 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs @@ -43,7 +43,7 @@ protected override void OnCancel() #region IAsyncContinuation - public void Invoke(IAsyncOperation op, bool inline) + public void Invoke(IAsyncOperation op) { try { @@ -51,7 +51,7 @@ public void Invoke(IAsyncOperation op, bool inline) { case Action a: a.Invoke(); - TrySetCompleted(inline); + TrySetCompleted(false); break; case Func> f1: @@ -63,13 +63,13 @@ public void Invoke(IAsyncOperation op, bool inline) break; default: - TrySetCanceled(inline); + TrySetCanceled(false); break; } } catch (Exception e) { - TrySetException(e, inline); + TrySetException(e, false); } } 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 2bdc310..7a33c93 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/RebindResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/RebindResult{T,U}.cs @@ -44,7 +44,7 @@ protected override void OnCancel() #region IAsyncContinuation - public void Invoke(IAsyncOperation op, bool inline) + public void Invoke(IAsyncOperation op) { try { @@ -53,26 +53,26 @@ public void Invoke(IAsyncOperation op, bool inline) switch (_continuation) { case Func f1: - TrySetResult(f1(), inline); + TrySetResult(f1(), false); break; case Func f2: - TrySetResult(f2((op as IAsyncOperation).Result), inline); + TrySetResult(f2((op as IAsyncOperation).Result), false); break; default: - TrySetCanceled(inline); + TrySetCanceled(false); break; } } else { - TrySetException(op.Exception, inline); + TrySetException(op.Exception, false); } } catch (Exception e) { - TrySetException(e, inline); + TrySetException(e, false); } } 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 eb37932..9b9dae5 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/ThenAllResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/ThenAllResult{T,U}.cs @@ -23,7 +23,7 @@ public ThenAllResult(IAsyncOperation op, object successCallback, Action op.AddCompletionCallback(this); } - protected virtual IAsyncOperation InvokeSuccessCallback(IAsyncOperation op, bool completedSynchronously, object continuation) + protected virtual IAsyncOperation InvokeSuccessCallback(IAsyncOperation op, object continuation) { IAsyncOperation result = null; @@ -37,12 +37,12 @@ protected virtual IAsyncOperation InvokeSuccessCallback(IAsyncOperation op, bool { case Action a: a.Invoke(); - TrySetCompleted(completedSynchronously); + TrySetCompleted(false); break; case Action a1: a1.Invoke((op as IAsyncOperation).Result); - TrySetCompleted(completedSynchronously); + TrySetCompleted(false); break; case Func> f3: @@ -66,7 +66,7 @@ protected virtual IAsyncOperation InvokeSuccessCallback(IAsyncOperation op, bool break; default: - TrySetCanceled(completedSynchronously); + TrySetCanceled(false); break; } @@ -87,7 +87,7 @@ protected override void OnCancel() #region IAsyncContinuation - public void Invoke(IAsyncOperation op, bool inline) + public void Invoke(IAsyncOperation op) { try { @@ -95,22 +95,22 @@ public void Invoke(IAsyncOperation op, bool inline) { if (IsCancellationRequested) { - TrySetCanceled(inline); + TrySetCanceled(false); } else { - _continuation = InvokeSuccessCallback(op, inline, _successCallback); + _continuation = InvokeSuccessCallback(op, _successCallback); } } else { _errorCallback?.Invoke(op.Exception); - TrySetException(op.Exception, inline); + TrySetException(op.Exception, false); } } catch (Exception e) { - TrySetException(e, inline); + TrySetException(e, false); } } diff --git a/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs index 082a2fc..0c40530 100644 --- a/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs @@ -53,7 +53,7 @@ protected override float GetProgress() #region IAsyncContinuation - public void Invoke(IAsyncOperation op, bool inline) + public void Invoke(IAsyncOperation op) { if (_state == State.WaitingForOuterOperation) { @@ -64,21 +64,21 @@ public void Invoke(IAsyncOperation op, bool inline) switch (op) { case IAsyncOperation> innerOp1: - ProcessInnerOperation(innerOp1.Result, inline); + ProcessInnerOperation(innerOp1.Result); break; case IAsyncOperation innerOp2: - ProcessInnerOperation(innerOp2.Result, inline); + ProcessInnerOperation(innerOp2.Result); break; default: - ProcessInnerOperation(null, inline); + ProcessInnerOperation(null); break; } } else { - TrySetException(op.Exception, inline); + TrySetException(op.Exception, false); } } else if (_state == State.WaitingForInnerOperation) @@ -111,11 +111,11 @@ public void Invoke(IAsyncOperation op, bool inline) #region implementation - private void ProcessInnerOperation(IAsyncOperation innerOp, bool completedSynchronously) + private void ProcessInnerOperation(IAsyncOperation innerOp) { if (innerOp == null) { - TrySetCanceled(completedSynchronously); + TrySetCanceled(false); } else { diff --git a/src/UnityFx.Async/Implementation/Observable/AsyncObservableSubscription{T}.cs b/src/UnityFx.Async/Implementation/Observable/AsyncObservableSubscription{T}.cs index 8c4822b..1e5c171 100644 --- a/src/UnityFx.Async/Implementation/Observable/AsyncObservableSubscription{T}.cs +++ b/src/UnityFx.Async/Implementation/Observable/AsyncObservableSubscription{T}.cs @@ -28,7 +28,7 @@ public AsyncObservableSubscription(IAsyncOperation op, IObserver observer) #region IAsyncContinuation - public void Invoke(IAsyncOperation op, bool inline) + public void Invoke(IAsyncOperation op) { if (_op.IsCompletedSuccessfully) { diff --git a/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs index ba79ceb..1a53ddc 100644 --- a/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs @@ -65,7 +65,7 @@ protected override void OnCancel() #region IAsyncContinuation - public void Invoke(IAsyncOperation op, bool inline) + public void Invoke(IAsyncOperation op) { 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 c8301b0..c7d9f13 100644 --- a/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs @@ -58,7 +58,7 @@ protected override void OnCancel() #region IAsyncContinuation - public void Invoke(IAsyncOperation asyncOp, bool inline) + 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 340c3c5..b8643cf 100644 --- a/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs @@ -42,9 +42,9 @@ protected override void OnCancel() #region IAsyncContinuation - public void Invoke(IAsyncOperation op, bool inline) + public void Invoke(IAsyncOperation op) { - TrySetResult((T)op, inline); + TrySetResult((T)op, false); } #endregion From 72afd870f910a114184127b3741ec22244aba341 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 4 Jun 2018 12:39:42 +0300 Subject: [PATCH 33/67] CHANGELOG update --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad45882..c3db5c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/); this proj ### Changed - Significantly reduced number of memory allocations when adding continuations (implemention optimizations). +- Changed signature of the `IAsyncContinuation.Invoke` method. ### Fixed - Fixed exception when removing listeners while in `AsyncUpdateSource.OnError` / `AsyncUpdateSource.OnCompleted` / `AsyncUpdateSource.Dispose`. From 3055c1f5035e425b8d3a33a043ec04f5fae6329e Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 4 Jun 2018 12:52:59 +0300 Subject: [PATCH 34/67] Added new convenience stuff to AsyncResult --- .../Helpers/AsyncResultOverrides.cs | 8 +-- .../Api/Core/AsyncCompletionSource.cs | 8 +-- .../Core/AsyncCompletionSource{TResult}.cs | 8 +-- src/UnityFx.Async/Api/Core/AsyncResult.cs | 54 +++++++++++++++++++ .../Api/Core/AsyncResult{TResult}.cs | 13 +++++ .../Implementation/Common/Constants.cs | 1 + .../Continuations/ContinueWithResult{T,U}.cs | 8 +-- .../Promises/CatchResult{T,TException}.cs | 8 +-- .../Promises/FinallyResult{T}.cs | 6 +-- .../Promises/RebindResult{T,U}.cs | 11 ++-- .../Promises/ThenAllResult{T,U}.cs | 2 +- .../Promises/ThenAnyResult{T,U}.cs | 10 ++-- .../Continuations/Promises/ThenResult{T,U}.cs | 12 ++--- .../Continuations/UnwrapResult{T}.cs | 10 ++-- .../Specialized/RetryResult{T}.cs | 16 +++--- .../Specialized/TimerDelayResult.cs | 4 +- .../Specialized/UpdatableDelayResult.cs | 4 +- .../Specialized/WhenAllResult{T}.cs | 8 +-- .../Specialized/WhenAnyResult{T}.cs | 2 +- 19 files changed, 130 insertions(+), 63 deletions(-) diff --git a/src/UnityFx.Async.Tests/Helpers/AsyncResultOverrides.cs b/src/UnityFx.Async.Tests/Helpers/AsyncResultOverrides.cs index f1e35d7..0eca5fe 100644 --- a/src/UnityFx.Async.Tests/Helpers/AsyncResultOverrides.cs +++ b/src/UnityFx.Async.Tests/Helpers/AsyncResultOverrides.cs @@ -12,10 +12,10 @@ public class AsyncResultOverrides : AsyncResult public bool OnCompletedCalled { get; private set; } public bool DisposeCalled { get; private set; } - public bool TrySetCanceled() => TrySetCanceled(false); - public bool TrySetException(Exception e) => TrySetException(e, false); - public bool TrySetCompleted() => TrySetCompleted(false); - public bool TrySetResult(int result) => TrySetResult(result, false); + public new bool TrySetCanceled() => TrySetCanceled(false); + public new bool TrySetException(Exception e) => TrySetException(e, false); + public new bool TrySetCompleted() => TrySetCompleted(false); + public new bool TrySetResult(int result) => TrySetResult(result, false); protected override void OnStatusChanged(AsyncOperationStatus status) { diff --git a/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs b/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs index c880a80..4fbc80e 100644 --- a/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs +++ b/src/UnityFx.Async/Api/Core/AsyncCompletionSource.cs @@ -231,16 +231,16 @@ public bool TrySetProgress(float progress) } /// - public bool TrySetCanceled() => TrySetCanceled(false); + public new bool TrySetCanceled() => TrySetCanceled(false); /// - public bool TrySetCompleted() => TrySetCompleted(false); + public new bool TrySetCompleted() => TrySetCompleted(false); /// - public bool TrySetException(Exception exception) => TrySetException(exception, false); + public new bool TrySetException(Exception exception) => TrySetException(exception, false); /// - public bool TrySetExceptions(IEnumerable exceptions) => TrySetExceptions(exceptions, false); + public new 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 5308549..3fa7b30 100644 --- a/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncCompletionSource{TResult}.cs @@ -232,16 +232,16 @@ public bool TrySetProgress(float progress) } /// - public bool TrySetCanceled() => TrySetCanceled(false); + public new bool TrySetCanceled() => TrySetCanceled(false); /// - public bool TrySetException(Exception exception) => TrySetException(exception, false); + public new bool TrySetException(Exception exception) => TrySetException(exception, false); /// - public bool TrySetExceptions(IEnumerable exceptions) => TrySetExceptions(exceptions, false); + public new bool TrySetExceptions(IEnumerable exceptions) => TrySetExceptions(exceptions, false); /// - public bool TrySetResult(TResult result) => TrySetResult(result, false); + public new bool TrySetResult(TResult result) => TrySetResult(result, false); #endregion } diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index e69c91e..d5b6e24 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -313,12 +313,24 @@ protected internal bool TrySetRunning() return false; } + /// + /// Attempts to transition the operation into the state. + /// + /// Thrown is the operation is disposed. + /// Returns if the attemp was successfull; otherwise. + /// + protected internal bool TrySetCanceled() + { + return TrySetCanceled(false); + } + /// /// 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. + /// protected internal bool TrySetCanceled(bool completedSynchronously) { ThrowIfDisposed(); @@ -337,6 +349,20 @@ protected internal bool TrySetCanceled(bool completedSynchronously) return false; } + /// + /// Attempts to transition the operation into the (or + /// if the exception is ) state. + /// + /// An exception that caused the operation to end prematurely. + /// Thrown if is . + /// Thrown is the operation is disposed. + /// Returns if the attemp was successfull; otherwise. + /// + protected internal bool TrySetException(Exception exception) + { + return TrySetException(exception, false); + } + /// /// Attempts to transition the operation into the (or /// if the exception is ) state. @@ -346,6 +372,7 @@ protected internal bool TrySetCanceled(bool completedSynchronously) /// Thrown if is . /// Thrown is the operation is disposed. /// Returns if the attemp was successfull; otherwise. + /// protected internal bool TrySetException(Exception exception, bool completedSynchronously) { ThrowIfDisposed(); @@ -386,6 +413,20 @@ protected internal bool TrySetException(Exception exception, bool completedSynch return false; } + /// + /// Attempts to transition the operation into the state. + /// + /// Exceptions that caused the operation to end prematurely. + /// Thrown if is . + /// Thrown if is empty. + /// Thrown is the operation is disposed. + /// Returns if the attemp was successfull; otherwise. + /// + protected internal bool TrySetExceptions(IEnumerable exceptions) + { + return TrySetExceptions(exceptions, false); + } + /// /// Attempts to transition the operation into the state. /// @@ -395,6 +436,7 @@ protected internal bool TrySetException(Exception exception, bool completedSynch /// Thrown if is empty. /// Thrown is the operation is disposed. /// Returns if the attemp was successfull; otherwise. + /// protected internal bool TrySetExceptions(IEnumerable exceptions, bool completedSynchronously) { ThrowIfDisposed(); @@ -444,12 +486,24 @@ protected internal bool TrySetExceptions(IEnumerable exceptions, bool return false; } + /// + /// Attempts to transition the operation into the state. + /// + /// Thrown is the operation is disposed. + /// Returns if the attemp was successfull; otherwise. + /// + protected internal bool TrySetCompleted() + { + return TrySetCompleted(false); + } + /// /// 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. + /// protected internal bool TrySetCompleted(bool completedSynchronously) { ThrowIfDisposed(); diff --git a/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs b/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs index 6718bbc..1530746 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs @@ -159,6 +159,18 @@ internal AsyncResult(TResult result, object asyncState) _result = result; } + /// + /// Attempts to transition the operation into the state. + /// + /// The operation result. + /// Thrown is the operation is disposed. + /// Returns if the attemp was successfull; otherwise. + /// + protected internal bool TrySetResult(TResult result) + { + return TrySetResult(result, false); + } + /// /// Attempts to transition the operation into the state. /// @@ -166,6 +178,7 @@ internal AsyncResult(TResult result, object asyncState) /// Value of the property. /// Thrown is the operation is disposed. /// Returns if the attemp was successfull; otherwise. + /// protected internal bool TrySetResult(TResult result, bool completedSynchronously) { ThrowIfDisposed(); diff --git a/src/UnityFx.Async/Implementation/Common/Constants.cs b/src/UnityFx.Async/Implementation/Common/Constants.cs index c937911..4cd0f5f 100644 --- a/src/UnityFx.Async/Implementation/Common/Constants.cs +++ b/src/UnityFx.Async/Implementation/Common/Constants.cs @@ -12,5 +12,6 @@ internal static class Constants public const string ErrorResultNotAvailable = "The operation result is not available."; public const string ErrorInvalidTimeout = "The timeout value specified is not valid."; public const string ErrorInvalidProgress = "The progress value should be in range [0, 1]."; + public const string ErrorMaxNumberOrRetries = "Maximum number of retries exceeded."; } } diff --git a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs index 4e5dc3d..5aa98d8 100644 --- a/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/ContinueWithResult{T,U}.cs @@ -99,20 +99,20 @@ public void Invoke(IAsyncOperation op) break; default: - TrySetCanceled(false); + TrySetCanceled(); return; } - TrySetResult(result, false); + TrySetResult(result); } catch (Exception e) { - TrySetException(e, false); + TrySetException(e); } } else { - TrySetCanceled(false); + TrySetCanceled(); } } 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 7e40241..43a4075 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/CatchResult{T,TException}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/CatchResult{T,TException}.cs @@ -47,22 +47,22 @@ public void Invoke(IAsyncOperation op) { if (op.IsCompletedSuccessfully) { - TrySetCompleted(false); + TrySetCompleted(); } else if (!(op.Exception is TException)) { - TrySetException(op.Exception, false); + TrySetException(op.Exception); } else { try { _errorCallback.Invoke(op.Exception as TException); - TrySetCompleted(false); + TrySetCompleted(); } catch (Exception e) { - TrySetException(e, false); + TrySetException(e); } } } diff --git a/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs index 358d9f8..4d8123f 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/FinallyResult{T}.cs @@ -51,7 +51,7 @@ public void Invoke(IAsyncOperation op) { case Action a: a.Invoke(); - TrySetCompleted(false); + TrySetCompleted(); break; case Func> f1: @@ -63,13 +63,13 @@ public void Invoke(IAsyncOperation op) break; default: - TrySetCanceled(false); + TrySetCanceled(); break; } } catch (Exception e) { - TrySetException(e, false); + TrySetException(e); } } 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 7a33c93..4682f9a 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/RebindResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/RebindResult{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.Promises { @@ -53,26 +52,26 @@ public void Invoke(IAsyncOperation op) switch (_continuation) { case Func f1: - TrySetResult(f1(), false); + TrySetResult(f1()); break; case Func f2: - TrySetResult(f2((op as IAsyncOperation).Result), false); + TrySetResult(f2((op as IAsyncOperation).Result)); break; default: - TrySetCanceled(false); + TrySetCanceled(); break; } } else { - TrySetException(op.Exception, false); + TrySetException(op.Exception); } } catch (Exception e) { - TrySetException(e, false); + TrySetException(e); } } 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 9b9dae5..e904283 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/ThenAllResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/ThenAllResult{T,U}.cs @@ -52,7 +52,7 @@ protected override IAsyncOperation InvokeSuccessCallback(IAsyncOperation op, obj } else { - TrySetCanceled(false); + TrySetCanceled(); } return result; diff --git a/src/UnityFx.Async/Implementation/Continuations/Promises/ThenAnyResult{T,U}.cs b/src/UnityFx.Async/Implementation/Continuations/Promises/ThenAnyResult{T,U}.cs index 971e340..dc6e98c 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/ThenAnyResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/ThenAnyResult{T,U}.cs @@ -53,7 +53,7 @@ protected override IAsyncOperation InvokeSuccessCallback(IAsyncOperation op, obj { if (IsCancellationRequested) { - TrySetCanceled(false); + TrySetCanceled(); } else if (op2.IsCompletedSuccessfully) { @@ -61,23 +61,23 @@ protected override IAsyncOperation InvokeSuccessCallback(IAsyncOperation op, obj if (op3 is IAsyncOperation op4) { - TrySetResult(op4.Result, false); + TrySetResult(op4.Result); } else { - TrySetCompleted(false); + TrySetCompleted(); } } else { - TrySetException(op2.Exception, false); + TrySetException(op2.Exception); } }, null); } else { - TrySetCanceled(false); + TrySetCanceled(); } return result; 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 6cc951b..0530576 100644 --- a/src/UnityFx.Async/Implementation/Continuations/Promises/ThenResult{T,U}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/Promises/ThenResult{T,U}.cs @@ -37,12 +37,12 @@ protected virtual IAsyncOperation InvokeSuccessCallback(IAsyncOperation op, obje { case Action a: a.Invoke(); - TrySetCompleted(false); + TrySetCompleted(); break; case Action a1: a1.Invoke((op as IAsyncOperation).Result); - TrySetCompleted(false); + TrySetCompleted(); break; case Func> f3: @@ -66,7 +66,7 @@ protected virtual IAsyncOperation InvokeSuccessCallback(IAsyncOperation op, obje break; default: - TrySetCanceled(false); + TrySetCanceled(); break; } @@ -95,7 +95,7 @@ public void Invoke(IAsyncOperation op) { if (IsCancellationRequested) { - TrySetCanceled(false); + TrySetCanceled(); } else { @@ -105,12 +105,12 @@ public void Invoke(IAsyncOperation op) else { _errorCallback?.Invoke(op.Exception); - TrySetException(op.Exception, false); + TrySetException(op.Exception); } } catch (Exception e) { - TrySetException(e, false); + TrySetException(e); } } diff --git a/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs b/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs index 0c40530..de170c9 100644 --- a/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Continuations/UnwrapResult{T}.cs @@ -78,7 +78,7 @@ public void Invoke(IAsyncOperation op) } else { - TrySetException(op.Exception, false); + TrySetException(op.Exception); } } else if (_state == State.WaitingForInnerOperation) @@ -87,16 +87,16 @@ public void Invoke(IAsyncOperation op) { if (op is IAsyncOperation innerOp) { - TrySetResult(innerOp.Result, false); + TrySetResult(innerOp.Result); } else { - TrySetCompleted(false); + TrySetCompleted(); } } else { - TrySetException(op.Exception, false); + TrySetException(op.Exception); } _state = State.Done; @@ -115,7 +115,7 @@ private void ProcessInnerOperation(IAsyncOperation innerOp) { if (innerOp == null) { - TrySetCanceled(false); + TrySetCanceled(); } else { diff --git a/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs index 1a53ddc..7f71acf 100644 --- a/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/RetryResult{T}.cs @@ -36,7 +36,7 @@ protected void EndWait() { if (IsCancellationRequested) { - TrySetCanceled(false); + TrySetCanceled(); } else { @@ -78,7 +78,7 @@ public void Invoke(IAsyncOperation op) } else if (IsCancellationRequested) { - TrySetCanceled(false); + TrySetCanceled(); } else if (_millisecondsRetryDelay > 0) { @@ -127,7 +127,7 @@ private void StartOperation() } catch (Exception e) { - TrySetException(e, false); + TrySetException(e); } } @@ -142,16 +142,16 @@ private void Retry() } else if (_op.IsFaulted) { - TrySetException(_op.Exception, false); + TrySetException(_op.Exception); } else if (_op.IsCanceled) { - TrySetCanceled(false); + TrySetCanceled(); } else { // NOTE: should not get here. - TrySetException(new Exception("Maximum number of retries exceeded."), false); + TrySetException(new Exception(Constants.ErrorMaxNumberOrRetries)); } } @@ -162,11 +162,11 @@ private void SetResult() if (_op is IAsyncOperation rop) { - TrySetResult(rop.Result, false); + TrySetResult(rop.Result); } else { - TrySetCompleted(false); + TrySetCompleted(); } } diff --git a/src/UnityFx.Async/Implementation/Specialized/TimerDelayResult.cs b/src/UnityFx.Async/Implementation/Specialized/TimerDelayResult.cs index 314423c..13396d0 100644 --- a/src/UnityFx.Async/Implementation/Specialized/TimerDelayResult.cs +++ b/src/UnityFx.Async/Implementation/Specialized/TimerDelayResult.cs @@ -19,7 +19,7 @@ internal sealed class TimerDelayResult : AsyncResult public TimerDelayResult(int millisecondsDelay) { _timer = new Timer( - state => (state as AsyncResult).TrySetCompleted(false), + state => (state as AsyncResult).TrySetCompleted(), this, millisecondsDelay, Timeout.Infinite); @@ -31,7 +31,7 @@ public TimerDelayResult(int millisecondsDelay) protected override void OnCancel() { - TrySetCanceled(false); + TrySetCanceled(); } protected override void OnCompleted() diff --git a/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs b/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs index e6c04d7..c909702 100644 --- a/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs +++ b/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs @@ -40,7 +40,7 @@ protected override float GetProgress() protected override void OnCancel() { - TrySetCanceled(false); + TrySetCanceled(); } protected override void OnCompleted() @@ -59,7 +59,7 @@ public void Update(float frameTime) if (_timer <= 0) { - TrySetCompleted(false); + TrySetCompleted(); } else { diff --git a/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs index c7d9f13..cac2d54 100644 --- a/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/WhenAllResult{T}.cs @@ -87,15 +87,15 @@ public void Invoke(IAsyncOperation asyncOp) if (exceptions != null) { - TrySetExceptions(exceptions, false); + TrySetExceptions(exceptions); } else if (canceledOp != null) { - TrySetCanceled(false); + TrySetCanceled(); } else if (typeof(T) == typeof(VoidResult)) { - TrySetCompleted(false); + TrySetCompleted(); } else { @@ -109,7 +109,7 @@ public void Invoke(IAsyncOperation asyncOp) } } - TrySetResult(results.ToArray(), false); + TrySetResult(results.ToArray()); } } else diff --git a/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs b/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs index b8643cf..b152c3a 100644 --- a/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs +++ b/src/UnityFx.Async/Implementation/Specialized/WhenAnyResult{T}.cs @@ -44,7 +44,7 @@ protected override void OnCancel() public void Invoke(IAsyncOperation op) { - TrySetResult((T)op, false); + TrySetResult((T)op); } #endregion From 2f88fd50a771e0f711fdeef33e22ad3fac4bdbca Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 4 Jun 2018 19:17:51 +0300 Subject: [PATCH 35/67] Added new AsyncResult.Delay() overloads --- .../UnityFx.Async/Scripts/AsyncUtility.cs | 11 +++ .../Api/Core/AsyncResult.Helpers.cs | 79 +++++++++++++++++-- .../Specialized/UpdatableDelayResult.cs | 4 +- 3 files changed, 86 insertions(+), 8 deletions(-) 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 96a00bb..c4a7e28 100644 --- a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncUtility.cs +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncUtility.cs @@ -93,6 +93,17 @@ public static IAsyncOperation Delay(int millisecondsDelay) return AsyncResult.Delay(millisecondsDelay, GetCoroutineRunner().UpdateSource); } + /// + /// Creates an operation that completes after a time delay. + /// + /// The number of seconds 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 IAsyncOperation Delay(float secondsDelay) + { + return AsyncResult.Delay(secondsDelay, GetCoroutineRunner().UpdateSource); + } + /// /// Creates an operation that completes after a time delay. /// diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs index 587b292..009b2da 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs @@ -456,6 +456,7 @@ public static AsyncResult FromObservable(IObservable observable) /// Thrown if the is less than -1. /// An operation that represents the time delay. /// + /// /// public static AsyncResult Delay(int millisecondsDelay) { @@ -482,7 +483,7 @@ public static AsyncResult Delay(int millisecondsDelay) /// /// Creates an operation that completes after a time delay. This method creates a more effecient operation - /// than but requires a specialized updates source. + /// than but requires a specialized update source. /// /// The number of milliseconds to wait before completing the returned operation, or (-1) to wait indefinitely. /// Update notifications provider. @@ -490,6 +491,8 @@ public static AsyncResult Delay(int millisecondsDelay) /// 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) @@ -513,7 +516,68 @@ public static AsyncResult Delay(int millisecondsDelay, IAsyncUpdateSource update return new AsyncCompletionSource(AsyncOperationStatus.Running); } - var result = new UpdatableDelayResult(millisecondsDelay, updateSource); + var result = new UpdatableDelayResult(millisecondsDelay / 1000f, updateSource); + result.Start(); + return result; + } + + /// + /// Creates an operation that completes after a specified time interval. + /// + /// The number of seconds to wait before completing the returned operation, or (-1) to wait indefinitely. + /// Thrown if the represents a negative time interval. + /// An operation that represents the time delay. + /// + /// + /// + public static AsyncResult Delay(float secondsDelay) + { + var millisecondsDelay = (long)((double)secondsDelay * 1000); + + if (millisecondsDelay > int.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(secondsDelay)); + } + + 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 update source. + /// + /// The number of seconds to wait before completing the returned operation, or (-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(float secondsDelay, IAsyncUpdateSource updateSource) + { + if (updateSource == null) + { + throw new ArgumentNullException(nameof(updateSource)); + } + + if (secondsDelay < Timeout.Infinite) + { + throw new ArgumentOutOfRangeException(nameof(secondsDelay), secondsDelay, Constants.ErrorValueIsLessThanZero); + } + + if (secondsDelay == 0) + { + return CompletedOperation; + } + + if (secondsDelay == Timeout.Infinite) + { + // NOTE: Cannot return AsyncResult instance because its Cancel implementation throws NotSupportedException. + return new AsyncCompletionSource(AsyncOperationStatus.Running); + } + + var result = new UpdatableDelayResult(secondsDelay, updateSource); result.Start(); return result; } @@ -526,6 +590,7 @@ public static AsyncResult Delay(int millisecondsDelay, IAsyncUpdateSource update /// An operation that represents the time delay. /// /// + /// public static AsyncResult Delay(TimeSpan delay) { var millisecondsDelay = (long)delay.TotalMilliseconds; @@ -540,7 +605,7 @@ public static AsyncResult Delay(TimeSpan delay) /// /// Creates an operation that completes after a specified time interval. This method creates a more effecient operation - /// than but requires a specialized updates source. + /// than but requires a specialized update source. /// /// The time span to wait before completing the returned operation, or TimeSpan.FromMilliseconds(-1) to wait indefinitely. /// Update notifications provider. @@ -548,16 +613,18 @@ public static AsyncResult Delay(TimeSpan delay) /// 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; + var secondsDelay = delay.TotalSeconds; - if (millisecondsDelay > int.MaxValue) + if (secondsDelay > float.MaxValue) { throw new ArgumentOutOfRangeException(nameof(delay)); } - return Delay((int)millisecondsDelay, updateSource); + return Delay((float)secondsDelay, updateSource); } #endregion diff --git a/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs b/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs index c909702..84e8d34 100644 --- a/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs +++ b/src/UnityFx.Async/Implementation/Specialized/UpdatableDelayResult.cs @@ -21,9 +21,9 @@ internal sealed class UpdatableDelayResult : AsyncResult, IAsyncUpdatable #region interface - public UpdatableDelayResult(int millisecondsDelay, IAsyncUpdateSource updateSource) + public UpdatableDelayResult(float secondsDelay, IAsyncUpdateSource updateSource) { - _timeToWait = millisecondsDelay / 1000f; + _timeToWait = secondsDelay; _timer = _timeToWait; _updateService = updateSource; _updateService.AddListener(this); From 8932f28a5b74cbd3864ece0167f9d5939277c8c8 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 4 Jun 2018 19:18:03 +0300 Subject: [PATCH 36/67] Comment fixes --- src/UnityFx.Async/Api/Core/AsyncResult.cs | 2 +- src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index d5b6e24..7c6d0f3 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -859,7 +859,7 @@ internal bool TryCopyCompletionState(IAsyncOperation patternOp, bool completedSy } /// - /// Unconditionally reports the operatino progress. + /// Unconditionally reports the operation progress. /// internal void ReportProgress() { diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs index 07a004d..3e55961 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs @@ -8,7 +8,7 @@ namespace UnityFx.Async { /// - /// References a method to be called when a corresponding operation completes. + /// References a method to be called when a corresponding operation state changes. /// /// The asynchronous operation. /// @@ -23,7 +23,7 @@ namespace UnityFx.Async public interface IAsyncOperationEvents { /// - /// Raised when the operation progress has changed. + /// Raised when the operation progress is changed. /// /// /// The event handler is invoked on a thread that registered it (if it has a attached). @@ -41,7 +41,7 @@ public interface IAsyncOperationEvents event ProgressChangedEventHandler ProgressChanged; /// - /// Raised when the operation has completed. + /// Raised when the operation is completed. /// /// /// The event handler is invoked on a thread that registered it (if it has a attached). From 79a118ea8d7257b03572839d2f4dc96cce4c2a72 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Mon, 4 Jun 2018 19:22:05 +0300 Subject: [PATCH 37/67] CHANGELOG update --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3db5c2..ddd8412 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/); this proj ### Added - Added push-based progress reporting support. +- Added `AsyncResult.Delay(float)` overloads. - Added update sources for `LateUpdate`, `FixedUpdate` and end-of-frame updates. ### Changed From ed2d898b1afe51db301970fd195502fe63db4cc9 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Tue, 5 Jun 2018 17:36:38 +0300 Subject: [PATCH 38/67] Changed progress callback signature --- .../Tests/ProgressCallbackTests.cs | 10 ++-- .../Api/Core/AsyncResult.Events.cs | 10 ++-- .../Api/Interfaces/IAsyncOperationEvents.cs | 60 +++++++++---------- .../Continuations/CallbackUtility.cs | 4 ++ 4 files changed, 44 insertions(+), 40 deletions(-) diff --git a/src/UnityFx.Async.Tests/Tests/ProgressCallbackTests.cs b/src/UnityFx.Async.Tests/Tests/ProgressCallbackTests.cs index fed71cc..ddac9c4 100644 --- a/src/UnityFx.Async.Tests/Tests/ProgressCallbackTests.cs +++ b/src/UnityFx.Async.Tests/Tests/ProgressCallbackTests.cs @@ -108,7 +108,7 @@ public void SetCompleted_RaisesProgressChanged() var progress = 0; var progress2 = 0f; var op = new AsyncCompletionSource(); - var p = Substitute.For>(); + var pc = Substitute.For>(); op.ProgressChanged += (sender, args) => { @@ -117,14 +117,14 @@ public void SetCompleted_RaisesProgressChanged() }; op.AddProgressCallback( - asyncOp => + p => { asyncCallbackCalled2 = true; - progress2 = asyncOp.Progress; + progress2 = p; }, null); - op.AddProgressCallback(p, null); + op.AddProgressCallback(pc, null); // Act op.SetCompleted(); @@ -134,7 +134,7 @@ public void SetCompleted_RaisesProgressChanged() Assert.True(asyncCallbackCalled2); Assert.Equal(100, progress); Assert.Equal(1, progress2); - p.Received(1).Report(1); + pc.Received(1).Report(1); } #endregion diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs index 416997d..3ea5379 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs @@ -206,7 +206,7 @@ public bool RemoveCompletionCallback(IAsyncContinuation continuation) } /// - public void AddProgressCallback(AsyncOperationCallback action) + public void AddProgressCallback(Action action) { if (!TryAddProgressCallback(action)) { @@ -215,7 +215,7 @@ public void AddProgressCallback(AsyncOperationCallback action) } /// - public bool TryAddProgressCallback(AsyncOperationCallback action) + public bool TryAddProgressCallback(Action action) { ThrowIfDisposed(); @@ -228,7 +228,7 @@ public bool TryAddProgressCallback(AsyncOperationCallback action) } /// - public void AddProgressCallback(AsyncOperationCallback action, SynchronizationContext syncContext) + public void AddProgressCallback(Action action, SynchronizationContext syncContext) { if (!TryAddProgressCallback(action, syncContext)) { @@ -237,7 +237,7 @@ public void AddProgressCallback(AsyncOperationCallback action, SynchronizationCo } /// - public bool TryAddProgressCallback(AsyncOperationCallback action, SynchronizationContext syncContext) + public bool TryAddProgressCallback(Action action, SynchronizationContext syncContext) { ThrowIfDisposed(); @@ -250,7 +250,7 @@ public bool TryAddProgressCallback(AsyncOperationCallback action, Synchronizatio } /// - public bool RemoveProgressCallback(AsyncOperationCallback action) + public bool RemoveProgressCallback(Action action) { if (action != null) { diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs index 3e55961..59ef738 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs @@ -33,11 +33,11 @@ public interface IAsyncOperationEvents /// Thrown if the delegate being registered is . /// Thrown is the operation has been disposed. /// - /// - /// - /// - /// - /// + /// + /// + /// + /// + /// event ProgressChangedEventHandler ProgressChanged; /// @@ -236,11 +236,11 @@ public interface IAsyncOperationEvents /// The callback to be executed when the operation progress has changed. /// Thrown if is . /// Thrown is the operation has been disposed. - /// - /// - /// - /// - void AddProgressCallback(AsyncOperationCallback action); + /// + /// + /// + /// + void AddProgressCallback(Action action); /// /// Attempts to add a callback to be executed when the operation progress has changed. If the operation is already completed @@ -254,11 +254,11 @@ public interface IAsyncOperationEvents /// Returns if the callback was added; otherwise (the operation is completed). /// Thrown if is . /// Thrown is the operation has been disposed. - /// - /// - /// - /// - bool TryAddProgressCallback(AsyncOperationCallback action); + /// + /// + /// + /// + bool TryAddProgressCallback(Action action); /// /// Adds a callback to be executed when the operation progress has changed. If the operation is completed is invoked @@ -273,11 +273,11 @@ public interface IAsyncOperationEvents /// /// Thrown if is . /// Thrown is the operation has been disposed. - /// - /// - /// - /// - void AddProgressCallback(AsyncOperationCallback action, SynchronizationContext syncContext); + /// + /// + /// + /// + void AddProgressCallback(Action action, SynchronizationContext syncContext); /// /// Attempts to add a callback to be executed when the operation progress has changed. If the operation is already completed @@ -293,22 +293,22 @@ public interface IAsyncOperationEvents /// Returns if the callback was added; otherwise (the operation is completed). /// Thrown if is . /// Thrown is the operation has been disposed. - /// - /// - /// - /// - bool TryAddProgressCallback(AsyncOperationCallback action, SynchronizationContext syncContext); + /// + /// + /// + /// + bool TryAddProgressCallback(Action action, SynchronizationContext syncContext); /// /// Removes an existing progress callback. /// /// The callback to remove. Can be . /// Returns if was removed; otherwise. - /// - /// - /// - /// - bool RemoveProgressCallback(AsyncOperationCallback action); + /// + /// + /// + /// + bool RemoveProgressCallback(Action action); #if !NET35 diff --git a/src/UnityFx.Async/Implementation/Continuations/CallbackUtility.cs b/src/UnityFx.Async/Implementation/Continuations/CallbackUtility.cs index 6ba1013..efc1117 100644 --- a/src/UnityFx.Async/Implementation/Continuations/CallbackUtility.cs +++ b/src/UnityFx.Async/Implementation/Continuations/CallbackUtility.cs @@ -63,6 +63,10 @@ public static void InvokeProgressCallback(IAsyncOperation op, object callback) break; #endif + case Action af: + af.Invoke(op.Progress); + break; + case AsyncOperationCallback ac: ac.Invoke(op); break; From e2c7df66051525195df95f1103506ce36e6e3277 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Tue, 5 Jun 2018 17:47:45 +0300 Subject: [PATCH 39/67] Removed AsyncOperationCallback delegate (Action is used instead) --- .../Tests/CompletionCallbackTests.cs | 2 +- .../Api/Core/AsyncResult.Events.cs | 10 +-- .../Api/Core/AsyncResultQueue{T}.cs | 2 +- .../Api/Interfaces/IAsyncContinuation.cs | 1 - .../Api/Interfaces/IAsyncOperationEvents.cs | 69 ++++++++----------- .../Continuations/CallbackUtility.cs | 12 ++-- 6 files changed, 41 insertions(+), 55 deletions(-) diff --git a/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs b/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs index d610dc5..0101f17 100644 --- a/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs +++ b/src/UnityFx.Async.Tests/Tests/CompletionCallbackTests.cs @@ -60,7 +60,7 @@ public async Task TryAddCompletionCallback_IsThreadSafe() // Arrange var op = new AsyncCompletionSource(); var counter = 0; - var d = new AsyncOperationCallback(CompletionCallback); + var d = new Action(CompletionCallback); void CompletionCallback(IAsyncOperation o) { diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs index 3ea5379..a4cc972 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs @@ -96,7 +96,7 @@ public event AsyncCompletedEventHandler Completed } /// - public void AddCompletionCallback(AsyncOperationCallback action) + public void AddCompletionCallback(Action action) { if (!TryAddCompletionCallback(action)) { @@ -105,7 +105,7 @@ public void AddCompletionCallback(AsyncOperationCallback action) } /// - public bool TryAddCompletionCallback(AsyncOperationCallback action) + public bool TryAddCompletionCallback(Action action) { ThrowIfDisposed(); @@ -118,7 +118,7 @@ public bool TryAddCompletionCallback(AsyncOperationCallback action) } /// - public void AddCompletionCallback(AsyncOperationCallback action, SynchronizationContext syncContext) + public void AddCompletionCallback(Action action, SynchronizationContext syncContext) { if (!TryAddCompletionCallback(action, syncContext)) { @@ -127,7 +127,7 @@ public void AddCompletionCallback(AsyncOperationCallback action, Synchronization } /// - public bool TryAddCompletionCallback(AsyncOperationCallback action, SynchronizationContext syncContext) + public bool TryAddCompletionCallback(Action action, SynchronizationContext syncContext) { ThrowIfDisposed(); @@ -140,7 +140,7 @@ public bool TryAddCompletionCallback(AsyncOperationCallback action, Synchronizat } /// - public bool RemoveCompletionCallback(AsyncOperationCallback action) + public bool RemoveCompletionCallback(Action action) { if (action != null) { diff --git a/src/UnityFx.Async/Api/Core/AsyncResultQueue{T}.cs b/src/UnityFx.Async/Api/Core/AsyncResultQueue{T}.cs index da70aaa..15e7d34 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResultQueue{T}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResultQueue{T}.cs @@ -31,7 +31,7 @@ public class AsyncResultQueue : ICollection, IReadOnlyCollection where private bool _suspended; private List _ops = new List(); private SendOrPostCallback _startCallback; - private AsyncOperationCallback _completionCallback; + private Action _completionCallback; #endregion diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs index 198467f..c4d6fdf 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncContinuation.cs @@ -13,7 +13,6 @@ namespace UnityFx.Async /// continuation operations. /// /// - /// /// public interface IAsyncContinuation { diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs index 59ef738..b057082 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncOperationEvents.cs @@ -7,15 +7,6 @@ namespace UnityFx.Async { - /// - /// References a method to be called when a corresponding operation state changes. - /// - /// The asynchronous operation. - /// - /// - /// - public delegate void AsyncOperationCallback(IAsyncOperation op); - /// /// Manages events and callbacks of . /// @@ -51,11 +42,11 @@ public interface IAsyncOperationEvents /// Thrown if the delegate being registered is . /// Thrown is the operation has been disposed. /// - /// - /// - /// - /// - /// + /// + /// + /// + /// + /// event AsyncCompletedEventHandler Completed; /// @@ -68,11 +59,11 @@ public interface IAsyncOperationEvents /// The callback to be executed when the operation has completed. /// Thrown if is . /// Thrown is the operation has been disposed. - /// - /// - /// - /// - void AddCompletionCallback(AsyncOperationCallback action); + /// + /// + /// + /// + void AddCompletionCallback(Action action); /// /// Attempts to add a completion callback to be executed after the operation has finished. If the operation is already completed @@ -86,11 +77,11 @@ public interface IAsyncOperationEvents /// Returns if the callback was added; otherwise (the operation is completed). /// Thrown if is . /// Thrown is the operation has been disposed. - /// - /// - /// - /// - bool TryAddCompletionCallback(AsyncOperationCallback action); + /// + /// + /// + /// + bool TryAddCompletionCallback(Action action); /// /// Adds a completion callback to be executed after the operation has completed. If the operation is completed is invoked @@ -105,11 +96,11 @@ public interface IAsyncOperationEvents /// /// Thrown if is . /// Thrown is the operation has been disposed. - /// - /// - /// - /// - void AddCompletionCallback(AsyncOperationCallback action, SynchronizationContext syncContext); + /// + /// + /// + /// + void AddCompletionCallback(Action action, SynchronizationContext syncContext); /// /// Attempts to add a completion callback to be executed after the operation has finished. If the operation is already completed @@ -125,22 +116,22 @@ public interface IAsyncOperationEvents /// Returns if the callback was added; otherwise (the operation is completed). /// Thrown if is . /// Thrown is the operation has been disposed. - /// - /// - /// - /// - bool TryAddCompletionCallback(AsyncOperationCallback action, SynchronizationContext syncContext); + /// + /// + /// + /// + bool TryAddCompletionCallback(Action action, SynchronizationContext syncContext); /// /// Removes an existing completion callback. /// /// The callback to remove. Can be . /// Returns if was removed; otherwise. - /// - /// - /// - /// - bool RemoveCompletionCallback(AsyncOperationCallback action); + /// + /// + /// + /// + bool RemoveCompletionCallback(Action action); /// /// Adds a continuation to be executed after the operation has completed. If the operation is completed is invoked synchronously. diff --git a/src/UnityFx.Async/Implementation/Continuations/CallbackUtility.cs b/src/UnityFx.Async/Implementation/Continuations/CallbackUtility.cs index efc1117..62ea6d9 100644 --- a/src/UnityFx.Async/Implementation/Continuations/CallbackUtility.cs +++ b/src/UnityFx.Async/Implementation/Continuations/CallbackUtility.cs @@ -23,16 +23,16 @@ public static void InvokeCompletionCallback(IAsyncOperation op, object continuat c.Invoke(op); break; - case AsyncOperationCallback aoc: - aoc.Invoke(op); + case Action a: + a.Invoke(op); break; case Action a: a.Invoke(); break; - case AsyncCallback ac: - ac.Invoke(op); + case AsyncCallback a: + a.Invoke(op); break; case AsyncCompletedEventHandler eh: @@ -67,10 +67,6 @@ public static void InvokeProgressCallback(IAsyncOperation op, object callback) af.Invoke(op.Progress); break; - case AsyncOperationCallback ac: - ac.Invoke(op); - break; - case ProgressChangedEventHandler ph: ph.Invoke(op, new ProgressChangedEventArgs((int)(op.Progress * 100), op.AsyncState)); break; From 87bf9a0c3d22929318eaadb785a9fd84bac6a96c Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Tue, 5 Jun 2018 17:50:25 +0300 Subject: [PATCH 40/67] CHANGELOG update --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ddd8412..129c4f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/); this proj ### Fixed - Fixed exception when removing listeners while in `AsyncUpdateSource.OnError` / `AsyncUpdateSource.OnCompleted` / `AsyncUpdateSource.Dispose`. +### Removed +- Removed `AsyncOperationCallback` delegate type. + ----------------------- ## [0.9.2] - 2018.05.25 From 53895b9ceaa085db1236c6dd569164a0152064da Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Tue, 5 Jun 2018 17:56:39 +0300 Subject: [PATCH 41/67] Project structure changes --- .../Implementation/{Continuations => Core}/CallbackCollection.cs | 0 .../Implementation/{Continuations => Core}/CallbackUtility.cs | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/UnityFx.Async/Implementation/{Continuations => Core}/CallbackCollection.cs (100%) rename src/UnityFx.Async/Implementation/{Continuations => Core}/CallbackUtility.cs (100%) diff --git a/src/UnityFx.Async/Implementation/Continuations/CallbackCollection.cs b/src/UnityFx.Async/Implementation/Core/CallbackCollection.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Continuations/CallbackCollection.cs rename to src/UnityFx.Async/Implementation/Core/CallbackCollection.cs diff --git a/src/UnityFx.Async/Implementation/Continuations/CallbackUtility.cs b/src/UnityFx.Async/Implementation/Core/CallbackUtility.cs similarity index 100% rename from src/UnityFx.Async/Implementation/Continuations/CallbackUtility.cs rename to src/UnityFx.Async/Implementation/Core/CallbackUtility.cs From 2dfcb12aa4c0429fea1c9db3768de43f7381515b Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 6 Jun 2018 11:10:51 +0300 Subject: [PATCH 42/67] Removed unused AggregateException methods for net35 --- .../Api/Net35/AggregateException.cs | 26 +------------------ 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/src/UnityFx.Async/Api/Net35/AggregateException.cs b/src/UnityFx.Async/Api/Net35/AggregateException.cs index 8b08281..c7efcb2 100644 --- a/src/UnityFx.Async/Api/Net35/AggregateException.cs +++ b/src/UnityFx.Async/Api/Net35/AggregateException.cs @@ -62,35 +62,11 @@ public AggregateException(string message, Exception innerException) _exceptions = new ReadOnlyCollection(new Exception[] { innerException }); } - /// - /// Initializes a new instance of the class. - /// - public AggregateException(params Exception[] exceptions) - : this(string.Empty, exceptions) - { - } - - /// - /// Initializes a new instance of the class. - /// - public AggregateException(string message, params Exception[] exceptions) - : this(message, (IList)exceptions) - { - } - /// /// Initializes a new instance of the class. /// public AggregateException(IEnumerable exceptions) - : this(string.Empty, exceptions) - { - } - - /// - /// Initializes a new instance of the class. - /// - public AggregateException(string message, IEnumerable exceptions) - : this(message, exceptions as IList ?? (exceptions == null ? null : new List(exceptions))) + : this(string.Empty, exceptions as IList ?? (exceptions == null ? (IList)new Exception[0] : new List(exceptions))) { } From 275485cdfd4d430e76ce643dbed47ad497dc50e8 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 6 Jun 2018 11:29:28 +0300 Subject: [PATCH 43/67] Added SuppressCancellation option --- src/UnityFx.Async/Api/Core/AsyncCreationOptions.cs | 11 ++++++++--- src/UnityFx.Async/Api/Core/AsyncResult.cs | 7 +++++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncCreationOptions.cs b/src/UnityFx.Async/Api/Core/AsyncCreationOptions.cs index d2bb40c..e39466a 100644 --- a/src/UnityFx.Async/Api/Core/AsyncCreationOptions.cs +++ b/src/UnityFx.Async/Api/Core/AsyncCreationOptions.cs @@ -13,13 +13,18 @@ namespace UnityFx.Async public enum AsyncCreationOptions { /// - /// Specifies that the default behavior should be used. + /// Specifies that the default behavior should be used. /// None = 0, /// - /// Forces continuations added to the current operation to be executed asynchronously. + /// Forces continuations added to the current operation to be executed asynchronously. /// - RunContinuationsAsynchronously = AsyncResult.OptionRunContinuationsAsynchronously + RunContinuationsAsynchronously = AsyncResult.OptionRunContinuationsAsynchronously, + + /// + /// If set cancelling the operation has no effect (silently ignored). + /// + SuppressCancellation = AsyncResult.OptionSuppressCancellation } } diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 7c6d0f3..8ea9c98 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -57,6 +57,7 @@ public partial class AsyncResult : IAsyncOperation, IEnumerator private const int _flagDoNotDispose = OptionDoNotDispose << _optionsOffset; private const int _flagRunContinuationsAsynchronously = OptionRunContinuationsAsynchronously << _optionsOffset; + private const int _flagSuppressCancellation = OptionSuppressCancellation << _optionsOffset; private const int _statusMask = 0x0000000f; private const int _optionsMask = 0x70000000; @@ -691,6 +692,7 @@ protected virtual void Dispose(bool disposing) internal const int OptionDoNotDispose = 1; internal const int OptionRunContinuationsAsynchronously = 2; + internal const int OptionSuppressCancellation = 4; /// /// Special status setter for and . @@ -954,6 +956,11 @@ public float Progress /// public void Cancel() { + if ((_flags & _flagSuppressCancellation) != 0) + { + return; + } + if (TrySetFlag(_flagCancellationRequested)) { OnCancel(); From f020acca6e2d89773ab41b8bc24c98997e9cf497 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 6 Jun 2018 11:49:17 +0300 Subject: [PATCH 44/67] Fixed MoveNext() implementation to always return 'true' when not 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 8ea9c98..6869bfd 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -1021,7 +1021,7 @@ public WaitHandle AsyncWaitHandle object IEnumerator.Current => null; /// - bool IEnumerator.MoveNext() => _flags == StatusRunning; + bool IEnumerator.MoveNext() => (_flags & _statusMask) <= StatusRunning; /// void IEnumerator.Reset() => throw new NotSupportedException(); From 0b7ec8448283c7f8610e4ae4ecfd80bb93a3ec89 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 6 Jun 2018 11:50:21 +0300 Subject: [PATCH 45/67] Fixed AsyncResult not constructed correctly when AsyncCreationOptions are specified --- 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 6869bfd..28fe117 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -1107,7 +1107,7 @@ private AsyncResult(int flags) _exception = new OperationCanceledException(); } - if (flags > StatusRunning) + if ((flags & _statusMask) > StatusRunning) { _callback = _callbackCompletionSentinel; _flags = flags | _flagCompletedSynchronously; From 3ee8329072b91596c1dda2595e040ee05d159a92 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 6 Jun 2018 11:58:27 +0300 Subject: [PATCH 46/67] Added more unit-tests --- .../Tests/AsyncResultTests.cs | 189 ++++++++++++++++++ .../Tests/ProgressCallbackTests.cs | 19 -- 2 files changed, 189 insertions(+), 19 deletions(-) diff --git a/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs b/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs index a1e4c4b..0587596 100644 --- a/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs +++ b/src/UnityFx.Async.Tests/Tests/AsyncResultTests.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; using System.Threading; using System.Threading.Tasks; using Xunit; @@ -423,8 +424,146 @@ public async Task Await_ShouldThrowIfCanceled() #endregion + #region IAsyncOperation + + [Theory] + [InlineData(AsyncOperationStatus.Created, 0)] + [InlineData(AsyncOperationStatus.Scheduled, 0)] + [InlineData(AsyncOperationStatus.Running, 0.3f)] + [InlineData(AsyncOperationStatus.RanToCompletion, 1)] + [InlineData(AsyncOperationStatus.Faulted, 0)] + [InlineData(AsyncOperationStatus.Canceled, 0)] + public void Progress_ReturnsCorrentValue(AsyncOperationStatus status, float expectedValue) + { + // Arrange + var progress = 0.3f; + var op = new AsyncCompletionSource(status); + + // Act + op.TrySetProgress(progress); + + // Assert + Assert.Equal(expectedValue, op.Progress); + } + + [Theory] + [InlineData(AsyncOperationStatus.Created, false)] + [InlineData(AsyncOperationStatus.Scheduled, false)] + [InlineData(AsyncOperationStatus.Running, false)] + [InlineData(AsyncOperationStatus.RanToCompletion, true)] + [InlineData(AsyncOperationStatus.Faulted, false)] + [InlineData(AsyncOperationStatus.Canceled, false)] + public void IsCompletedSuccessfully_ReturnsCorrentValue(AsyncOperationStatus status, bool expectedResult) + { + // Arrange + var op = new AsyncResult(status); + + // Act + var result = op.IsCompletedSuccessfully; + + // Assert + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData(AsyncOperationStatus.Created, false)] + [InlineData(AsyncOperationStatus.Scheduled, false)] + [InlineData(AsyncOperationStatus.Running, false)] + [InlineData(AsyncOperationStatus.RanToCompletion, false)] + [InlineData(AsyncOperationStatus.Faulted, true)] + [InlineData(AsyncOperationStatus.Canceled, false)] + public void IsFaulted_ReturnsCorrentValue(AsyncOperationStatus status, bool expectedResult) + { + // Arrange + var op = new AsyncResult(status); + + // Act + var result = op.IsFaulted; + + // Assert + Assert.Equal(expectedResult, result); + } + + [Theory] + [InlineData(AsyncOperationStatus.Created, false)] + [InlineData(AsyncOperationStatus.Scheduled, false)] + [InlineData(AsyncOperationStatus.Running, false)] + [InlineData(AsyncOperationStatus.RanToCompletion, false)] + [InlineData(AsyncOperationStatus.Faulted, false)] + [InlineData(AsyncOperationStatus.Canceled, true)] + public void IsCancelled_ReturnsCorrentValue(AsyncOperationStatus status, bool expectedResult) + { + // Arrange + var op = new AsyncResult(status); + + // Act + var result = op.IsCanceled; + + // Assert + Assert.Equal(expectedResult, result); + } + + #endregion + + #region IAsyncCancellable + + [Fact] + public void Cancel_CanBeCalledWhenDisposed() + { + // Arrange + var op = new AsyncResult(AsyncOperationStatus.RanToCompletion); + op.Dispose(); + + // Act/Assert + op.Cancel(); + } + + [Fact] + public void Cancel_DefaultImplementationThrows() + { + // Arrange + var op = new AsyncResult(); + + // Act/Assert + Assert.Throws(() => op.Cancel()); + } + + [Fact] + public void Cancel_CanBeSuppressed() + { + // Arrange + var op = new AsyncCompletionSource(AsyncCreationOptions.SuppressCancellation); + + // Act + op.Cancel(); + + // Assert + Assert.False(op.IsCompleted); + } + + #endregion + #region IAsyncResult + [Theory] + [InlineData(AsyncOperationStatus.Created, false)] + [InlineData(AsyncOperationStatus.Scheduled, false)] + [InlineData(AsyncOperationStatus.Running, false)] + [InlineData(AsyncOperationStatus.RanToCompletion, true)] + [InlineData(AsyncOperationStatus.Faulted, true)] + [InlineData(AsyncOperationStatus.Canceled, true)] + public void IsCompleted_ReturnsCorrentValue(AsyncOperationStatus status, bool expectedResult) + { + // Arrange + var op = new AsyncResult(status); + + // Act + var result = op.IsCompleted; + + // Assert + Assert.Equal(expectedResult, result); + } + [Fact] public void AsyncWaitHandle_ThrowsIfDisposed() { @@ -438,6 +577,56 @@ public void AsyncWaitHandle_ThrowsIfDisposed() #endregion + #region IEnumerator + + [Fact] + public void Current_IsNull() + { + // Arrange/Act + var op = new AsyncResult(); + + // Act + var result = (op as IEnumerator).Current; + + // Assert + Assert.Null(result); + } + + [Fact] + public void MoveNext_CanBeCalledWhenDisposed() + { + // Arrange + var op = new AsyncResult(AsyncOperationStatus.RanToCompletion); + op.Dispose(); + + // Act + var result = (op as IEnumerator).MoveNext(); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData(AsyncOperationStatus.Created, true)] + [InlineData(AsyncOperationStatus.Scheduled, true)] + [InlineData(AsyncOperationStatus.Running, true)] + [InlineData(AsyncOperationStatus.RanToCompletion, false)] + [InlineData(AsyncOperationStatus.Faulted, false)] + [InlineData(AsyncOperationStatus.Canceled, false)] + public void MoveNext_ReturnsCorrentValue(AsyncOperationStatus status, bool expectedResult) + { + // Arrange + var op = new AsyncResult(status); + + // Act + var result = (op as IEnumerator).MoveNext(); + + // Assert + Assert.Equal(expectedResult, result); + } + + #endregion + #region IDisposable [Fact] diff --git a/src/UnityFx.Async.Tests/Tests/ProgressCallbackTests.cs b/src/UnityFx.Async.Tests/Tests/ProgressCallbackTests.cs index ddac9c4..c8461fa 100644 --- a/src/UnityFx.Async.Tests/Tests/ProgressCallbackTests.cs +++ b/src/UnityFx.Async.Tests/Tests/ProgressCallbackTests.cs @@ -46,25 +46,6 @@ public void TrySetProgress_ReturnsCorrentValue(AsyncOperationStatus status, bool Assert.Equal(expectedResult, result); } - [Theory] - [InlineData(AsyncOperationStatus.Created, 0)] - [InlineData(AsyncOperationStatus.Scheduled, 0)] - [InlineData(AsyncOperationStatus.RanToCompletion, 1)] - [InlineData(AsyncOperationStatus.Faulted, 0)] - [InlineData(AsyncOperationStatus.Canceled, 0)] - public void Progress_ReturnsCorrentValue(AsyncOperationStatus status, float expectedValue) - { - // Arrange - var progress = 0.3f; - var op = new AsyncCompletionSource(status); - - // Act - op.TrySetProgress(progress); - - // Assert - Assert.Equal(expectedValue, op.Progress); - } - [Fact] public void SetProgress_SetsCorrectValue() { From 768153e9db7df8186c7d72af8cea2f71072274f5 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 6 Jun 2018 12:02:23 +0300 Subject: [PATCH 47/67] CHANGELOG update --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 129c4f7..727c112 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,11 +12,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/); this proj - Added update sources for `LateUpdate`, `FixedUpdate` and end-of-frame updates. ### Changed -- Significantly reduced number of memory allocations when adding continuations (implemention optimizations). +- Significantly reduced number of memory allocations when adding continuations. - Changed signature of the `IAsyncContinuation.Invoke` method. ### Fixed - Fixed exception when removing listeners while in `AsyncUpdateSource.OnError` / `AsyncUpdateSource.OnCompleted` / `AsyncUpdateSource.Dispose`. +- Fixed `AsyncResult.MoveNext` to always return `true` while the operation is not completed. +- Fixed `AsyncResult` construction code not working as intended when `AsyncCreationOptions` are specified. ### Removed - Removed `AsyncOperationCallback` delegate type. From 04722a3f141f525496afb18d1ad3626254635ec5 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 6 Jun 2018 12:10:19 +0300 Subject: [PATCH 48/67] A few more unit-tests added --- src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs b/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs index 0587596..68d4dda 100644 --- a/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs +++ b/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs @@ -528,17 +528,19 @@ public void Cancel_DefaultImplementationThrows() Assert.Throws(() => op.Cancel()); } - [Fact] - public void Cancel_CanBeSuppressed() + [Theory] + [InlineData(AsyncCreationOptions.None, true)] + [InlineData(AsyncCreationOptions.SuppressCancellation, false)] + public void Cancel_CanBeSuppressed(AsyncCreationOptions options, bool expectedCompleted) { // Arrange - var op = new AsyncCompletionSource(AsyncCreationOptions.SuppressCancellation); + var op = new AsyncCompletionSource(options); // Act op.Cancel(); // Assert - Assert.False(op.IsCompleted); + Assert.Equal(expectedCompleted, op.IsCompleted); } #endregion From 42443271d91f5fe615d16f1928d4bdb6987ea898 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 6 Jun 2018 15:39:28 +0300 Subject: [PATCH 49/67] Added default Unity main thread SynchronizationContext implementation --- .../UnityFx.Async/Scripts/AsyncUtility.cs | 63 +++--- .../Scripts/MainThreadScheduler.cs | 180 ++++++++++++++++++ .../MainThreadSynchronizationContext.cs | 55 ++++++ 3 files changed, 271 insertions(+), 27 deletions(-) create mode 100644 src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/MainThreadScheduler.cs create mode 100644 src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/MainThreadSynchronizationContext.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 c4a7e28..a86cc1d 100644 --- a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncUtility.cs +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncUtility.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Threading; using UnityEngine; #if UNITY_5_4_OR_NEWER || UNITY_2017 || UNITY_2018 using UnityEngine.Networking; @@ -38,24 +39,32 @@ public static GameObject GetRootGo() { if (ReferenceEquals(_go, null)) { - _go = GameObject.Find(RootGoName); - - if (ReferenceEquals(_go, null)) - { - _go = new GameObject(RootGoName); - GameObject.DontDestroyOnLoad(_go); - } + _go = new GameObject(RootGoName); + GameObject.DontDestroyOnLoad(_go); } return _go; } + /// + /// Sets the for main thread (if none). + /// + public static void SetSynchronizationContext() + { + var go = GetRootGo(); + + if (go && !go.GetComponent()) + { + go.AddComponent(); + } + } + /// /// Returns an instance of an for Update. /// public static IAsyncUpdateSource GetUpdateSource() { - return GetCoroutineRunner().UpdateSource; + return GetRootBehaviour().UpdateSource; } /// @@ -63,7 +72,7 @@ public static IAsyncUpdateSource GetUpdateSource() /// public static IAsyncUpdateSource GetLateUpdateSource() { - return GetCoroutineRunner().LateUpdateSource; + return GetRootBehaviour().LateUpdateSource; } /// @@ -71,7 +80,7 @@ public static IAsyncUpdateSource GetLateUpdateSource() /// public static IAsyncUpdateSource GetFixedUpdateSource() { - return GetCoroutineRunner().FixedUpdateSource; + return GetRootBehaviour().FixedUpdateSource; } /// @@ -79,7 +88,7 @@ public static IAsyncUpdateSource GetFixedUpdateSource() /// public static IAsyncUpdateSource GetEndOfFrameUpdateSource() { - return GetCoroutineRunner().EofUpdateSource; + return GetRootBehaviour().EofUpdateSource; } /// @@ -90,7 +99,7 @@ public static IAsyncUpdateSource GetEndOfFrameUpdateSource() /// An operation that represents the time delay. public static IAsyncOperation Delay(int millisecondsDelay) { - return AsyncResult.Delay(millisecondsDelay, GetCoroutineRunner().UpdateSource); + return AsyncResult.Delay(millisecondsDelay, GetRootBehaviour().UpdateSource); } /// @@ -101,7 +110,7 @@ public static IAsyncOperation Delay(int millisecondsDelay) /// An operation that represents the time delay. public static IAsyncOperation Delay(float secondsDelay) { - return AsyncResult.Delay(secondsDelay, GetCoroutineRunner().UpdateSource); + return AsyncResult.Delay(secondsDelay, GetRootBehaviour().UpdateSource); } /// @@ -112,7 +121,7 @@ public static IAsyncOperation Delay(float secondsDelay) /// An operation that represents the time delay. public static IAsyncOperation Delay(TimeSpan delay) { - return AsyncResult.Delay(delay, GetCoroutineRunner().UpdateSource); + return AsyncResult.Delay(delay, GetRootBehaviour().UpdateSource); } /// @@ -126,7 +135,7 @@ public static Coroutine StartCoroutine(IEnumerator enumerator) throw new ArgumentNullException("enumerator"); } - return GetCoroutineRunner().StartCoroutine(enumerator); + return GetRootBehaviour().StartCoroutine(enumerator); } /// @@ -137,7 +146,7 @@ public static void StopCoroutine(Coroutine coroutine) { if (coroutine != null) { - var runner = TryGetCoroutineRunner(); + var runner = TryGetRootBehaviour(); if (runner) { @@ -154,7 +163,7 @@ public static void StopCoroutine(IEnumerator enumerator) { if (enumerator != null) { - var runner = TryGetCoroutineRunner(); + var runner = TryGetRootBehaviour(); if (runner) { @@ -168,7 +177,7 @@ public static void StopCoroutine(IEnumerator enumerator) /// public static void StopAllCoroutines() { - var runner = TryGetCoroutineRunner(); + var runner = TryGetRootBehaviour(); if (runner) { @@ -193,7 +202,7 @@ public static void AddCompletionCallback(AsyncOperation op, Action completionCal throw new ArgumentNullException("completionCallback"); } - GetCoroutineRunner().AddCompletionCallback(op, completionCallback); + GetRootBehaviour().AddCompletionCallback(op, completionCallback); } #if UNITY_5_2_OR_NEWER || UNITY_5_3_OR_NEWER || UNITY_2017 || UNITY_2018 @@ -215,7 +224,7 @@ public static void AddCompletionCallback(UnityWebRequest request, Action complet throw new ArgumentNullException("completionCallback"); } - GetCoroutineRunner().AddCompletionCallback(request, completionCallback); + GetRootBehaviour().AddCompletionCallback(request, completionCallback); } #endif @@ -237,14 +246,14 @@ internal static void AddCompletionCallback(WWW request, Action completionCallbac throw new ArgumentNullException("completionCallback"); } - GetCoroutineRunner().AddCompletionCallback(request, completionCallback); + GetRootBehaviour().AddCompletionCallback(request, completionCallback); } #endregion #region implementation - private class CoroutineRunner : MonoBehaviour + private class AsyncRootBehaviour : MonoBehaviour { #region data @@ -443,13 +452,13 @@ private IEnumerator EofEnumerator() #endregion } - private static CoroutineRunner TryGetCoroutineRunner() + private static AsyncRootBehaviour TryGetRootBehaviour() { var go = GetRootGo(); if (go) { - var runner = go.GetComponent(); + var runner = go.GetComponent(); if (runner) { @@ -460,17 +469,17 @@ private static CoroutineRunner TryGetCoroutineRunner() return null; } - private static CoroutineRunner GetCoroutineRunner() + private static AsyncRootBehaviour GetRootBehaviour() { var go = GetRootGo(); if (go) { - var runner = go.GetComponent(); + var runner = go.GetComponent(); if (!runner) { - runner = go.AddComponent(); + runner = go.AddComponent(); } return runner; diff --git a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/MainThreadScheduler.cs b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/MainThreadScheduler.cs new file mode 100644 index 0000000..dff64be --- /dev/null +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/MainThreadScheduler.cs @@ -0,0 +1,180 @@ +// 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.Diagnostics; +using System.Threading; +using UnityEngine; + +namespace UnityFx.Async +{ + /// + /// Main thread scheduler that can be used to make sure the code is executed on the main thread. + /// The implementation setups a (if there is none) and + /// attaches it to the main thread. + /// + /// + public sealed class MainThreadScheduler : MonoBehaviour + { + #region data + + private sealed class InvokeResult : AsyncResult + { + private readonly SendOrPostCallback _callback; + + public InvokeResult(SendOrPostCallback d, object asyncState) + : base(null, asyncState) + { + _callback = d; + } + + public void SetCompleted() + { + _callback.Invoke(AsyncState); + } + + public void SetException(Exception e) + { + TrySetException(e); + } + } + + private SynchronizationContext _context; + private int _mainThreadId; + private Queue _actionQueue = new Queue(); + + #endregion + + #region interface + + /// + /// Dispatches a synchronous message to the main thread. + /// + /// The delegate to execute. + /// The object passed to the delegate. + /// Thrown if is . + /// Thrown if the object is disposed. + /// + public void Send(SendOrPostCallback d, object state) + { + if (d == null) + { + throw new ArgumentNullException("d"); + } + + if (!this) + { + throw new ObjectDisposedException(GetType().Name); + } + + if (_mainThreadId == Thread.CurrentThread.ManagedThreadId) + { + d.Invoke(state); + } + else + { + var asyncResult = new InvokeResult(d, state); + + lock (_actionQueue) + { + _actionQueue.Enqueue(asyncResult); + } + + asyncResult.Wait(); + } + } + + /// + /// Dispatches an asynchronous message to the main thread. + /// + /// The delegate to execute. + /// The object passed to the delegate. + /// Thrown if is . + /// Thrown if the object is disposed. + /// + public void Post(SendOrPostCallback d, object state) + { + if (d == null) + { + throw new ArgumentNullException("d"); + } + + if (!this) + { + throw new ObjectDisposedException(GetType().Name); + } + + var asyncResult = new InvokeResult(d, state); + + lock (_actionQueue) + { + _actionQueue.Enqueue(asyncResult); + } + } + + #endregion + + #region MonoBehaviour + + private void Awake() + { + var currentContext = SynchronizationContext.Current; + + if (currentContext == null) + { + var context = new MainThreadSynchronizationContext(this); + SynchronizationContext.SetSynchronizationContext(context); + _context = context; + } + + _mainThreadId = Thread.CurrentThread.ManagedThreadId; + } + + private void OnDestroy() + { + if (_context != null && _context == SynchronizationContext.Current) + { + SynchronizationContext.SetSynchronizationContext(null); + } + + lock (_actionQueue) + { + _actionQueue.Clear(); + } + + _context = null; + } + + private void Update() + { + if (_actionQueue.Count > 0) + { + lock (_actionQueue) + { + while (_actionQueue.Count > 0) + { + var asyncResult = _actionQueue.Dequeue(); + + if (asyncResult != null) + { + try + { + asyncResult.SetCompleted(); + } + catch (Exception e) + { + asyncResult.SetException(e); + } + } + } + } + } + } + + #endregion + + #region implementation + #endregion + } +} diff --git a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/MainThreadSynchronizationContext.cs b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/MainThreadSynchronizationContext.cs new file mode 100644 index 0000000..f574f93 --- /dev/null +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/MainThreadSynchronizationContext.cs @@ -0,0 +1,55 @@ +// 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 +{ + /// + /// Implementation of for Unity. The class is a helper for ; + /// do not use unless absolutely nessesary. + /// + public class MainThreadSynchronizationContext : SynchronizationContext + { + #region data + + private readonly MainThreadScheduler _scheduler; + + #endregion + + #region interface + + /// + /// Initializes a new instance of the class. + /// + public MainThreadSynchronizationContext(MainThreadScheduler scheduler) + { + _scheduler = scheduler; + } + + #endregion + + #region SynchronizationContext + + /// + public override SynchronizationContext CreateCopy() + { + return new MainThreadSynchronizationContext(_scheduler); + } + + /// + public override void Send(SendOrPostCallback d, object state) + { + _scheduler.Send(d, state); + } + + /// + public override void Post(SendOrPostCallback d, object state) + { + _scheduler.Post(d, state); + } + + #endregion + } +} From 4ea6f72f21e30907942ce803e74481616bbd86ae Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 6 Jun 2018 15:41:04 +0300 Subject: [PATCH 50/67] CHANGELOG update --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 727c112..4bc8ced 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/); this proj - Added push-based progress reporting support. - Added `AsyncResult.Delay(float)` overloads. - Added update sources for `LateUpdate`, `FixedUpdate` and end-of-frame updates. +- Added default Unity `SynchronizationContext` implementation. ### Changed - Significantly reduced number of memory allocations when adding continuations. From 9651be041884bba7448ad8c372ba89b79d4e6187 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 6 Jun 2018 18:31:25 +0300 Subject: [PATCH 51/67] Main thread scheduler fixes --- .../Scripts/MainThreadScheduler.cs | 30 +++++++++---------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/MainThreadScheduler.cs b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/MainThreadScheduler.cs index dff64be..fbaf02d 100644 --- a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/MainThreadScheduler.cs +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/MainThreadScheduler.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Threading; using UnityEngine; @@ -74,14 +73,15 @@ public void Send(SendOrPostCallback d, object state) } else { - var asyncResult = new InvokeResult(d, state); - - lock (_actionQueue) + using (var asyncResult = new InvokeResult(d, state)) { - _actionQueue.Enqueue(asyncResult); - } + lock (_actionQueue) + { + _actionQueue.Enqueue(asyncResult); + } - asyncResult.Wait(); + asyncResult.Wait(); + } } } @@ -156,16 +156,14 @@ private void Update() { var asyncResult = _actionQueue.Dequeue(); - if (asyncResult != null) + try + { + asyncResult.SetCompleted(); + } + catch (Exception e) { - try - { - asyncResult.SetCompleted(); - } - catch (Exception e) - { - asyncResult.SetException(e); - } + asyncResult.SetException(e); + Debug.LogException(e); } } } From 4855b8d2bd381aa2301a7f13b42d35677b833a31 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 6 Jun 2018 19:07:36 +0300 Subject: [PATCH 52/67] Comment fixes --- .../UnityFx.Async/Scripts/UnityExtensions.cs | 2 +- src/UnityFx.Async/Api/Core/AsyncResult.cs | 116 +++++++++++++----- .../Api/Core/AsyncResult{TResult}.cs | 12 +- .../Api/Interfaces/IAsyncCancellable.cs | 1 - 4 files changed, 99 insertions(+), 32 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 01fa4f6..33e2653 100644 --- a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/UnityExtensions.cs +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/UnityExtensions.cs @@ -246,7 +246,7 @@ public static WebRequestResult ToAsyncString(this UnityWebRequest reques #if NET_4_6 || NET_STANDARD_2_0 /// - /// Provides an object that waits for the completion of an . This type and its members are intended for compiler use only. + /// 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 { diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 28fe117..750559b 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -16,23 +16,15 @@ namespace UnityFx.Async /// A lightweight net35-compatible asynchronous operation for Unity3d. /// /// - /// This class is the core entity of the library. In many aspects it mimics Task + /// 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 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). - /// 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 - /// if property was used. Also keep in mind that - /// implementation is not thread-safe. - /// - /// Please note that while the class is designed as a lightweight and portable Task-like object, - /// it's NOT a replacement for .NET Task. It is recommended to use Task in general and only switch - /// to this class if Unity/net35 compatibility is a concern. - /// + /// and can be used to implement Asynchronous Programming Model (APM). There are operation + /// state accessors that can be used exactly like corresponding properties of Task. + /// While the class implements disposing is only required + /// if property was used. /// /// Promises for game development /// How to implement the IAsyncResult design pattern @@ -914,7 +906,10 @@ internal static bool TryThrowException(Exception e) #region IAsyncOperation - /// + /// + /// Gets the operation progress in range [0, 1]. + /// + /// Progress of the operation in range [0, 1]. public float Progress { get @@ -934,26 +929,49 @@ public float Progress } } - /// + /// + /// Gets the operation status identifier. + /// + /// Identifier of the operation status. public AsyncOperationStatus Status => (AsyncOperationStatus)(_flags & _statusMask); - /// + /// + /// Gets an exception that caused the operation to end prematurely. If the operation completed successfully + /// or has not yet thrown any exceptions, this will return . + /// + /// An exception that caused the operation to end prematurely. public Exception Exception => (_flags & _flagCompleted) != 0 ? _exception : null; - /// + /// + /// Gets a value indicating whether the operation completed successfully (i.e. with status). + /// + /// A value indicating whether the operation completed successfully. public bool IsCompletedSuccessfully => (_flags & _statusMask) == StatusRanToCompletion; - /// + /// + /// Gets a value indicating whether the operation completed due to an unhandled exception (i.e. with status). + /// + /// A value indicating whether the operation has failed. public bool IsFaulted => (_flags & _statusMask) == StatusFaulted; - /// + /// + /// Gets a value indicating whether the operation completed due to being canceled (i.e. with status). + /// + /// A value indicating whether the operation was canceled. public bool IsCanceled => (_flags & _statusMask) == StatusCanceled; #endregion #region IAsyncCancellable - /// + /// + /// 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. + /// can be used to suppress this method for a specific operation instance. + /// + /// Thrown if cancellation is not supported by the implementation. public void Cancel() { if ((_flags & _flagSuppressCancellation) != 0) @@ -971,7 +989,15 @@ public void Cancel() #region IAsyncResult - /// + /// + /// Gets a that is used to wait for an asynchronous operation to complete. + /// + /// + /// The handle is lazily allocated on the first property access. Make sure to call when + /// the operation instance is not in use. + /// + /// A that is used to wait for an asynchronous operation to complete. + /// public WaitHandle AsyncWaitHandle { get @@ -1004,26 +1030,59 @@ public WaitHandle AsyncWaitHandle } } - /// + /// + /// Gets a user-defined object that qualifies or contains information about an asynchronous operation. + /// + /// A user-defined object that qualifies or contains information about an asynchronous operation. public object AsyncState => _asyncState; - /// + /// + /// Gets a value indicating whether the asynchronous operation completed synchronously. + /// + /// + /// For the vast majority of cases this is . Do not rely on this vlaue. + /// + /// if the asynchronous operation completed synchronously; otherwise, . + /// public bool CompletedSynchronously => (_flags & _flagSynchronous) != 0; - /// + /// + /// Gets a value indicating whether the asynchronous operation has completed. + /// + /// if the operation is complete; otherwise, . + /// public bool IsCompleted => (_flags & _flagCompleted) != 0; #endregion #region IEnumerator - /// + /// + /// Gets the current element in the collection. + /// + /// + /// Not implemented. Always returns . + /// + /// + /// The current element in the collection. + /// object IEnumerator.Current => null; - /// + /// + /// Advances the enumerator to the next element of the collection. + /// + /// + /// Checks whether the operation is completed. Returns if it is; otherwise, . + /// + /// Returns if the enumerator was successfully advanced to the next element; if the enumerator has passed the end of the collection. bool IEnumerator.MoveNext() => (_flags & _statusMask) <= StatusRunning; - /// + /// + /// Sets the enumerator to its initial position, which is before the first element in the collection. + /// + /// + /// Not implemented. Always throws . + /// void IEnumerator.Reset() => throw new NotSupportedException(); #endregion @@ -1031,7 +1090,8 @@ public WaitHandle AsyncWaitHandle #region IDisposable /// - /// Disposes the , releasing all of its unmanaged resources. + /// Disposes the , releasing all of its unmanaged resources. This call is only required if + /// was accessed; otherwise it is safe to ignore this method. /// /// /// Unlike most of the members of , this method is not thread-safe. diff --git a/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs b/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs index 1530746..0cd7375 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult{TResult}.cs @@ -260,7 +260,11 @@ internal bool TryCopyCompletionState(IAsyncOperation patternOp, bool co #region IAsyncOperation - /// + /// + /// Gets the operation result value. + /// + /// Result of the operation. + /// Thrown if the property is accessed before operation is completed. public TResult Result { get @@ -281,7 +285,11 @@ public TResult Result #if !NET35 - /// + /// + /// Notifies the provider that an observer is to receive notifications. + /// + /// The object that is to receive notifications. + /// A reference to an interface that allows observers to stop receiving notifications before the provider has finished sending them. public IDisposable Subscribe(IObserver observer) { ThrowIfDisposed(); diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncCancellable.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncCancellable.cs index e4b2633..8069b19 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncCancellable.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncCancellable.cs @@ -15,7 +15,6 @@ public interface IAsyncCancellable /// 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(); } From bdf8e87cfae646b0e3700c474f453f05999e11e4 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Wed, 6 Jun 2018 19:13:25 +0300 Subject: [PATCH 53/67] README update --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 1680caa..2e5ea81 100644 --- a/README.md +++ b/README.md @@ -5,18 +5,18 @@ Channel | UnityFx.Async | AppVeyor | [![Build status](https://ci.appveyor.com/api/projects/status/hfmq9vow53al7tpd/branch/master?svg=true)](https://ci.appveyor.com/project/Arvtesh/unityfx-async/branch/master) [![AppVeyor tests](https://img.shields.io/appveyor/tests/Arvtesh/unityFx-async.svg)](https://ci.appveyor.com/project/Arvtesh/unityfx-async/build/tests) NuGet | [![NuGet](https://img.shields.io/nuget/v/UnityFx.Async.svg)](https://www.nuget.org/packages/UnityFx.Async) Github | [![GitHub release](https://img.shields.io/github/release/Arvtesh/UnityFx.Async.svg?logo=github)](https://github.com/Arvtesh/UnityFx.Async/releases) -Unity Asset Store | [![Asynchronous operations for Unity](https://img.shields.io/badge/tools-v0.9.2-green.svg)](https://assetstore.unity.com/packages/tools/asynchronous-operations-for-unity-96696) +Unity Asset Store | [![Asynchronous operations for Unity](https://img.shields.io/badge/tools-v0.9.3-green.svg)](https://assetstore.unity.com/packages/tools/asynchronous-operations-for-unity-96696) ## Synopsis -*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). +*UnityFx.Async* is a set of classes and interfaces that extend [Unity3d](https://unity3d.com) asynchronous operations and can be used very much like [Tasks](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) in .NET or [Promises](https://developers.google.com/web/fundamentals/primers/promises) in JS. 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. - 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 operations in coroutines and net35-compilance. +- [Unity3d](https://unity3d.com)-specific features and compatibility. This includes possibility to yield operations in coroutines, net35-compilance, Unity asynchronous operation extensions etc. The table below summarizes differences berween *UnityFx.Async* and other popular asynchronous operation frameworks: From 5f6220ad0adad42372e6ac1e217070e073ba23dc Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 7 Jun 2018 12:45:49 +0300 Subject: [PATCH 54/67] Added AsyncResult wrappers for AsyncOperation, ResourceRequest and AssetBundleRequest --- .../Scripts/AssetBundleRequestResult{T}.cs | 74 +++++++++++++++++++ .../Scripts/AsyncOperationResult.cs | 74 +++++++++++++++++++ .../Scripts/ResourceRequestResult{T}.cs | 74 +++++++++++++++++++ .../UnityFx.Async/Scripts/UnityExtensions.cs | 59 +-------------- 4 files changed, 225 insertions(+), 56 deletions(-) create mode 100644 src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AssetBundleRequestResult{T}.cs create mode 100644 src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncOperationResult.cs create mode 100644 src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/ResourceRequestResult{T}.cs diff --git a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AssetBundleRequestResult{T}.cs b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AssetBundleRequestResult{T}.cs new file mode 100644 index 0000000..e99751b --- /dev/null +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AssetBundleRequestResult{T}.cs @@ -0,0 +1,74 @@ +// Copyright (c) Alexander Bogarsukov. +// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; +using UnityEngine; + +namespace UnityFx.Async +{ + /// + /// A wrapper for with result value. + /// + /// Result type. + public sealed class AssetBundleRequestResult : AsyncResult where T : UnityEngine.Object + { + #region data + + private readonly AssetBundleRequest _op; + + #endregion + + #region interface + + /// + /// Gets the underlying instance. + /// + public AssetBundleRequest Request + { + get + { + return _op; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// Source web request. + public AssetBundleRequestResult(AssetBundleRequest op) + : base(AsyncOperationStatus.Running) + { + _op = op; + + if (op.isDone) + { + TrySetResult(op.asset as T, true); + } + else + { +#if UNITY_2017_2_OR_NEWER || UNITY_2018 + + // Starting with Unity 2017.2 there is AsyncOperation.completed event + op.completed += o => TrySetResult(o.asset as T); + +#else + + AsyncUtility.AddCompletionCallback(op, () => TrySetResult(_op.asset as T)); + +#endif + } + } + + #endregion + + #region AsyncResult + + /// + protected override float GetProgress() + { + return _op.progress; + } + + #endregion + } +} diff --git a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncOperationResult.cs b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncOperationResult.cs new file mode 100644 index 0000000..8ed91a4 --- /dev/null +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncOperationResult.cs @@ -0,0 +1,74 @@ +// Copyright (c) Alexander Bogarsukov. +// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; +using UnityEngine; + +namespace UnityFx.Async +{ + /// + /// A wrapper for with result value. + /// + /// Result type. + public class AsyncOperationResult : AsyncResult + { + #region data + + private readonly AsyncOperation _op; + + #endregion + + #region interface + + /// + /// Gets the underlying instance. + /// + public AsyncOperation Operation + { + get + { + return _op; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// Source web request. + public AsyncOperationResult(AsyncOperation op) + : base(AsyncOperationStatus.Running) + { + _op = op; + + if (op.isDone) + { + TrySetCompleted(true); + } + else + { +#if UNITY_2017_2_OR_NEWER || UNITY_2018 + + // Starting with Unity 2017.2 there is AsyncOperation.completed event + op.completed += o => TrySetCompleted(); + +#else + + AsyncUtility.AddCompletionCallback(op, () => TrySetCompleted()); + +#endif + } + } + + #endregion + + #region AsyncResult + + /// + protected override float GetProgress() + { + return _op.progress; + } + + #endregion + } +} diff --git a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/ResourceRequestResult{T}.cs b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/ResourceRequestResult{T}.cs new file mode 100644 index 0000000..4e5a45c --- /dev/null +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/ResourceRequestResult{T}.cs @@ -0,0 +1,74 @@ +// Copyright (c) Alexander Bogarsukov. +// Licensed under the MIT license. See the LICENSE.md file in the project root for more information. + +using System; +using UnityEngine; + +namespace UnityFx.Async +{ + /// + /// A wrapper for with result value. + /// + /// Result type. + public class ResourceRequestResult : AsyncResult where T : UnityEngine.Object + { + #region data + + private readonly ResourceRequest _op; + + #endregion + + #region interface + + /// + /// Gets the underlying instance. + /// + public ResourceRequest Request + { + get + { + return _op; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// Source web request. + public ResourceRequestResult(ResourceRequest op) + : base(AsyncOperationStatus.Running) + { + _op = op; + + if (op.isDone) + { + TrySetResult(op.asset as T, true); + } + else + { +#if UNITY_2017_2_OR_NEWER || UNITY_2018 + + // Starting with Unity 2017.2 there is AsyncOperation.completed event + op.completed += o => TrySetResult(o.asset as T); + +#else + + AsyncUtility.AddCompletionCallback(op, () => TrySetResult(_op.asset as T)); + +#endif + } + } + + #endregion + + #region AsyncResult + + /// + protected override float GetProgress() + { + return _op.progress; + } + + #endregion + } +} 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 33e2653..a97b2a4 100644 --- a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/UnityExtensions.cs +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/UnityExtensions.cs @@ -32,20 +32,7 @@ public static IAsyncOperation ToAsync(this AsyncOperation op) } else { - var result = new AsyncCompletionSource(AsyncOperationStatus.Running, op); - -#if UNITY_2017_2_OR_NEWER || UNITY_2018 - - // Starting with Unity 2017.2 there is AsyncOperation.completed event - op.completed += o => result.TrySetCompleted(); - -#else - - AsyncUtility.AddCompletionCallback(op, () => result.TrySetCompleted()); - -#endif - - return result; + return new AsyncOperationResult(op); } } @@ -55,27 +42,7 @@ public static IAsyncOperation ToAsync(this AsyncOperation op) /// The source operation. public static IAsyncOperation ToAsync(this ResourceRequest op) where T : UnityEngine.Object { - if (op.isDone) - { - return AsyncResult.FromResult(op.asset as T); - } - else - { - var result = new AsyncCompletionSource(AsyncOperationStatus.Running, op); - -#if UNITY_2017_2_OR_NEWER || UNITY_2018 - - // Starting with Unity 2017.2 there is AsyncOperation.completed event - op.completed += o => result.TrySetResult(op.asset as T); - -#else - - AsyncUtility.AddCompletionCallback(op, () => result.TrySetResult(op.asset as T)); - -#endif - - return result; - } + return new ResourceRequestResult(op); } /// @@ -84,27 +51,7 @@ public static IAsyncOperation ToAsync(this ResourceRequest op) where T : U /// The source operation. public static IAsyncOperation ToAsync(this AssetBundleRequest op) where T : UnityEngine.Object { - if (op.isDone) - { - return AsyncResult.FromResult(op.asset as T); - } - else - { - var result = new AsyncCompletionSource(AsyncOperationStatus.Running, op); - -#if UNITY_2017_2_OR_NEWER || UNITY_2018 - - // Starting with Unity 2017.2 there is AsyncOperation.completed event - op.completed += o => result.TrySetResult(op.asset as T); - -#else - - AsyncUtility.AddCompletionCallback(op, () => result.TrySetResult(op.asset as T)); - -#endif - - return result; - } + return new AssetBundleRequestResult(op); } #if NET_4_6 || NET_STANDARD_2_0 From 2b6a2ff37eec6c1a5fa5b5a268432e75924e48a3 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 7 Jun 2018 12:58:02 +0300 Subject: [PATCH 55/67] Refactoring of main thread SynchronizationContext implementation --- .../Scripts/AsyncOperationResult.cs | 1 - .../UnityFx.Async/Scripts/AsyncUtility.cs | 168 ++++++++++++++++- .../Scripts/MainThreadScheduler.cs | 178 ------------------ .../MainThreadSynchronizationContext.cs | 55 ------ src/UnityFx.Async/NOTES.txt | 1 - 5 files changed, 159 insertions(+), 244 deletions(-) delete mode 100644 src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/MainThreadScheduler.cs delete mode 100644 src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/MainThreadSynchronizationContext.cs delete mode 100644 src/UnityFx.Async/NOTES.txt diff --git a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncOperationResult.cs b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncOperationResult.cs index 8ed91a4..998f81c 100644 --- a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncOperationResult.cs +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncOperationResult.cs @@ -9,7 +9,6 @@ namespace UnityFx.Async /// /// A wrapper for with result value. /// - /// Result type. public class AsyncOperationResult : AsyncResult { #region data 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 a86cc1d..0c9eaee 100644 --- a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncUtility.cs +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncUtility.cs @@ -47,16 +47,11 @@ public static GameObject GetRootGo() } /// - /// Sets the for main thread (if none). + /// Initializes the utilities. If skipped the utilities are lazily initialized. /// - public static void SetSynchronizationContext() + public static void Initialize() { - var go = GetRootGo(); - - if (go && !go.GetComponent()) - { - go.AddComponent(); - } + GetRootBehaviour(); } /// @@ -253,18 +248,84 @@ internal static void AddCompletionCallback(WWW request, Action completionCallbac #region implementation - private class AsyncRootBehaviour : MonoBehaviour + private sealed class InvokeResult : AsyncResult + { + private readonly SendOrPostCallback _callback; + + public InvokeResult(SendOrPostCallback d, object asyncState) + : base(null, asyncState) + { + _callback = d; + } + + public void Invoke() + { + _callback.Invoke(AsyncState); + } + + public void SetCompleted() + { + TrySetCompleted(); + } + + public void SetException(Exception e) + { + TrySetException(e); + } + } + + private sealed class MainThreadSynchronizationContext : SynchronizationContext + { + private readonly AsyncRootBehaviour _scheduler; + + public MainThreadSynchronizationContext(AsyncRootBehaviour scheduler) + { + _scheduler = scheduler; + } + + public override SynchronizationContext CreateCopy() + { + return new MainThreadSynchronizationContext(_scheduler); + } + + public override void Send(SendOrPostCallback d, object state) + { + if (d == null) + { + throw new ArgumentNullException("d"); + } + + _scheduler.Send(d, state); + } + + public override void Post(SendOrPostCallback d, object state) + { + if (d == null) + { + throw new ArgumentNullException("d"); + } + + _scheduler.Post(d, state); + } + } + + private sealed class AsyncRootBehaviour : MonoBehaviour { #region data private Dictionary _ops; private List _opsToRemove; + private AsyncUpdateSource _updateSource; private AsyncUpdateSource _lateUpdateSource; private AsyncUpdateSource _fixedUpdateSource; private AsyncUpdateSource _eofUpdateSource; private WaitForEndOfFrame _eof; + private SynchronizationContext _context; + private int _mainThreadId; + private Queue _actionQueue; + #endregion #region interface @@ -334,10 +395,65 @@ public void AddCompletionCallback(object op, Action cb) _ops.Add(op, cb); } + public void Send(SendOrPostCallback d, object state) + { + if (!this) + { + throw new ObjectDisposedException(GetType().Name); + } + + if (_mainThreadId == Thread.CurrentThread.ManagedThreadId) + { + d.Invoke(state); + } + else + { + using (var asyncResult = new InvokeResult(d, state)) + { + lock (_actionQueue) + { + _actionQueue.Enqueue(asyncResult); + } + + asyncResult.Wait(); + } + } + } + + public void Post(SendOrPostCallback d, object state) + { + if (!this) + { + throw new ObjectDisposedException(GetType().Name); + } + + var asyncResult = new InvokeResult(d, state); + + lock (_actionQueue) + { + _actionQueue.Enqueue(asyncResult); + } + } + #endregion #region MonoBehavoiur + private void Awake() + { + var currentContext = SynchronizationContext.Current; + + if (currentContext == null) + { + var context = new MainThreadSynchronizationContext(this); + SynchronizationContext.SetSynchronizationContext(context); + _context = context; + } + + _mainThreadId = Thread.CurrentThread.ManagedThreadId; + _actionQueue = new Queue(); + } + private void Update() { if (_ops != null && _ops.Count > 0) @@ -390,6 +506,28 @@ private void Update() { _updateSource.OnNext(Time.deltaTime); } + + if (_actionQueue.Count > 0) + { + lock (_actionQueue) + { + while (_actionQueue.Count > 0) + { + var asyncResult = _actionQueue.Dequeue(); + + try + { + asyncResult.Invoke(); + asyncResult.SetCompleted(); + } + catch (Exception e) + { + asyncResult.SetException(e); + Debug.LogException(e); + } + } + } + } } private void LateUpdate() @@ -433,6 +571,18 @@ private void OnDestroy() _eofUpdateSource.Dispose(); _eofUpdateSource = null; } + + if (_context != null && _context == SynchronizationContext.Current) + { + SynchronizationContext.SetSynchronizationContext(null); + } + + lock (_actionQueue) + { + _actionQueue.Clear(); + } + + _context = null; } #endregion diff --git a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/MainThreadScheduler.cs b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/MainThreadScheduler.cs deleted file mode 100644 index fbaf02d..0000000 --- a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/MainThreadScheduler.cs +++ /dev/null @@ -1,178 +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.Threading; -using UnityEngine; - -namespace UnityFx.Async -{ - /// - /// Main thread scheduler that can be used to make sure the code is executed on the main thread. - /// The implementation setups a (if there is none) and - /// attaches it to the main thread. - /// - /// - public sealed class MainThreadScheduler : MonoBehaviour - { - #region data - - private sealed class InvokeResult : AsyncResult - { - private readonly SendOrPostCallback _callback; - - public InvokeResult(SendOrPostCallback d, object asyncState) - : base(null, asyncState) - { - _callback = d; - } - - public void SetCompleted() - { - _callback.Invoke(AsyncState); - } - - public void SetException(Exception e) - { - TrySetException(e); - } - } - - private SynchronizationContext _context; - private int _mainThreadId; - private Queue _actionQueue = new Queue(); - - #endregion - - #region interface - - /// - /// Dispatches a synchronous message to the main thread. - /// - /// The delegate to execute. - /// The object passed to the delegate. - /// Thrown if is . - /// Thrown if the object is disposed. - /// - public void Send(SendOrPostCallback d, object state) - { - if (d == null) - { - throw new ArgumentNullException("d"); - } - - if (!this) - { - throw new ObjectDisposedException(GetType().Name); - } - - if (_mainThreadId == Thread.CurrentThread.ManagedThreadId) - { - d.Invoke(state); - } - else - { - using (var asyncResult = new InvokeResult(d, state)) - { - lock (_actionQueue) - { - _actionQueue.Enqueue(asyncResult); - } - - asyncResult.Wait(); - } - } - } - - /// - /// Dispatches an asynchronous message to the main thread. - /// - /// The delegate to execute. - /// The object passed to the delegate. - /// Thrown if is . - /// Thrown if the object is disposed. - /// - public void Post(SendOrPostCallback d, object state) - { - if (d == null) - { - throw new ArgumentNullException("d"); - } - - if (!this) - { - throw new ObjectDisposedException(GetType().Name); - } - - var asyncResult = new InvokeResult(d, state); - - lock (_actionQueue) - { - _actionQueue.Enqueue(asyncResult); - } - } - - #endregion - - #region MonoBehaviour - - private void Awake() - { - var currentContext = SynchronizationContext.Current; - - if (currentContext == null) - { - var context = new MainThreadSynchronizationContext(this); - SynchronizationContext.SetSynchronizationContext(context); - _context = context; - } - - _mainThreadId = Thread.CurrentThread.ManagedThreadId; - } - - private void OnDestroy() - { - if (_context != null && _context == SynchronizationContext.Current) - { - SynchronizationContext.SetSynchronizationContext(null); - } - - lock (_actionQueue) - { - _actionQueue.Clear(); - } - - _context = null; - } - - private void Update() - { - if (_actionQueue.Count > 0) - { - lock (_actionQueue) - { - while (_actionQueue.Count > 0) - { - var asyncResult = _actionQueue.Dequeue(); - - try - { - asyncResult.SetCompleted(); - } - catch (Exception e) - { - asyncResult.SetException(e); - Debug.LogException(e); - } - } - } - } - } - - #endregion - - #region implementation - #endregion - } -} diff --git a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/MainThreadSynchronizationContext.cs b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/MainThreadSynchronizationContext.cs deleted file mode 100644 index f574f93..0000000 --- a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/MainThreadSynchronizationContext.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 -{ - /// - /// Implementation of for Unity. The class is a helper for ; - /// do not use unless absolutely nessesary. - /// - public class MainThreadSynchronizationContext : SynchronizationContext - { - #region data - - private readonly MainThreadScheduler _scheduler; - - #endregion - - #region interface - - /// - /// Initializes a new instance of the class. - /// - public MainThreadSynchronizationContext(MainThreadScheduler scheduler) - { - _scheduler = scheduler; - } - - #endregion - - #region SynchronizationContext - - /// - public override SynchronizationContext CreateCopy() - { - return new MainThreadSynchronizationContext(_scheduler); - } - - /// - public override void Send(SendOrPostCallback d, object state) - { - _scheduler.Send(d, state); - } - - /// - public override void Post(SendOrPostCallback d, object state) - { - _scheduler.Post(d, state); - } - - #endregion - } -} diff --git a/src/UnityFx.Async/NOTES.txt b/src/UnityFx.Async/NOTES.txt deleted file mode 100644 index ff959d7..0000000 --- a/src/UnityFx.Async/NOTES.txt +++ /dev/null @@ -1 +0,0 @@ -TODO: From 1d6362265297ff4a025a486163ca6a513986c2ec Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 7 Jun 2018 13:08:22 +0300 Subject: [PATCH 56/67] Added comments to IAsyncOperationEvents implementation --- .../Api/Core/AsyncResult.Events.cs | 260 ++++++++++++++++-- 1 file changed, 238 insertions(+), 22 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs index a4cc972..b05d394 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Events.cs @@ -39,7 +39,17 @@ internal void SetContinuationForAwait(Action continuation, SynchronizationContex #region IAsyncOperationEvents - /// + /// + /// Raised when the operation progress is changed. + /// + /// + /// The event handler is invoked on a thread that registered it (if it has a attached). + /// If the operation is already completed the event handler is called synchronously. Throwing an exception from the event handler + /// might cause unspecified behaviour. + /// + /// Thrown if the delegate being registered is . + /// Thrown is the operation has been disposed. + /// public event ProgressChangedEventHandler ProgressChanged { add @@ -67,7 +77,17 @@ public event ProgressChangedEventHandler ProgressChanged } } - /// + /// + /// Raised when the operation is completed. + /// + /// + /// The event handler is invoked on a thread that registered it (if it has a attached). + /// If the operation is already completed the event handler is called synchronously. Throwing an exception from the event handler + /// might cause unspecified behaviour. + /// + /// Thrown if the delegate being registered is . + /// Thrown is the operation has been disposed. + /// public event AsyncCompletedEventHandler Completed { add @@ -95,7 +115,16 @@ public event AsyncCompletedEventHandler Completed } } - /// + /// + /// Adds a completion callback to be executed after the operation has completed. If the operation is already completed the is called synchronously. + /// + /// + /// The is invoked on a thread that registered the continuation (if it has a attached). + /// Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The callback to be executed when the operation has completed. + /// Thrown if is . + /// Thrown is the operation has been disposed. public void AddCompletionCallback(Action action) { if (!TryAddCompletionCallback(action)) @@ -104,7 +133,18 @@ public void AddCompletionCallback(Action 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 is invoked on a thread that registered the continuation (if it has a attached). + /// Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The callback to be executed when the operation has completed. + /// Returns if the callback was added; otherwise (the operation is completed). + /// Thrown if is . + /// Thrown is the operation has been disposed. public bool TryAddCompletionCallback(Action action) { ThrowIfDisposed(); @@ -117,7 +157,19 @@ public bool TryAddCompletionCallback(Action action) return TryAddCallback(action, SynchronizationContext.Current, true); } - /// + /// + /// Adds a completion callback to be executed after the operation has completed. If the operation is completed is invoked + /// on the specified. + /// + /// + /// The is invoked on a specified. Throwing an exception from the callback might cause unspecified behaviour. + /// + /// 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. public void AddCompletionCallback(Action action, SynchronizationContext syncContext) { if (!TryAddCompletionCallback(action, syncContext)) @@ -126,7 +178,20 @@ public void AddCompletionCallback(Action action, Synchronizatio } } - /// + /// + /// 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 is invoked on a specified. Throwing an exception from the callback might cause unspecified behaviour. + /// + /// 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. + /// + /// Returns if the callback was added; otherwise (the operation is completed). + /// Thrown if is . + /// Thrown is the operation has been disposed. public bool TryAddCompletionCallback(Action action, SynchronizationContext syncContext) { ThrowIfDisposed(); @@ -139,7 +204,11 @@ public bool TryAddCompletionCallback(Action action, Synchroniza return TryAddCallback(action, syncContext, true); } - /// + /// + /// Removes an existing completion callback. + /// + /// The callback to remove. Can be . + /// Returns if was removed; otherwise. public bool RemoveCompletionCallback(Action action) { if (action != null) @@ -150,7 +219,16 @@ public bool RemoveCompletionCallback(Action action) return false; } - /// + /// + /// Adds a continuation to be executed after the operation has completed. If the operation is completed is invoked synchronously. + /// + /// + /// The is invoked on a thread that registered the continuation (if it has a attached). + /// Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The callback to be executed when the operation has completed. + /// Thrown if is . + /// Thrown is the operation has been disposed. public void AddCompletionCallback(IAsyncContinuation continuation) { if (!TryAddCompletionCallback(continuation)) @@ -159,7 +237,18 @@ public void AddCompletionCallback(IAsyncContinuation continuation) } } - /// + /// + /// Attempts to add a continuation to be executed after the operation has finished. If the operation is already completed + /// the method does nothing and just returns . + /// + /// + /// The is invoked on a thread that registered the continuation (if it has a attached). + /// Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The cotinuation to be executed when the operation has completed. + /// Returns if the callback was added; otherwise (the operation is completed). + /// Thrown if is . + /// Thrown is the operation has been disposed. public bool TryAddCompletionCallback(IAsyncContinuation continuation) { ThrowIfDisposed(); @@ -172,7 +261,19 @@ public bool TryAddCompletionCallback(IAsyncContinuation continuation) return TryAddCallback(continuation, SynchronizationContext.Current, true); } - /// + /// + /// Adds a continuation to be executed after the operation has completed. If the operation is completed + /// is invoked on the specified. + /// + /// + /// The is invoked on a specified. Throwing an exception from the callback might cause unspecified behaviour. + /// + /// 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. public void AddCompletionCallback(IAsyncContinuation continuation, SynchronizationContext syncContext) { if (!TryAddCompletionCallback(continuation, syncContext)) @@ -181,7 +282,20 @@ public void AddCompletionCallback(IAsyncContinuation continuation, Synchronizati } } - /// + /// + /// Attempts to add a continuation to be executed after the operation has finished. If the operation is already completed + /// the method does nothing and just returns . + /// + /// + /// The is invoked on a specified. Throwing an exception from the callback might cause unspecified behaviour. + /// + /// 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. public bool TryAddCompletionCallback(IAsyncContinuation continuation, SynchronizationContext syncContext) { ThrowIfDisposed(); @@ -194,7 +308,11 @@ public bool TryAddCompletionCallback(IAsyncContinuation continuation, Synchroniz return TryAddCallback(continuation, syncContext, true); } - /// + /// + /// Removes an existing continuation. + /// + /// The continuation to remove. Can be . + /// Returns if was removed; otherwise. public bool RemoveCompletionCallback(IAsyncContinuation continuation) { if (continuation != null) @@ -205,7 +323,16 @@ public bool RemoveCompletionCallback(IAsyncContinuation continuation) return false; } - /// + /// + /// Adds a callback to be executed when the operation progress has changed. If the operation is already completed the is called synchronously. + /// + /// + /// The is invoked on a thread that registered the callback (if it has a attached). + /// Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The callback to be executed when the operation progress has changed. + /// Thrown if is . + /// Thrown is the operation has been disposed. public void AddProgressCallback(Action action) { if (!TryAddProgressCallback(action)) @@ -214,7 +341,18 @@ public void AddProgressCallback(Action action) } } - /// + /// + /// Attempts to add a callback to be executed when the operation progress has changed. If the operation is already completed + /// the method does nothing and just returns . + /// + /// + /// The is invoked on a thread that registered the callback (if it has a attached). + /// Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The callback to be executed when the operation progress has changed. + /// Returns if the callback was added; otherwise (the operation is completed). + /// Thrown if is . + /// Thrown is the operation has been disposed. public bool TryAddProgressCallback(Action action) { ThrowIfDisposed(); @@ -227,7 +365,19 @@ public bool TryAddProgressCallback(Action action) return TryAddCallback(action, SynchronizationContext.Current, false); } - /// + /// + /// Adds a callback to be executed when the operation progress has changed. If the operation is completed is invoked + /// on the specified. + /// + /// + /// The is invoked on a specified. Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The callback to be executed when the operation progress has changed. + /// If not method attempts to marshal the continuation to the synchronization context. + /// Otherwise the callback is invoked on a thread that initiated the operation. + /// + /// Thrown if is . + /// Thrown is the operation has been disposed. public void AddProgressCallback(Action action, SynchronizationContext syncContext) { if (!TryAddProgressCallback(action, syncContext)) @@ -236,7 +386,20 @@ public void AddProgressCallback(Action action, SynchronizationContext syn } } - /// + /// + /// Attempts to add a callback to be executed when the operation progress has changed. If the operation is already completed + /// the method does nothing and just returns . + /// + /// + /// The is invoked on a specified. Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The callback to be executed when the operation progress has changed. + /// If not method attempts to marshal the callback to the synchronization context. + /// Otherwise the callback is invoked on a thread that initiated the operation. + /// + /// Returns if the callback was added; otherwise (the operation is completed). + /// Thrown if is . + /// Thrown is the operation has been disposed. public bool TryAddProgressCallback(Action action, SynchronizationContext syncContext) { ThrowIfDisposed(); @@ -249,7 +412,11 @@ public bool TryAddProgressCallback(Action action, SynchronizationContext return TryAddCallback(action, syncContext, false); } - /// + /// + /// Removes an existing progress callback. + /// + /// The callback to remove. Can be . + /// Returns if was removed; otherwise. public bool RemoveProgressCallback(Action action) { if (action != null) @@ -262,7 +429,16 @@ public bool RemoveProgressCallback(Action action) #if !NET35 - /// + /// + /// Adds a callback to be executed each time progress value changes. If the operation is completed is invoked synchronously. + /// + /// + /// The is invoked on a thread that registered it (if it has a attached). + /// Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The callback to be executed when the operation progress value has changed. + /// Thrown if is . + /// Thrown is the operation has been disposed. public void AddProgressCallback(IProgress callback) { if (!TryAddProgressCallback(callback)) @@ -271,7 +447,18 @@ public void AddProgressCallback(IProgress callback) } } - /// + /// + /// Attempts to add a progress callback to be executed each time progress value changes. If the operation is already completed + /// the method does nothing and just returns . + /// + /// + /// The is invoked on a thread that registered the continuation (if it has a attached). + /// Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The callback to be executed when the operation progress value has changed. + /// Returns if the callback was added; otherwise (the operation is completed). + /// Thrown if is . + /// Thrown is the operation has been disposed. public bool TryAddProgressCallback(IProgress callback) { ThrowIfDisposed(); @@ -284,7 +471,19 @@ public bool TryAddProgressCallback(IProgress callback) return TryAddCallback(callback, SynchronizationContext.Current, false); } - /// + /// + /// Adds a callback to be executed each time progress value changes. If the operation is completed + /// is invoked on the specified. + /// + /// + /// The is invoked on a specified. Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The callback to be executed when the operation progress value has changed. + /// 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. public void AddProgressCallback(IProgress callback, SynchronizationContext syncContext) { if (!TryAddProgressCallback(callback, syncContext)) @@ -293,7 +492,20 @@ public void AddProgressCallback(IProgress callback, SynchronizationContex } } - /// + /// + /// Attempts to add a progress callback to be executed each time progress value changes. If the operation is already completed + /// the method does nothing and just returns . + /// + /// + /// The is invoked on a specified. Throwing an exception from the callback might cause unspecified behaviour. + /// + /// The callback to be executed when the operation progress value has changed. + /// If not method attempts to marshal the callback 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. public bool TryAddProgressCallback(IProgress callback, SynchronizationContext syncContext) { ThrowIfDisposed(); @@ -306,7 +518,11 @@ public bool TryAddProgressCallback(IProgress callback, SynchronizationCon return TryAddCallback(callback, syncContext, false); } - /// + /// + /// Removes an existing progress callback. + /// + /// The callback to remove. Can be . + /// Returns if was removed; otherwise. public bool RemoveProgressCallback(IProgress callback) { if (callback != null) From 863ad4b7b628b6aa87bf523bcaa3d7eddab33cb5 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 7 Jun 2018 15:09:01 +0300 Subject: [PATCH 57/67] Added main thread related utility methods --- .../UnityFx.Async/Scripts/AsyncUtility.cs | 112 +++++++++++++++--- 1 file changed, 98 insertions(+), 14 deletions(-) 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 0c9eaee..b1d64f7 100644 --- a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncUtility.cs +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncUtility.cs @@ -86,6 +86,45 @@ public static IAsyncUpdateSource GetEndOfFrameUpdateSource() return GetRootBehaviour().EofUpdateSource; } + /// + /// Dispatches a synchronous message to the main thread. + /// + /// The delegate to invoke. + /// The object passed to the delegate. + /// Thrown if is . + /// + /// + public static void SendToMainThread(SendOrPostCallback d, object state) + { + GetRootBehaviour().Send(d, state); + } + + /// + /// Dispatches an asynchronous message to the main thread. + /// + /// The delegate to invoke. + /// The object passed to the delegate. + /// Thrown if is . + /// + /// + public static IAsyncOperation PostToMainThread(SendOrPostCallback d, object state) + { + return GetRootBehaviour().Post(d, state); + } + + /// + /// Dispatches the specified delegate on the main thread. + /// + /// The delegate to invoke. + /// The object passed to the delegate. + /// Thrown if is . + /// + /// + public static IAsyncOperation InvokeOnMainThread(SendOrPostCallback d, object state) + { + return GetRootBehaviour().Invoke(d, state); + } + /// /// Creates an operation that completes after a time delay. /// @@ -290,21 +329,11 @@ public override SynchronizationContext CreateCopy() public override void Send(SendOrPostCallback d, object state) { - if (d == null) - { - throw new ArgumentNullException("d"); - } - _scheduler.Send(d, state); } public override void Post(SendOrPostCallback d, object state) { - if (d == null) - { - throw new ArgumentNullException("d"); - } - _scheduler.Post(d, state); } } @@ -323,13 +352,21 @@ private sealed class AsyncRootBehaviour : MonoBehaviour private WaitForEndOfFrame _eof; private SynchronizationContext _context; - private int _mainThreadId; + private SynchronizationContext _mainThreadContext; private Queue _actionQueue; #endregion #region interface + public SynchronizationContext MainThreadContext + { + get + { + return _mainThreadContext; + } + } + public IAsyncUpdateSource UpdateSource { get @@ -397,12 +434,17 @@ public void AddCompletionCallback(object op, Action cb) public void Send(SendOrPostCallback d, object state) { + if (d == null) + { + throw new ArgumentNullException("d"); + } + if (!this) { throw new ObjectDisposedException(GetType().Name); } - if (_mainThreadId == Thread.CurrentThread.ManagedThreadId) + if (_mainThreadContext == SynchronizationContext.Current) { d.Invoke(state); } @@ -420,8 +462,13 @@ public void Send(SendOrPostCallback d, object state) } } - public void Post(SendOrPostCallback d, object state) + public IAsyncOperation Post(SendOrPostCallback d, object state) { + if (d == null) + { + throw new ArgumentNullException("d"); + } + if (!this) { throw new ObjectDisposedException(GetType().Name); @@ -433,6 +480,38 @@ public void Post(SendOrPostCallback d, object state) { _actionQueue.Enqueue(asyncResult); } + + return asyncResult; + } + + public IAsyncOperation Invoke(SendOrPostCallback d, object state) + { + if (d == null) + { + throw new ArgumentNullException("d"); + } + + if (!this) + { + throw new ObjectDisposedException(GetType().Name); + } + + if (_mainThreadContext == SynchronizationContext.Current) + { + d.Invoke(state); + return AsyncResult.CompletedOperation; + } + else + { + var asyncResult = new InvokeResult(d, state); + + lock (_actionQueue) + { + _actionQueue.Enqueue(asyncResult); + } + + return asyncResult; + } } #endregion @@ -448,9 +527,13 @@ private void Awake() var context = new MainThreadSynchronizationContext(this); SynchronizationContext.SetSynchronizationContext(context); _context = context; + _mainThreadContext = context; + } + else + { + _mainThreadContext = currentContext; } - _mainThreadId = Thread.CurrentThread.ManagedThreadId; _actionQueue = new Queue(); } @@ -582,6 +665,7 @@ private void OnDestroy() _actionQueue.Clear(); } + _mainThreadContext = null; _context = null; } From e515f147d2c4064b94c9a6889709a85e895601af Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 7 Jun 2018 15:15:43 +0300 Subject: [PATCH 58/67] Added more comments --- .../UnityFx.Async/Scripts/AsyncUtility.cs | 43 ++++++++++++++++++- 1 file changed, 41 insertions(+), 2 deletions(-) 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 b1d64f7..cd02663 100644 --- a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncUtility.cs +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncUtility.cs @@ -57,6 +57,9 @@ public static void Initialize() /// /// Returns an instance of an for Update. /// + /// + /// + /// public static IAsyncUpdateSource GetUpdateSource() { return GetRootBehaviour().UpdateSource; @@ -65,6 +68,9 @@ public static IAsyncUpdateSource GetUpdateSource() /// /// Returns an instance of an for LateUpdate. /// + /// + /// + /// public static IAsyncUpdateSource GetLateUpdateSource() { return GetRootBehaviour().LateUpdateSource; @@ -73,6 +79,9 @@ public static IAsyncUpdateSource GetLateUpdateSource() /// /// Returns an instance of an for FixedUpdate. /// + /// + /// + /// public static IAsyncUpdateSource GetFixedUpdateSource() { return GetRootBehaviour().FixedUpdateSource; @@ -81,6 +90,9 @@ public static IAsyncUpdateSource GetFixedUpdateSource() /// /// Returns an instance of an for end of frame. /// + /// + /// + /// public static IAsyncUpdateSource GetEndOfFrameUpdateSource() { return GetRootBehaviour().EofUpdateSource; @@ -131,6 +143,8 @@ public static IAsyncOperation InvokeOnMainThread(SendOrPostCallback d, object st /// 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 IAsyncOperation Delay(int millisecondsDelay) { return AsyncResult.Delay(millisecondsDelay, GetRootBehaviour().UpdateSource); @@ -142,6 +156,8 @@ public static IAsyncOperation Delay(int millisecondsDelay) /// The number of seconds 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 IAsyncOperation Delay(float secondsDelay) { return AsyncResult.Delay(secondsDelay, GetRootBehaviour().UpdateSource); @@ -153,6 +169,8 @@ public static IAsyncOperation Delay(float secondsDelay) /// 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 IAsyncOperation Delay(TimeSpan delay) { return AsyncResult.Delay(delay, GetRootBehaviour().UpdateSource); @@ -162,6 +180,11 @@ public static IAsyncOperation Delay(TimeSpan delay) /// Starts a coroutine. /// /// The coroutine to run. + /// Returns the coroutine handle. + /// Thrown if is . + /// + /// + /// public static Coroutine StartCoroutine(IEnumerator enumerator) { if (enumerator == null) @@ -175,7 +198,10 @@ public static Coroutine StartCoroutine(IEnumerator enumerator) /// /// Stops the specified coroutine. /// - /// The coroutine to run. + /// The coroutine to stop. + /// + /// + /// public static void StopCoroutine(Coroutine coroutine) { if (coroutine != null) @@ -192,7 +218,10 @@ public static void StopCoroutine(Coroutine coroutine) /// /// Stops the specified coroutine. /// - /// The coroutine to run. + /// The coroutine to stop. + /// + /// + /// public static void StopCoroutine(IEnumerator enumerator) { if (enumerator != null) @@ -209,6 +238,9 @@ public static void StopCoroutine(IEnumerator enumerator) /// /// Stops all coroutines. /// + /// + /// + /// public static void StopAllCoroutines() { var runner = TryGetRootBehaviour(); @@ -224,6 +256,8 @@ public static void StopAllCoroutines() /// /// The request to register completion callback for. /// A delegate to be called when the has completed. + /// Thrown if or is . + /// public static void AddCompletionCallback(AsyncOperation op, Action completionCallback) { if (op == null) @@ -246,6 +280,9 @@ public static void AddCompletionCallback(AsyncOperation op, Action completionCal /// /// The request to register completion callback for. /// A delegate to be called when the has completed. + /// Thrown if or is . + /// + /// public static void AddCompletionCallback(UnityWebRequest request, Action completionCallback) { if (request == null) @@ -268,6 +305,8 @@ public static void AddCompletionCallback(UnityWebRequest request, Action complet /// /// The request to register completion callback for. /// A delegate to be called when the has completed. + /// Thrown if or is . + /// internal static void AddCompletionCallback(WWW request, Action completionCallback) { if (request == null) From 851cd992b70a5d219ea6b406ea20ea24e7037d95 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Thu, 7 Jun 2018 15:20:01 +0300 Subject: [PATCH 59/67] README update --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2e5ea81..febdb93 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. +- Extensibility. The library entities are designed to be easily extensible. - 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)-specific features and compatibility. This includes possibility to yield operations in coroutines, net35-compilance, Unity asynchronous operation extensions etc. +- [Unity3d](https://unity3d.com)-specific features and compatibility. This includes possibility to yield operations in coroutines, net35-compilance, extensions of Unity asynchronous operations etc. The table below summarizes differences berween *UnityFx.Async* and other popular asynchronous operation frameworks: From cccf3104f2ab1f6908c1fde0a3c1fedc4bc938ff Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 8 Jun 2018 15:45:20 +0300 Subject: [PATCH 60/67] AsyncUtility.InvokeOnMainThread implementation fixes --- .../UnityFx.Async/Scripts/AsyncUtility.cs | 22 +---- .../Api/Core/AsyncResult.Helpers.cs | 86 +++++++++++++++++++ 2 files changed, 88 insertions(+), 20 deletions(-) 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 cd02663..7e5a914 100644 --- a/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncUtility.cs +++ b/src/UnityFx.Async.AssetStore/Assets/UnityFx.Async/Scripts/AsyncUtility.cs @@ -525,31 +525,13 @@ public IAsyncOperation Post(SendOrPostCallback d, object state) public IAsyncOperation Invoke(SendOrPostCallback d, object state) { - if (d == null) - { - throw new ArgumentNullException("d"); - } - - if (!this) - { - throw new ObjectDisposedException(GetType().Name); - } - if (_mainThreadContext == SynchronizationContext.Current) { - d.Invoke(state); - return AsyncResult.CompletedOperation; + return AsyncResult.FromAction(d, state); } else { - var asyncResult = new InvokeResult(d, state); - - lock (_actionQueue) - { - _actionQueue.Enqueue(asyncResult); - } - - return asyncResult; + return Post(d, state); } } diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs index 009b2da..db67f45 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs @@ -270,6 +270,8 @@ public static AsyncResult FromResult(T result, object asyncState) /// The delegate to execute. /// Thrown if is . /// A completed operation that represents result. + /// + /// /// public static AsyncResult FromAction(Action action) { @@ -289,6 +291,62 @@ public static AsyncResult FromAction(Action action) } } + /// + /// Creates a completed that represents result of the specified. + /// + /// The delegate to execute. + /// User-defained state to pass to the . + /// Thrown if is . + /// A completed operation that represents result. + /// + /// + /// + public static AsyncResult FromAction(Action action, object state) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + try + { + action(state); + return CompletedOperation; + } + catch (Exception e) + { + return new AsyncResult(e, state); + } + } + + /// + /// Creates a completed that represents result of the specified. + /// + /// The delegate to execute. + /// User-defained state to pass to the . + /// Thrown if is . + /// A completed operation that represents result. + /// + /// + /// + public static AsyncResult FromAction(SendOrPostCallback callback, object state) + { + if (callback == null) + { + throw new ArgumentNullException(nameof(callback)); + } + + try + { + callback(state); + return CompletedOperation; + } + catch (Exception e) + { + return new AsyncResult(e, state); + } + } + /// /// Creates a completed that represents result of the specified. /// @@ -296,6 +354,7 @@ public static AsyncResult FromAction(Action action) /// Thrown if is . /// A completed operation that represents result. /// + /// public static AsyncResult FromAction(Func action) { if (action == null) @@ -314,6 +373,33 @@ public static AsyncResult FromAction(Func action) } } + /// + /// Creates a completed that represents result of the specified. + /// + /// The delegate to execute. + /// User-defained state to pass to the . + /// Thrown if is . + /// A completed operation that represents result. + /// + /// + public static AsyncResult FromAction(Func action, object state) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + try + { + var result = action(state); + return new AsyncResult(result, state); + } + catch (Exception e) + { + return new AsyncResult(e, state); + } + } + #if !NET35 /// From c1c9134b357480793aba4cff5ace01af701ca8ae Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 8 Jun 2018 16:02:14 +0300 Subject: [PATCH 61/67] CHANGELOG update --- CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bc8ced..655d6ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/); this proj ### Added - Added push-based progress reporting support. - Added `AsyncResult.Delay(float)` overloads. +- Added `AsyncCreationOptions.SuppressCancellation` option. - Added update sources for `LateUpdate`, `FixedUpdate` and end-of-frame updates. -- Added default Unity `SynchronizationContext` implementation. +- Added `SynchronizationContext` for the main thread (if not set by Unity). +- Added methods `AsyncUtility.PostToMainThread`, `AsyncUtility.SendToMainThread` as `AsyncUtility.InvokeOnMainThread`. ### Changed - Significantly reduced number of memory allocations when adding continuations. From c07083658c50343ab4f53a6c909b16a5c0640d46 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 8 Jun 2018 16:13:38 +0300 Subject: [PATCH 62/67] Copy CHANGELOG.md into an AssetStore package on build --- src/Build.ps1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Build.ps1 b/src/Build.ps1 index 0b9ff19..cabf1b1 100644 --- a/src/Build.ps1 +++ b/src/Build.ps1 @@ -68,14 +68,17 @@ function _PublishAssetStorePackage { param([string]$targetFramework) + $changelogPath = (Join-Path $scriptPath "..\CHANGELOG.md") $filesToPublish = (Join-Path $scriptPath "UnityFx.Async.AssetStore\Assets\*") $binToPublish =(Join-Path $binPath (Join-Path $targetFramework "\*")) $publishPath = (Join-Path $assetStorePath (Join-Path $targetFramework "Assets")) + $publishPath2 = (Join-Path $publishPath "UnityFx.Async") $publishBinPath = (Join-Path $publishPath "UnityFx.Async\Bin") New-Item $publishBinPath -ItemType Directory Copy-Item -Path $filesToPublish -Destination $publishPath -Force -Recurse Copy-Item -Path $binToPublish -Destination $publishBinPath -Force -Recurse + Copy-Item -Path $changelogPath -Destination $publishPath2 -Force } From 2979712af5d0b7bfbd85f8afbb2e3365568f0c77 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 8 Jun 2018 16:27:25 +0300 Subject: [PATCH 63/67] README update --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index febdb93..244ffb0 100644 --- a/README.md +++ b/README.md @@ -9,14 +9,14 @@ Unity Asset Store | [![Asynchronous operations for Unity](https://img.shields.io ## Synopsis -*UnityFx.Async* is a set of classes and interfaces that extend [Unity3d](https://unity3d.com) asynchronous operations and can be used very much like [Tasks](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) in .NET or [Promises](https://developers.google.com/web/fundamentals/primers/promises) in JS. 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* introduces effective and portable asynchronous operations that can be used very much like [Tasks](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) in .NET or [Promises](https://developers.google.com/web/fundamentals/primers/promises) in JS. [AsyncResult](https://arvtesh.github.io/UnityFx.Async/api/netstandard2.0/UnityFx.Async.AsyncResult.html) class is an implementation of a generic asynchronous operation (aka `promise` or `future`). In many aspects it mimics [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task) (for example, it can be used with `async`/`await` operators, supports continuations and synchronization context capturing) while maintaining Unity/net35 compatibility. It is a great foundation toolset for any Unity project. 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 entities are designed to be easily extensible. - 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)-specific features and compatibility. This includes possibility to yield operations in coroutines, net35-compilance, extensions of Unity asynchronous operations etc. +- [Unity3d](https://unity3d.com)-specific features and compatibility. This includes possibility to yield operations in coroutines, `net35`-compilance, extensions of Unity asynchronous operations etc. The table below summarizes differences berween *UnityFx.Async* and other popular asynchronous operation frameworks: From d61bbd2d081b4496e1d3a33dc7ceac9c5a4e33bf Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 8 Jun 2018 20:15:27 +0300 Subject: [PATCH 64/67] Changed AsyncResult.Cancel() to do nothing (previously default implementation threw NotSupportedException) --- src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs | 9 ++++++--- src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs | 9 +++------ src/UnityFx.Async/Api/Core/AsyncResult.cs | 4 +--- src/UnityFx.Async/Api/Interfaces/IAsyncCancellable.cs | 1 - 4 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs b/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs index 68d4dda..4ddf08f 100644 --- a/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs +++ b/src/UnityFx.Async.Tests/Tests/AsyncResultTests.cs @@ -519,13 +519,16 @@ public void Cancel_CanBeCalledWhenDisposed() } [Fact] - public void Cancel_DefaultImplementationThrows() + public void Cancel_DefaultImplementationDoesNothing() { // Arrange var op = new AsyncResult(); - // Act/Assert - Assert.Throws(() => op.Cancel()); + // Act + op.Cancel(); + + // Assert + Assert.False(op.IsCompleted); } [Theory] diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs index db67f45..8220137 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs @@ -558,8 +558,7 @@ public static AsyncResult Delay(int millisecondsDelay) if (millisecondsDelay == Timeout.Infinite) { - // NOTE: Cannot return AsyncResult instance because its Cancel implementation throws NotSupportedException. - return new AsyncCompletionSource(AsyncOperationStatus.Running); + return new AsyncResult(AsyncOperationStatus.Running); } var result = new TimerDelayResult(millisecondsDelay); @@ -598,8 +597,7 @@ public static AsyncResult Delay(int millisecondsDelay, IAsyncUpdateSource update if (millisecondsDelay == Timeout.Infinite) { - // NOTE: Cannot return AsyncResult instance because its Cancel implementation throws NotSupportedException. - return new AsyncCompletionSource(AsyncOperationStatus.Running); + return new AsyncResult(AsyncOperationStatus.Running); } var result = new UpdatableDelayResult(millisecondsDelay / 1000f, updateSource); @@ -659,8 +657,7 @@ public static AsyncResult Delay(float secondsDelay, IAsyncUpdateSource updateSou if (secondsDelay == Timeout.Infinite) { - // NOTE: Cannot return AsyncResult instance because its Cancel implementation throws NotSupportedException. - return new AsyncCompletionSource(AsyncOperationStatus.Running); + return new AsyncResult(AsyncOperationStatus.Running); } var result = new UpdatableDelayResult(secondsDelay, updateSource); diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.cs b/src/UnityFx.Async/Api/Core/AsyncResult.cs index 750559b..cf013c4 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.cs @@ -623,12 +623,11 @@ protected virtual void OnStarted() } /// - /// Called when the operation cancellation has been requested. Default implementation throws . + /// Called when the operation cancellation has been requested. Default implementation does nothing. /// /// protected virtual void OnCancel() { - throw new NotSupportedException(); } /// @@ -971,7 +970,6 @@ public float Progress /// There is no guarantee that this call will actually cancel the operation or that the operation will be cancelled immidiately. /// can be used to suppress this method for a specific operation instance. /// - /// Thrown if cancellation is not supported by the implementation. public void Cancel() { if ((_flags & _flagSuppressCancellation) != 0) diff --git a/src/UnityFx.Async/Api/Interfaces/IAsyncCancellable.cs b/src/UnityFx.Async/Api/Interfaces/IAsyncCancellable.cs index 8069b19..4459458 100644 --- a/src/UnityFx.Async/Api/Interfaces/IAsyncCancellable.cs +++ b/src/UnityFx.Async/Api/Interfaces/IAsyncCancellable.cs @@ -15,7 +15,6 @@ public interface IAsyncCancellable /// 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 cancellation is not supported by the implementation. void Cancel(); } } From 05e4dbd56a92ad3ed694688d2bfc66b0f4c97ed1 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Fri, 8 Jun 2018 20:17:32 +0300 Subject: [PATCH 65/67] CHANGELOG update --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 655d6ae..21a827d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/); this proj ### Changed - Significantly reduced number of memory allocations when adding continuations. - Changed signature of the `IAsyncContinuation.Invoke` method. +- Changed `AsyncResult.OnCancel` implementation to do nothing (previously it threw `NotSupportedException`). ### Fixed - Fixed exception when removing listeners while in `AsyncUpdateSource.OnError` / `AsyncUpdateSource.OnCompleted` / `AsyncUpdateSource.Dispose`. From 6e473ef707bbf97d8b61c4c2e346739e83dbd053 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 9 Jun 2018 18:19:38 +0300 Subject: [PATCH 66/67] FromAction interface fixes --- src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs b/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs index 8220137..4f5ffa1 100644 --- a/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs +++ b/src/UnityFx.Async/Api/Core/AsyncResult.Helpers.cs @@ -270,7 +270,7 @@ public static AsyncResult FromResult(T result, object asyncState) /// The delegate to execute. /// Thrown if is . /// A completed operation that represents result. - /// + /// /// /// public static AsyncResult FromAction(Action action) @@ -301,7 +301,7 @@ public static AsyncResult FromAction(Action action) /// /// /// - public static AsyncResult FromAction(Action action, object state) + public static AsyncResult FromAction(Action action, T state) { if (action == null) { @@ -327,7 +327,7 @@ public static AsyncResult FromAction(Action action, object state) /// Thrown if is . /// A completed operation that represents result. /// - /// + /// /// public static AsyncResult FromAction(SendOrPostCallback callback, object state) { @@ -354,7 +354,7 @@ public static AsyncResult FromAction(SendOrPostCallback callback, object state) /// Thrown if is . /// A completed operation that represents result. /// - /// + /// public static AsyncResult FromAction(Func action) { if (action == null) @@ -380,9 +380,9 @@ public static AsyncResult FromAction(Func action) /// User-defained state to pass to the . /// Thrown if is . /// A completed operation that represents result. - /// + /// /// - public static AsyncResult FromAction(Func action, object state) + public static AsyncResult FromAction(Func action, T state) { if (action == null) { From 01829482a7485404f1045edddcc6d7b20a278566 Mon Sep 17 00:00:00 2001 From: Alexander Bogarsukov Date: Sat, 9 Jun 2018 18:21:01 +0300 Subject: [PATCH 67/67] CHANGELOG update --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21a827d..fea45d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/); this project adheres to [Semantic Versioning](http://semver.org/). ----------------------- -## [0.9.3] - unreleased +## [0.9.3] - 2018.06.09 ### Added - Added push-based progress reporting support. @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/); this proj - Added update sources for `LateUpdate`, `FixedUpdate` and end-of-frame updates. - Added `SynchronizationContext` for the main thread (if not set by Unity). - Added methods `AsyncUtility.PostToMainThread`, `AsyncUtility.SendToMainThread` as `AsyncUtility.InvokeOnMainThread`. +- Added new `FromAction` overloads. ### Changed - Significantly reduced number of memory allocations when adding continuations.