From 3f042c8886c0e2985d51e053fbc3ebeb009bb974 Mon Sep 17 00:00:00 2001 From: hadashiA Date: Thu, 26 Oct 2023 18:11:25 +0900 Subject: [PATCH 01/13] Add cancel immediate flag --- .../Linq/UnityExtensions/EveryUpdate.cs | 25 ++- .../Linq/UnityExtensions/EveryValueChanged.cs | 49 ++++-- .../Runtime/Linq/UnityExtensions/Timer.cs | 68 ++++--- .../Plugins/UniTask/Runtime/UniTask.Delay.cs | 166 +++++++++++++----- .../UniTask/Runtime/UniTask.WaitUntil.cs | 83 +++++++-- ...cExtensions.AssetBundleRequestAllAssets.cs | 23 ++- .../UnityAsyncExtensions.AsyncGPUReadback.cs | 23 ++- .../UniTask/Runtime/UnityAsyncExtensions.cs | 48 ++++- 8 files changed, 375 insertions(+), 110 deletions(-) diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryUpdate.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryUpdate.cs index 585fff9..09c5cf9 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryUpdate.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryUpdate.cs @@ -4,38 +4,50 @@ namespace Cysharp.Threading.Tasks.Linq { public static partial class UniTaskAsyncEnumerable { - public static IUniTaskAsyncEnumerable EveryUpdate(PlayerLoopTiming updateTiming = PlayerLoopTiming.Update) + public static IUniTaskAsyncEnumerable EveryUpdate(PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool cancelImmediately = false) { - return new EveryUpdate(updateTiming); + return new EveryUpdate(updateTiming, cancelImmediately); } } internal class EveryUpdate : IUniTaskAsyncEnumerable { readonly PlayerLoopTiming updateTiming; + readonly bool cancelImmediately; - public EveryUpdate(PlayerLoopTiming updateTiming) + public EveryUpdate(PlayerLoopTiming updateTiming, bool cancelImmediately) { this.updateTiming = updateTiming; + this.cancelImmediately = cancelImmediately; } public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) { - return new _EveryUpdate(updateTiming, cancellationToken); + return new _EveryUpdate(updateTiming, cancellationToken, cancelImmediately); } class _EveryUpdate : MoveNextSource, IUniTaskAsyncEnumerator, IPlayerLoopItem { readonly PlayerLoopTiming updateTiming; - CancellationToken cancellationToken; + readonly CancellationToken cancellationToken; + readonly CancellationTokenRegistration cancellationTokenRegistration; bool disposed; - public _EveryUpdate(PlayerLoopTiming updateTiming, CancellationToken cancellationToken) + public _EveryUpdate(PlayerLoopTiming updateTiming, CancellationToken cancellationToken, bool cancelImmediately) { this.updateTiming = updateTiming; this.cancellationToken = cancellationToken; + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var source = (_EveryUpdate)state; + source.completionSource.TrySetCanceled(source.cancellationToken); + }, this); + } + TaskTracker.TrackActiveTask(this, 2); PlayerLoopHelper.AddAction(updateTiming, this); } @@ -55,6 +67,7 @@ namespace Cysharp.Threading.Tasks.Linq { if (!disposed) { + cancellationTokenRegistration.Dispose(); disposed = true; TaskTracker.RemoveTracking(this); } diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryValueChanged.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryValueChanged.cs index f678e7a..8b19f64 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryValueChanged.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryValueChanged.cs @@ -7,7 +7,7 @@ namespace Cysharp.Threading.Tasks.Linq { public static partial class UniTaskAsyncEnumerable { - public static IUniTaskAsyncEnumerable EveryValueChanged(TTarget target, Func propertySelector, PlayerLoopTiming monitorTiming = PlayerLoopTiming.Update, IEqualityComparer equalityComparer = null) + public static IUniTaskAsyncEnumerable EveryValueChanged(TTarget target, Func propertySelector, PlayerLoopTiming monitorTiming = PlayerLoopTiming.Update, IEqualityComparer equalityComparer = null, bool cancelImmediately = false) where TTarget : class { var unityObject = target as UnityEngine.Object; @@ -15,11 +15,11 @@ namespace Cysharp.Threading.Tasks.Linq if (isUnityObject) { - return new EveryValueChangedUnityObject(target, propertySelector, equalityComparer ?? UnityEqualityComparer.GetDefault(), monitorTiming); + return new EveryValueChangedUnityObject(target, propertySelector, equalityComparer ?? UnityEqualityComparer.GetDefault(), monitorTiming, cancelImmediately); } else { - return new EveryValueChangedStandardObject(target, propertySelector, equalityComparer ?? UnityEqualityComparer.GetDefault(), monitorTiming); + return new EveryValueChangedStandardObject(target, propertySelector, equalityComparer ?? UnityEqualityComparer.GetDefault(), monitorTiming, cancelImmediately); } } } @@ -30,18 +30,20 @@ namespace Cysharp.Threading.Tasks.Linq readonly Func propertySelector; readonly IEqualityComparer equalityComparer; readonly PlayerLoopTiming monitorTiming; + readonly bool cancelImmediately; - public EveryValueChangedUnityObject(TTarget target, Func propertySelector, IEqualityComparer equalityComparer, PlayerLoopTiming monitorTiming) + public EveryValueChangedUnityObject(TTarget target, Func propertySelector, IEqualityComparer equalityComparer, PlayerLoopTiming monitorTiming, bool cancelImmediately) { this.target = target; this.propertySelector = propertySelector; this.equalityComparer = equalityComparer; this.monitorTiming = monitorTiming; + this.cancelImmediately = cancelImmediately; } public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) { - return new _EveryValueChanged(target, propertySelector, equalityComparer, monitorTiming, cancellationToken); + return new _EveryValueChanged(target, propertySelector, equalityComparer, monitorTiming, cancellationToken, cancelImmediately); } sealed class _EveryValueChanged : MoveNextSource, IUniTaskAsyncEnumerator, IPlayerLoopItem @@ -50,13 +52,14 @@ namespace Cysharp.Threading.Tasks.Linq readonly UnityEngine.Object targetAsUnityObject; readonly IEqualityComparer equalityComparer; readonly Func propertySelector; - CancellationToken cancellationToken; + readonly CancellationToken cancellationToken; + readonly CancellationTokenRegistration cancellationTokenRegistration; bool first; TProperty currentValue; bool disposed; - public _EveryValueChanged(TTarget target, Func propertySelector, IEqualityComparer equalityComparer, PlayerLoopTiming monitorTiming, CancellationToken cancellationToken) + public _EveryValueChanged(TTarget target, Func propertySelector, IEqualityComparer equalityComparer, PlayerLoopTiming monitorTiming, CancellationToken cancellationToken, bool cancelImmediately) { this.target = target; this.targetAsUnityObject = target as UnityEngine.Object; @@ -64,6 +67,16 @@ namespace Cysharp.Threading.Tasks.Linq this.equalityComparer = equalityComparer; this.cancellationToken = cancellationToken; this.first = true; + + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var source = (_EveryValueChanged)state; + source.completionSource.TrySetCanceled(source.cancellationToken); + }, this); + } + TaskTracker.TrackActiveTask(this, 2); PlayerLoopHelper.AddAction(monitorTiming, this); } @@ -139,18 +152,20 @@ namespace Cysharp.Threading.Tasks.Linq readonly Func propertySelector; readonly IEqualityComparer equalityComparer; readonly PlayerLoopTiming monitorTiming; + readonly bool cancelImmediately; - public EveryValueChangedStandardObject(TTarget target, Func propertySelector, IEqualityComparer equalityComparer, PlayerLoopTiming monitorTiming) + public EveryValueChangedStandardObject(TTarget target, Func propertySelector, IEqualityComparer equalityComparer, PlayerLoopTiming monitorTiming, bool cancelImmediately) { this.target = new WeakReference(target, false); this.propertySelector = propertySelector; this.equalityComparer = equalityComparer; this.monitorTiming = monitorTiming; + this.cancelImmediately = cancelImmediately; } public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) { - return new _EveryValueChanged(target, propertySelector, equalityComparer, monitorTiming, cancellationToken); + return new _EveryValueChanged(target, propertySelector, equalityComparer, monitorTiming, cancellationToken, cancelImmediately); } sealed class _EveryValueChanged : MoveNextSource, IUniTaskAsyncEnumerator, IPlayerLoopItem @@ -158,19 +173,30 @@ namespace Cysharp.Threading.Tasks.Linq readonly WeakReference target; readonly IEqualityComparer equalityComparer; readonly Func propertySelector; - CancellationToken cancellationToken; + readonly CancellationToken cancellationToken; + readonly CancellationTokenRegistration cancellationTokenRegistration; bool first; TProperty currentValue; bool disposed; - public _EveryValueChanged(WeakReference target, Func propertySelector, IEqualityComparer equalityComparer, PlayerLoopTiming monitorTiming, CancellationToken cancellationToken) + public _EveryValueChanged(WeakReference target, Func propertySelector, IEqualityComparer equalityComparer, PlayerLoopTiming monitorTiming, CancellationToken cancellationToken, bool cancelImmediately) { this.target = target; this.propertySelector = propertySelector; this.equalityComparer = equalityComparer; this.cancellationToken = cancellationToken; this.first = true; + + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var source = (_EveryValueChanged)state; + source.completionSource.TrySetCanceled(source.cancellationToken); + }, this); + } + TaskTracker.TrackActiveTask(this, 2); PlayerLoopHelper.AddAction(monitorTiming, this); } @@ -200,6 +226,7 @@ namespace Cysharp.Threading.Tasks.Linq { if (!disposed) { + cancellationTokenRegistration.Dispose(); disposed = true; TaskTracker.RemoveTracking(this); } diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/Timer.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/Timer.cs index 53ecfcd..a3bab3c 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/Timer.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/Timer.cs @@ -6,32 +6,32 @@ namespace Cysharp.Threading.Tasks.Linq { public static partial class UniTaskAsyncEnumerable { - public static IUniTaskAsyncEnumerable Timer(TimeSpan dueTime, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool ignoreTimeScale = false) + public static IUniTaskAsyncEnumerable Timer(TimeSpan dueTime, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool ignoreTimeScale = false, bool cancelImmediately = false) { - return new Timer(dueTime, null, updateTiming, ignoreTimeScale); + return new Timer(dueTime, null, updateTiming, ignoreTimeScale, cancelImmediately); } - public static IUniTaskAsyncEnumerable Timer(TimeSpan dueTime, TimeSpan period, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool ignoreTimeScale = false) + public static IUniTaskAsyncEnumerable Timer(TimeSpan dueTime, TimeSpan period, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool ignoreTimeScale = false, bool cancelImmediately = false) { - return new Timer(dueTime, period, updateTiming, ignoreTimeScale); + return new Timer(dueTime, period, updateTiming, ignoreTimeScale, cancelImmediately); } - public static IUniTaskAsyncEnumerable Interval(TimeSpan period, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool ignoreTimeScale = false) + public static IUniTaskAsyncEnumerable Interval(TimeSpan period, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool ignoreTimeScale = false, bool cancelImmediately = false) { - return new Timer(period, period, updateTiming, ignoreTimeScale); + return new Timer(period, period, updateTiming, ignoreTimeScale, cancelImmediately); } - public static IUniTaskAsyncEnumerable TimerFrame(int dueTimeFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update) + public static IUniTaskAsyncEnumerable TimerFrame(int dueTimeFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool cancelImmediately = false) { if (dueTimeFrameCount < 0) { throw new ArgumentOutOfRangeException("Delay does not allow minus delayFrameCount. dueTimeFrameCount:" + dueTimeFrameCount); } - return new TimerFrame(dueTimeFrameCount, null, updateTiming); + return new TimerFrame(dueTimeFrameCount, null, updateTiming, cancelImmediately); } - public static IUniTaskAsyncEnumerable TimerFrame(int dueTimeFrameCount, int periodFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update) + public static IUniTaskAsyncEnumerable TimerFrame(int dueTimeFrameCount, int periodFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool cancelImmediately = false) { if (dueTimeFrameCount < 0) { @@ -42,16 +42,16 @@ namespace Cysharp.Threading.Tasks.Linq throw new ArgumentOutOfRangeException("Delay does not allow minus periodFrameCount. periodFrameCount:" + dueTimeFrameCount); } - return new TimerFrame(dueTimeFrameCount, periodFrameCount, updateTiming); + return new TimerFrame(dueTimeFrameCount, periodFrameCount, updateTiming, cancelImmediately); } - public static IUniTaskAsyncEnumerable IntervalFrame(int intervalFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update) + public static IUniTaskAsyncEnumerable IntervalFrame(int intervalFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool cancelImmediately = false) { if (intervalFrameCount < 0) { throw new ArgumentOutOfRangeException("Delay does not allow minus intervalFrameCount. intervalFrameCount:" + intervalFrameCount); } - return new TimerFrame(intervalFrameCount, intervalFrameCount, updateTiming); + return new TimerFrame(intervalFrameCount, intervalFrameCount, updateTiming, cancelImmediately); } } @@ -61,18 +61,20 @@ namespace Cysharp.Threading.Tasks.Linq readonly TimeSpan dueTime; readonly TimeSpan? period; readonly bool ignoreTimeScale; + readonly bool cancelImmediately; - public Timer(TimeSpan dueTime, TimeSpan? period, PlayerLoopTiming updateTiming, bool ignoreTimeScale) + public Timer(TimeSpan dueTime, TimeSpan? period, PlayerLoopTiming updateTiming, bool ignoreTimeScale, bool cancelImmediately) { this.updateTiming = updateTiming; this.dueTime = dueTime; this.period = period; this.ignoreTimeScale = ignoreTimeScale; + this.cancelImmediately = cancelImmediately; } public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) { - return new _Timer(dueTime, period, updateTiming, ignoreTimeScale, cancellationToken); + return new _Timer(dueTime, period, updateTiming, ignoreTimeScale, cancellationToken, cancelImmediately); } class _Timer : MoveNextSource, IUniTaskAsyncEnumerator, IPlayerLoopItem @@ -81,7 +83,8 @@ namespace Cysharp.Threading.Tasks.Linq readonly float? period; readonly PlayerLoopTiming updateTiming; readonly bool ignoreTimeScale; - CancellationToken cancellationToken; + readonly CancellationToken cancellationToken; + readonly CancellationTokenRegistration cancellationTokenRegistration; int initialFrame; float elapsed; @@ -89,7 +92,7 @@ namespace Cysharp.Threading.Tasks.Linq bool completed; bool disposed; - public _Timer(TimeSpan dueTime, TimeSpan? period, PlayerLoopTiming updateTiming, bool ignoreTimeScale, CancellationToken cancellationToken) + public _Timer(TimeSpan dueTime, TimeSpan? period, PlayerLoopTiming updateTiming, bool ignoreTimeScale, CancellationToken cancellationToken, bool cancelImmediately) { this.dueTime = (float)dueTime.TotalSeconds; this.period = (period == null) ? null : (float?)period.Value.TotalSeconds; @@ -105,6 +108,17 @@ namespace Cysharp.Threading.Tasks.Linq this.updateTiming = updateTiming; this.ignoreTimeScale = ignoreTimeScale; this.cancellationToken = cancellationToken; + + + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var source = (_Timer)state; + source.completionSource.TrySetCanceled(source.cancellationToken); + }, this); + } + TaskTracker.TrackActiveTask(this, 2); PlayerLoopHelper.AddAction(updateTiming, this); } @@ -127,6 +141,7 @@ namespace Cysharp.Threading.Tasks.Linq { if (!disposed) { + cancellationTokenRegistration.Dispose(); disposed = true; TaskTracker.RemoveTracking(this); } @@ -187,24 +202,27 @@ namespace Cysharp.Threading.Tasks.Linq readonly PlayerLoopTiming updateTiming; readonly int dueTimeFrameCount; readonly int? periodFrameCount; + readonly bool cancelImmediately; - public TimerFrame(int dueTimeFrameCount, int? periodFrameCount, PlayerLoopTiming updateTiming) + public TimerFrame(int dueTimeFrameCount, int? periodFrameCount, PlayerLoopTiming updateTiming, bool cancelImmediately) { this.updateTiming = updateTiming; this.dueTimeFrameCount = dueTimeFrameCount; this.periodFrameCount = periodFrameCount; + this.cancelImmediately = cancelImmediately; } public IUniTaskAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default) { - return new _TimerFrame(dueTimeFrameCount, periodFrameCount, updateTiming, cancellationToken); + return new _TimerFrame(dueTimeFrameCount, periodFrameCount, updateTiming, cancellationToken, cancelImmediately); } class _TimerFrame : MoveNextSource, IUniTaskAsyncEnumerator, IPlayerLoopItem { readonly int dueTimeFrameCount; readonly int? periodFrameCount; - CancellationToken cancellationToken; + readonly CancellationToken cancellationToken; + readonly CancellationTokenRegistration cancellationTokenRegistration; int initialFrame; int currentFrame; @@ -212,7 +230,7 @@ namespace Cysharp.Threading.Tasks.Linq bool completed; bool disposed; - public _TimerFrame(int dueTimeFrameCount, int? periodFrameCount, PlayerLoopTiming updateTiming, CancellationToken cancellationToken) + public _TimerFrame(int dueTimeFrameCount, int? periodFrameCount, PlayerLoopTiming updateTiming, CancellationToken cancellationToken, bool cancelImmediately) { if (dueTimeFrameCount <= 0) dueTimeFrameCount = 0; if (periodFrameCount != null) @@ -225,6 +243,15 @@ namespace Cysharp.Threading.Tasks.Linq this.dueTimeFrameCount = dueTimeFrameCount; this.periodFrameCount = periodFrameCount; this.cancellationToken = cancellationToken; + + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var source = (_TimerFrame)state; + source.completionSource.TrySetCanceled(source.cancellationToken); + }, this); + } TaskTracker.TrackActiveTask(this, 2); PlayerLoopHelper.AddAction(updateTiming, this); @@ -249,6 +276,7 @@ namespace Cysharp.Threading.Tasks.Linq { if (!disposed) { + cancellationTokenRegistration.Dispose(); disposed = true; TaskTracker.RemoveTracking(this); } diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Delay.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Delay.cs index fadd63c..7f02a1a 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Delay.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.Delay.cs @@ -33,14 +33,14 @@ namespace Cysharp.Threading.Tasks return new YieldAwaitable(timing); } - public static UniTask Yield(CancellationToken cancellationToken) + public static UniTask Yield(CancellationToken cancellationToken, bool cancelImmediately = false) { - return new UniTask(YieldPromise.Create(PlayerLoopTiming.Update, cancellationToken, out var token), token); + return new UniTask(YieldPromise.Create(PlayerLoopTiming.Update, cancellationToken, cancelImmediately, out var token), token); } - public static UniTask Yield(PlayerLoopTiming timing, CancellationToken cancellationToken) + public static UniTask Yield(PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately = false) { - return new UniTask(YieldPromise.Create(timing, cancellationToken, out var token), token); + return new UniTask(YieldPromise.Create(timing, cancellationToken, cancelImmediately, out var token), token); } /// @@ -48,7 +48,7 @@ namespace Cysharp.Threading.Tasks /// public static UniTask NextFrame() { - return new UniTask(NextFramePromise.Create(PlayerLoopTiming.Update, CancellationToken.None, out var token), token); + return new UniTask(NextFramePromise.Create(PlayerLoopTiming.Update, CancellationToken.None, false, out var token), token); } /// @@ -56,23 +56,23 @@ namespace Cysharp.Threading.Tasks /// public static UniTask NextFrame(PlayerLoopTiming timing) { - return new UniTask(NextFramePromise.Create(timing, CancellationToken.None, out var token), token); + return new UniTask(NextFramePromise.Create(timing, CancellationToken.None, false, out var token), token); } /// /// Similar as UniTask.Yield but guaranteed run on next frame. /// - public static UniTask NextFrame(CancellationToken cancellationToken) + public static UniTask NextFrame(CancellationToken cancellationToken, bool cancelImmediately = false) { - return new UniTask(NextFramePromise.Create(PlayerLoopTiming.Update, cancellationToken, out var token), token); + return new UniTask(NextFramePromise.Create(PlayerLoopTiming.Update, cancellationToken, cancelImmediately, out var token), token); } /// /// Similar as UniTask.Yield but guaranteed run on next frame. /// - public static UniTask NextFrame(PlayerLoopTiming timing, CancellationToken cancellationToken) + public static UniTask NextFrame(PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately = false) { - return new UniTask(NextFramePromise.Create(timing, cancellationToken, out var token), token); + return new UniTask(NextFramePromise.Create(timing, cancellationToken, cancelImmediately, out var token), token); } #if UNITY_2023_1_OR_NEWER @@ -88,15 +88,21 @@ namespace Cysharp.Threading.Tasks } [Obsolete("Use WaitForEndOfFrame(MonoBehaviour) instead or UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate). Equivalent for coroutine's WaitForEndOfFrame requires MonoBehaviour(runner of Coroutine).")] - public static UniTask WaitForEndOfFrame(CancellationToken cancellationToken) + public static UniTask WaitForEndOfFrame(CancellationToken cancellationToken, bool cancelImmediately = false) { - return UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate, cancellationToken); + return UniTask.Yield(PlayerLoopTiming.LastPostLateUpdate, cancellationToken, cancelImmediately); } #endif - public static UniTask WaitForEndOfFrame(MonoBehaviour coroutineRunner, CancellationToken cancellationToken = default) + public static UniTask WaitForEndOfFrame(MonoBehaviour coroutineRunner) { - var source = WaitForEndOfFramePromise.Create(coroutineRunner, cancellationToken, out var token); + var source = WaitForEndOfFramePromise.Create(coroutineRunner, CancellationToken.None, false, out var token); + return new UniTask(source, token); + } + + public static UniTask WaitForEndOfFrame(MonoBehaviour coroutineRunner, CancellationToken cancellationToken, bool cancelImmediately = false) + { + var source = WaitForEndOfFramePromise.Create(coroutineRunner, cancellationToken, cancelImmediately, out var token); return new UniTask(source, token); } @@ -113,50 +119,50 @@ namespace Cysharp.Threading.Tasks /// /// Same as UniTask.Yield(PlayerLoopTiming.LastFixedUpdate, cancellationToken). /// - public static UniTask WaitForFixedUpdate(CancellationToken cancellationToken) + public static UniTask WaitForFixedUpdate(CancellationToken cancellationToken, bool cancelImmediately = false) { - return UniTask.Yield(PlayerLoopTiming.LastFixedUpdate, cancellationToken); + return UniTask.Yield(PlayerLoopTiming.LastFixedUpdate, cancellationToken, cancelImmediately); } - public static UniTask WaitForSeconds(float duration, bool ignoreTimeScale = false, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask WaitForSeconds(float duration, bool ignoreTimeScale = false, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { - return Delay(Mathf.RoundToInt(1000 * duration), ignoreTimeScale, delayTiming, cancellationToken); + return Delay(Mathf.RoundToInt(1000 * duration), ignoreTimeScale, delayTiming, cancellationToken, cancelImmediately); } - public static UniTask WaitForSeconds(int duration, bool ignoreTimeScale = false, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask WaitForSeconds(int duration, bool ignoreTimeScale = false, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { - return Delay(1000 * duration, ignoreTimeScale, delayTiming, cancellationToken); + return Delay(1000 * duration, ignoreTimeScale, delayTiming, cancellationToken, cancelImmediately); } - public static UniTask DelayFrame(int delayFrameCount, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask DelayFrame(int delayFrameCount, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { if (delayFrameCount < 0) { throw new ArgumentOutOfRangeException("Delay does not allow minus delayFrameCount. delayFrameCount:" + delayFrameCount); } - return new UniTask(DelayFramePromise.Create(delayFrameCount, delayTiming, cancellationToken, out var token), token); + return new UniTask(DelayFramePromise.Create(delayFrameCount, delayTiming, cancellationToken, cancelImmediately, out var token), token); } - public static UniTask Delay(int millisecondsDelay, bool ignoreTimeScale = false, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask Delay(int millisecondsDelay, bool ignoreTimeScale = false, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { var delayTimeSpan = TimeSpan.FromMilliseconds(millisecondsDelay); - return Delay(delayTimeSpan, ignoreTimeScale, delayTiming, cancellationToken); + return Delay(delayTimeSpan, ignoreTimeScale, delayTiming, cancellationToken, cancelImmediately); } - public static UniTask Delay(TimeSpan delayTimeSpan, bool ignoreTimeScale = false, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask Delay(TimeSpan delayTimeSpan, bool ignoreTimeScale = false, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { var delayType = ignoreTimeScale ? DelayType.UnscaledDeltaTime : DelayType.DeltaTime; - return Delay(delayTimeSpan, delayType, delayTiming, cancellationToken); + return Delay(delayTimeSpan, delayType, delayTiming, cancellationToken, cancelImmediately); } - public static UniTask Delay(int millisecondsDelay, DelayType delayType, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask Delay(int millisecondsDelay, DelayType delayType, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { var delayTimeSpan = TimeSpan.FromMilliseconds(millisecondsDelay); - return Delay(delayTimeSpan, delayType, delayTiming, cancellationToken); + return Delay(delayTimeSpan, delayType, delayTiming, cancellationToken, cancelImmediately); } - public static UniTask Delay(TimeSpan delayTimeSpan, DelayType delayType, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask Delay(TimeSpan delayTimeSpan, DelayType delayType, PlayerLoopTiming delayTiming = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { if (delayTimeSpan < TimeSpan.Zero) { @@ -175,16 +181,16 @@ namespace Cysharp.Threading.Tasks { case DelayType.UnscaledDeltaTime: { - return new UniTask(DelayIgnoreTimeScalePromise.Create(delayTimeSpan, delayTiming, cancellationToken, out var token), token); + return new UniTask(DelayIgnoreTimeScalePromise.Create(delayTimeSpan, delayTiming, cancellationToken, cancelImmediately, out var token), token); } case DelayType.Realtime: { - return new UniTask(DelayRealtimePromise.Create(delayTimeSpan, delayTiming, cancellationToken, out var token), token); + return new UniTask(DelayRealtimePromise.Create(delayTimeSpan, delayTiming, cancellationToken, cancelImmediately, out var token), token); } case DelayType.DeltaTime: default: { - return new UniTask(DelayPromise.Create(delayTimeSpan, delayTiming, cancellationToken, out var token), token); + return new UniTask(DelayPromise.Create(delayTimeSpan, delayTiming, cancellationToken, cancelImmediately, out var token), token); } } } @@ -201,13 +207,14 @@ namespace Cysharp.Threading.Tasks } CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; UniTaskCompletionSourceCore core; YieldPromise() { } - public static IUniTaskSource Create(PlayerLoopTiming timing, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -219,8 +226,16 @@ namespace Cysharp.Threading.Tasks result = new YieldPromise(); } - result.cancellationToken = cancellationToken; + + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var promise = (YieldPromise)state; + promise.core.TrySetCanceled(promise.cancellationToken); + }, result); + } TaskTracker.TrackActiveTask(result, 3); @@ -274,6 +289,7 @@ namespace Cysharp.Threading.Tasks TaskTracker.RemoveTracking(this); core.Reset(); cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } @@ -290,14 +306,15 @@ namespace Cysharp.Threading.Tasks } int frameCount; - CancellationToken cancellationToken; UniTaskCompletionSourceCore core; + CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; NextFramePromise() { } - public static IUniTaskSource Create(PlayerLoopTiming timing, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -312,6 +329,15 @@ namespace Cysharp.Threading.Tasks result.frameCount = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1; result.cancellationToken = cancellationToken; + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var promise = (NextFramePromise)state; + promise.core.TrySetCanceled(promise.cancellationToken); + }, result); + } + TaskTracker.TrackActiveTask(result, 3); PlayerLoopHelper.AddAction(timing, result); @@ -369,6 +395,7 @@ namespace Cysharp.Threading.Tasks TaskTracker.RemoveTracking(this); core.Reset(); cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } @@ -384,14 +411,15 @@ namespace Cysharp.Threading.Tasks TaskPool.RegisterSizeGetter(typeof(WaitForEndOfFramePromise), () => pool.Size); } - CancellationToken cancellationToken; UniTaskCompletionSourceCore core; + CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; WaitForEndOfFramePromise() { } - public static IUniTaskSource Create(MonoBehaviour coroutineRunner, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(MonoBehaviour coroutineRunner, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -405,6 +433,15 @@ namespace Cysharp.Threading.Tasks result.cancellationToken = cancellationToken; + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var promise = (WaitForEndOfFramePromise)state; + promise.core.TrySetCanceled(promise.cancellationToken); + }, result); + } + TaskTracker.TrackActiveTask(result, 3); coroutineRunner.StartCoroutine(result); @@ -446,6 +483,7 @@ namespace Cysharp.Threading.Tasks core.Reset(); Reset(); // Reset Enumerator cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } @@ -494,6 +532,7 @@ namespace Cysharp.Threading.Tasks int initialFrame; int delayFrameCount; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; int currentFrameCount; UniTaskCompletionSourceCore core; @@ -502,7 +541,7 @@ namespace Cysharp.Threading.Tasks { } - public static IUniTaskSource Create(int delayFrameCount, PlayerLoopTiming timing, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(int delayFrameCount, PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -518,6 +557,15 @@ namespace Cysharp.Threading.Tasks result.cancellationToken = cancellationToken; result.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1; + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var promise = (DelayFramePromise)state; + promise.core.TrySetCanceled(promise.cancellationToken); + }, result); + } + TaskTracker.TrackActiveTask(result, 3); PlayerLoopHelper.AddAction(timing, result); @@ -604,6 +652,7 @@ namespace Cysharp.Threading.Tasks currentFrameCount = default; delayFrameCount = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } @@ -623,6 +672,7 @@ namespace Cysharp.Threading.Tasks float delayTimeSpan; float elapsed; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; UniTaskCompletionSourceCore core; @@ -630,7 +680,7 @@ namespace Cysharp.Threading.Tasks { } - public static IUniTaskSource Create(TimeSpan delayTimeSpan, PlayerLoopTiming timing, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(TimeSpan delayTimeSpan, PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -647,6 +697,15 @@ namespace Cysharp.Threading.Tasks result.cancellationToken = cancellationToken; result.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1; + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var promise = (DelayPromise)state; + promise.core.TrySetCanceled(promise.cancellationToken); + }, result); + } + TaskTracker.TrackActiveTask(result, 3); PlayerLoopHelper.AddAction(timing, result); @@ -715,6 +774,7 @@ namespace Cysharp.Threading.Tasks delayTimeSpan = default; elapsed = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } @@ -734,6 +794,7 @@ namespace Cysharp.Threading.Tasks float elapsed; int initialFrame; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; UniTaskCompletionSourceCore core; @@ -741,7 +802,7 @@ namespace Cysharp.Threading.Tasks { } - public static IUniTaskSource Create(TimeSpan delayFrameTimeSpan, PlayerLoopTiming timing, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(TimeSpan delayFrameTimeSpan, PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -758,6 +819,15 @@ namespace Cysharp.Threading.Tasks result.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1; result.cancellationToken = cancellationToken; + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var promise = (DelayIgnoreTimeScalePromise)state; + promise.core.TrySetCanceled(promise.cancellationToken); + }, result); + } + TaskTracker.TrackActiveTask(result, 3); PlayerLoopHelper.AddAction(timing, result); @@ -826,6 +896,7 @@ namespace Cysharp.Threading.Tasks delayFrameTimeSpan = default; elapsed = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } @@ -844,6 +915,7 @@ namespace Cysharp.Threading.Tasks long delayTimeSpanTicks; ValueStopwatch stopwatch; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; UniTaskCompletionSourceCore core; @@ -851,7 +923,7 @@ namespace Cysharp.Threading.Tasks { } - public static IUniTaskSource Create(TimeSpan delayTimeSpan, PlayerLoopTiming timing, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(TimeSpan delayTimeSpan, PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -867,6 +939,15 @@ namespace Cysharp.Threading.Tasks result.delayTimeSpanTicks = delayTimeSpan.Ticks; result.cancellationToken = cancellationToken; + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var promise = (DelayRealtimePromise)state; + promise.core.TrySetCanceled(promise.cancellationToken); + }, result); + } + TaskTracker.TrackActiveTask(result, 3); PlayerLoopHelper.AddAction(timing, result); @@ -931,6 +1012,7 @@ namespace Cysharp.Threading.Tasks core.Reset(); stopwatch = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.WaitUntil.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.WaitUntil.cs index 0a09fe0..b28a529 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.WaitUntil.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UniTask.WaitUntil.cs @@ -9,30 +9,30 @@ namespace Cysharp.Threading.Tasks { public partial struct UniTask { - public static UniTask WaitUntil(Func predicate, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask WaitUntil(Func predicate, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { - return new UniTask(WaitUntilPromise.Create(predicate, timing, cancellationToken, out var token), token); + return new UniTask(WaitUntilPromise.Create(predicate, timing, cancellationToken, cancelImmediately, out var token), token); } - public static UniTask WaitWhile(Func predicate, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask WaitWhile(Func predicate, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { - return new UniTask(WaitWhilePromise.Create(predicate, timing, cancellationToken, out var token), token); + return new UniTask(WaitWhilePromise.Create(predicate, timing, cancellationToken, cancelImmediately, out var token), token); } - public static UniTask WaitUntilCanceled(CancellationToken cancellationToken, PlayerLoopTiming timing = PlayerLoopTiming.Update) + public static UniTask WaitUntilCanceled(CancellationToken cancellationToken, PlayerLoopTiming timing = PlayerLoopTiming.Update, bool completeImmediately = false) { - return new UniTask(WaitUntilCanceledPromise.Create(cancellationToken, timing, out var token), token); + return new UniTask(WaitUntilCanceledPromise.Create(cancellationToken, timing, completeImmediately, out var token), token); } - public static UniTask WaitUntilValueChanged(T target, Func monitorFunction, PlayerLoopTiming monitorTiming = PlayerLoopTiming.Update, IEqualityComparer equalityComparer = null, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask WaitUntilValueChanged(T target, Func monitorFunction, PlayerLoopTiming monitorTiming = PlayerLoopTiming.Update, IEqualityComparer equalityComparer = null, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) where T : class { var unityObject = target as UnityEngine.Object; var isUnityObject = target is UnityEngine.Object; // don't use (unityObject == null) return new UniTask(isUnityObject - ? WaitUntilValueChangedUnityObjectPromise.Create(target, monitorFunction, equalityComparer, monitorTiming, cancellationToken, out var token) - : WaitUntilValueChangedStandardObjectPromise.Create(target, monitorFunction, equalityComparer, monitorTiming, cancellationToken, out token), token); + ? WaitUntilValueChangedUnityObjectPromise.Create(target, monitorFunction, equalityComparer, monitorTiming, cancellationToken, cancelImmediately, out var token) + : WaitUntilValueChangedStandardObjectPromise.Create(target, monitorFunction, equalityComparer, monitorTiming, cancellationToken, cancelImmediately, out token), token); } sealed class WaitUntilPromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode @@ -48,6 +48,7 @@ namespace Cysharp.Threading.Tasks Func predicate; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; UniTaskCompletionSourceCore core; @@ -55,7 +56,7 @@ namespace Cysharp.Threading.Tasks { } - public static IUniTaskSource Create(Func predicate, PlayerLoopTiming timing, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(Func predicate, PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -70,6 +71,15 @@ namespace Cysharp.Threading.Tasks result.predicate = predicate; result.cancellationToken = cancellationToken; + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var promise = (WaitUntilPromise)state; + promise.core.TrySetCanceled(promise.cancellationToken); + }, result); + } + TaskTracker.TrackActiveTask(result, 3); PlayerLoopHelper.AddAction(timing, result); @@ -136,6 +146,7 @@ namespace Cysharp.Threading.Tasks core.Reset(); predicate = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } @@ -153,6 +164,7 @@ namespace Cysharp.Threading.Tasks Func predicate; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; UniTaskCompletionSourceCore core; @@ -160,7 +172,7 @@ namespace Cysharp.Threading.Tasks { } - public static IUniTaskSource Create(Func predicate, PlayerLoopTiming timing, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(Func predicate, PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -174,6 +186,15 @@ namespace Cysharp.Threading.Tasks result.predicate = predicate; result.cancellationToken = cancellationToken; + + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var promise = (WaitWhilePromise)state; + promise.core.TrySetCanceled(promise.cancellationToken); + }, result); + } TaskTracker.TrackActiveTask(result, 3); @@ -241,6 +262,7 @@ namespace Cysharp.Threading.Tasks core.Reset(); predicate = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } @@ -257,6 +279,7 @@ namespace Cysharp.Threading.Tasks } CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; UniTaskCompletionSourceCore core; @@ -264,7 +287,7 @@ namespace Cysharp.Threading.Tasks { } - public static IUniTaskSource Create(CancellationToken cancellationToken, PlayerLoopTiming timing, out short token) + public static IUniTaskSource Create(CancellationToken cancellationToken, PlayerLoopTiming timing, bool completeImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -278,6 +301,15 @@ namespace Cysharp.Threading.Tasks result.cancellationToken = cancellationToken; + if (completeImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var promise = (WaitUntilCanceledPromise)state; + promise.core.TrySetResult(null); + }, result); + } + TaskTracker.TrackActiveTask(result, 3); PlayerLoopHelper.AddAction(timing, result); @@ -329,6 +361,7 @@ namespace Cysharp.Threading.Tasks TaskTracker.RemoveTracking(this); core.Reset(); cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } @@ -351,6 +384,7 @@ namespace Cysharp.Threading.Tasks Func monitorFunction; IEqualityComparer equalityComparer; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; UniTaskCompletionSourceCore core; @@ -358,7 +392,7 @@ namespace Cysharp.Threading.Tasks { } - public static IUniTaskSource Create(T target, Func monitorFunction, IEqualityComparer equalityComparer, PlayerLoopTiming timing, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(T target, Func monitorFunction, IEqualityComparer equalityComparer, PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -376,6 +410,15 @@ namespace Cysharp.Threading.Tasks result.currentValue = monitorFunction(target); result.equalityComparer = equalityComparer ?? UnityEqualityComparer.GetDefault(); result.cancellationToken = cancellationToken; + + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var promise = (WaitUntilValueChangedUnityObjectPromise)state; + promise.core.TrySetCanceled(promise.cancellationToken); + }, result); + } TaskTracker.TrackActiveTask(result, 3); @@ -453,6 +496,7 @@ namespace Cysharp.Threading.Tasks monitorFunction = default; equalityComparer = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } @@ -474,6 +518,7 @@ namespace Cysharp.Threading.Tasks Func monitorFunction; IEqualityComparer equalityComparer; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; UniTaskCompletionSourceCore core; @@ -481,7 +526,7 @@ namespace Cysharp.Threading.Tasks { } - public static IUniTaskSource Create(T target, Func monitorFunction, IEqualityComparer equalityComparer, PlayerLoopTiming timing, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(T target, Func monitorFunction, IEqualityComparer equalityComparer, PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -498,6 +543,15 @@ namespace Cysharp.Threading.Tasks result.currentValue = monitorFunction(target); result.equalityComparer = equalityComparer ?? UnityEqualityComparer.GetDefault(); result.cancellationToken = cancellationToken; + + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var promise = (WaitUntilValueChangedStandardObjectPromise)state; + promise.core.TrySetCanceled(promise.cancellationToken); + }, result); + } TaskTracker.TrackActiveTask(result, 3); @@ -575,6 +629,7 @@ namespace Cysharp.Threading.Tasks monitorFunction = default; equalityComparer = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AssetBundleRequestAllAssets.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AssetBundleRequestAllAssets.cs index 1a1e011..95f00b2 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AssetBundleRequestAllAssets.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AssetBundleRequestAllAssets.cs @@ -24,12 +24,17 @@ namespace Cysharp.Threading.Tasks return AwaitForAllAssets(asyncOperation, null, PlayerLoopTiming.Update, cancellationToken: cancellationToken); } - public static UniTask AwaitForAllAssets(this AssetBundleRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask AwaitForAllAssets(this AssetBundleRequest asyncOperation, CancellationToken cancellationToken, bool cancelImmediately) + { + return AwaitForAllAssets(asyncOperation, null, PlayerLoopTiming.Update, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately); + } + + public static UniTask AwaitForAllAssets(this AssetBundleRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.allAssets); - return new UniTask(AssetBundleRequestAllAssetsConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); + return new UniTask(AssetBundleRequestAllAssetsConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token); } public struct AssetBundleRequestAllAssetsAwaiter : ICriticalNotifyCompletion @@ -95,15 +100,15 @@ namespace Cysharp.Threading.Tasks AssetBundleRequest asyncOperation; IProgress progress; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; UniTaskCompletionSourceCore core; AssetBundleRequestAllAssetsConfiguredSource() { - } - public static IUniTaskSource Create(AssetBundleRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(AssetBundleRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -118,6 +123,15 @@ namespace Cysharp.Threading.Tasks result.asyncOperation = asyncOperation; result.progress = progress; result.cancellationToken = cancellationToken; + + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var promise = (AssetBundleRequestAllAssetsConfiguredSource)state; + promise.core.TrySetCanceled(promise.cancellationToken); + }, result); + } TaskTracker.TrackActiveTask(result, 3); @@ -188,6 +202,7 @@ namespace Cysharp.Threading.Tasks asyncOperation = default; progress = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AsyncGPUReadback.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AsyncGPUReadback.cs index 5805dbb..0faf0be 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AsyncGPUReadback.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AsyncGPUReadback.cs @@ -20,10 +20,15 @@ namespace Cysharp.Threading.Tasks return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } - public static UniTask ToUniTask(this AsyncGPUReadbackRequest asyncOperation, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask WithCancellation(this AsyncGPUReadbackRequest asyncOperation, CancellationToken cancellationToken, bool cancelImmediately) + { + return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately); + } + + public static UniTask ToUniTask(this AsyncGPUReadbackRequest asyncOperation, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { if (asyncOperation.done) return UniTask.FromResult(asyncOperation); - return new UniTask(AsyncGPUReadbackRequestAwaiterConfiguredSource.Create(asyncOperation, timing, cancellationToken, out var token), token); + return new UniTask(AsyncGPUReadbackRequestAwaiterConfiguredSource.Create(asyncOperation, timing, cancellationToken, cancelImmediately, out var token), token); } sealed class AsyncGPUReadbackRequestAwaiterConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode @@ -39,15 +44,15 @@ namespace Cysharp.Threading.Tasks AsyncGPUReadbackRequest asyncOperation; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; UniTaskCompletionSourceCore core; AsyncGPUReadbackRequestAwaiterConfiguredSource() { - } - public static IUniTaskSource Create(AsyncGPUReadbackRequest asyncOperation, PlayerLoopTiming timing, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(AsyncGPUReadbackRequest asyncOperation, PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -61,6 +66,15 @@ namespace Cysharp.Threading.Tasks result.asyncOperation = asyncOperation; result.cancellationToken = cancellationToken; + + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var promise = (AsyncGPUReadbackRequestAwaiterConfiguredSource)state; + promise.core.TrySetCanceled(promise.cancellationToken); + }, result); + } TaskTracker.TrackActiveTask(result, 3); @@ -131,6 +145,7 @@ namespace Cysharp.Threading.Tasks core.Reset(); asyncOperation = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs index 00afe66..db22575 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs @@ -29,13 +29,18 @@ namespace Cysharp.Threading.Tasks { return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } + + public static UniTask WithCancellation(this AsyncOperation asyncOperation, CancellationToken cancellationToken, bool cancelImmediately) + { + return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately); + } - public static UniTask ToUniTask(this AsyncOperation asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask ToUniTask(this AsyncOperation asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); if (asyncOperation.isDone) return UniTask.CompletedTask; - return new UniTask(AsyncOperationConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); + return new UniTask(AsyncOperationConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token); } public struct AsyncOperationAwaiter : ICriticalNotifyCompletion @@ -92,15 +97,15 @@ namespace Cysharp.Threading.Tasks AsyncOperation asyncOperation; IProgress progress; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; UniTaskCompletionSourceCore core; AsyncOperationConfiguredSource() { - } - public static IUniTaskSource Create(AsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(AsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -111,6 +116,16 @@ namespace Cysharp.Threading.Tasks { result = new AsyncOperationConfiguredSource(); } + + + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var promise = (AsyncOperationConfiguredSource)state; + promise.core.TrySetCanceled(promise.cancellationToken); + }, result); + } result.asyncOperation = asyncOperation; result.progress = progress; @@ -136,7 +151,6 @@ namespace Cysharp.Threading.Tasks } } - public UniTaskStatus GetStatus(short token) { return core.GetStatus(token); @@ -181,6 +195,7 @@ namespace Cysharp.Threading.Tasks asyncOperation = default; progress = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } @@ -739,7 +754,12 @@ namespace Cysharp.Threading.Tasks return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } - public static UniTask ToUniTask(this UnityWebRequestAsyncOperation asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask WithCancellation(this UnityWebRequestAsyncOperation asyncOperation, CancellationToken cancellationToken, bool cancelImmediately) + { + return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately); + } + + public static UniTask ToUniTask(this UnityWebRequestAsyncOperation asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); @@ -751,7 +771,7 @@ namespace Cysharp.Threading.Tasks } return UniTask.FromResult(asyncOperation.webRequest); } - return new UniTask(UnityWebRequestAsyncOperationConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); + return new UniTask(UnityWebRequestAsyncOperationConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token); } public struct UnityWebRequestAsyncOperationAwaiter : ICriticalNotifyCompletion @@ -820,15 +840,15 @@ namespace Cysharp.Threading.Tasks UnityWebRequestAsyncOperation asyncOperation; IProgress progress; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; UniTaskCompletionSourceCore core; UnityWebRequestAsyncOperationConfiguredSource() { - } - public static IUniTaskSource Create(UnityWebRequestAsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(UnityWebRequestAsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -843,6 +863,15 @@ namespace Cysharp.Threading.Tasks result.asyncOperation = asyncOperation; result.progress = progress; result.cancellationToken = cancellationToken; + + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var promise = (UnityWebRequestAsyncOperationConfiguredSource)state; + promise.core.TrySetCanceled(promise.cancellationToken); + }, result); + } TaskTracker.TrackActiveTask(result, 3); @@ -925,6 +954,7 @@ namespace Cysharp.Threading.Tasks asyncOperation = default; progress = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } From 7f582e5e29228179cacee450da471d41ea125dd8 Mon Sep 17 00:00:00 2001 From: hadashiA Date: Fri, 27 Oct 2023 14:51:52 +0900 Subject: [PATCH 02/13] Update README --- README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 487aec0..d1cacac 100644 --- a/README.md +++ b/README.md @@ -336,6 +336,18 @@ if (isCanceled) Note: Only suppress throws if you call directly into the most source method. Otherwise, the return value will be converted, but the entire pipeline will not suppress throws. +Some features that use Unity's player loop, such as `UniTask.Yield` and `UniTask.Delay` etc, determines CancellationToken state on the player loop. +This means it does not cancel immediately upon `CancellationToken` fired. + +If you want to change this behaviour, the cancellation to be immediate, set the `cancelImmediately` flag as an argument. + +```csharp +await UniTask.Yield(cancellationToken, cancelImmediately: true); +``` + +Note: Setting `cancelImmediately` to true and detecting an immediate cancellation is more costly than the default behavior. +This is because it uses `CancellationToken.Register`; it is heavier than checking CancellationToken on the player loop. + Timeout handling --- Timeout is a variation of cancellation. You can set timeout by `CancellationTokenSouce.CancelAfterSlim(TimeSpan)` and pass CancellationToken to async methods. @@ -363,7 +375,7 @@ If you want to use timeout with other source of cancellation, use `CancellationT ```csharp var cancelToken = new CancellationTokenSource(); -cancelButton.onClick.AddListener(()=> +cancelButton.onClick.AddListener(() => { cancelToken.Cancel(); // cancel from button click. }); From 94be2e748b650baefa506f1def9a880e2a918c07 Mon Sep 17 00:00:00 2001 From: hadashiA Date: Fri, 27 Oct 2023 15:06:12 +0900 Subject: [PATCH 03/13] Add cancelImmediately flag for addressable extensions --- .../AddressablesAsyncExtensions.cs | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/External/Addressables/AddressablesAsyncExtensions.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/External/Addressables/AddressablesAsyncExtensions.cs index f321bdb..a0ca8a1 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/External/Addressables/AddressablesAsyncExtensions.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/External/Addressables/AddressablesAsyncExtensions.cs @@ -25,7 +25,12 @@ namespace Cysharp.Threading.Tasks return ToUniTask(handle, cancellationToken: cancellationToken); } - public static UniTask ToUniTask(this AsyncOperationHandle handle, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask WithCancellation(this AsyncOperationHandle handle, CancellationToken cancellationToken, bool cancelImmediately) + { + return ToUniTask(handle, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately); + } + + public static UniTask ToUniTask(this AsyncOperationHandle handle, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); @@ -44,7 +49,7 @@ namespace Cysharp.Threading.Tasks return UniTask.CompletedTask; } - return new UniTask(AsyncOperationHandleConfiguredSource.Create(handle, timing, progress, cancellationToken, out var token), token); + return new UniTask(AsyncOperationHandleConfiguredSource.Create(handle, timing, progress, cancellationToken, cancelImmediately, out var token), token); } public struct AsyncOperationHandleAwaiter : ICriticalNotifyCompletion @@ -106,6 +111,7 @@ namespace Cysharp.Threading.Tasks readonly Action continuationAction; AsyncOperationHandle handle; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; IProgress progress; bool completed; @@ -116,7 +122,7 @@ namespace Cysharp.Threading.Tasks continuationAction = Continuation; } - public static IUniTaskSource Create(AsyncOperationHandle handle, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(AsyncOperationHandle handle, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -132,6 +138,15 @@ namespace Cysharp.Threading.Tasks result.progress = progress; result.cancellationToken = cancellationToken; result.completed = false; + + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var promise = (AsyncOperationHandleConfiguredSource)state; + promise.core.TrySetCanceled(promise.cancellationToken); + }, result); + } TaskTracker.TrackActiveTask(result, 3); @@ -219,6 +234,7 @@ namespace Cysharp.Threading.Tasks handle = default; progress = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } @@ -237,7 +253,12 @@ namespace Cysharp.Threading.Tasks return ToUniTask(handle, cancellationToken: cancellationToken); } - public static UniTask ToUniTask(this AsyncOperationHandle handle, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask WithCancellation(this AsyncOperationHandle handle, CancellationToken cancellationToken, bool cancelImmediately) + { + return ToUniTask(handle, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately); + } + + public static UniTask ToUniTask(this AsyncOperationHandle handle, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); @@ -255,7 +276,7 @@ namespace Cysharp.Threading.Tasks return UniTask.FromResult(handle.Result); } - return new UniTask(AsyncOperationHandleConfiguredSource.Create(handle, timing, progress, cancellationToken, out var token), token); + return new UniTask(AsyncOperationHandleConfiguredSource.Create(handle, timing, progress, cancellationToken, cancelImmediately, out var token), token); } sealed class AsyncOperationHandleConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode> @@ -272,6 +293,7 @@ namespace Cysharp.Threading.Tasks readonly Action> continuationAction; AsyncOperationHandle handle; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; IProgress progress; bool completed; @@ -282,7 +304,7 @@ namespace Cysharp.Threading.Tasks continuationAction = Continuation; } - public static IUniTaskSource Create(AsyncOperationHandle handle, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(AsyncOperationHandle handle, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -298,6 +320,15 @@ namespace Cysharp.Threading.Tasks result.cancellationToken = cancellationToken; result.completed = false; result.progress = progress; + + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var promise = (AsyncOperationHandleConfiguredSource)state; + promise.core.TrySetCanceled(promise.cancellationToken); + }, result); + } TaskTracker.TrackActiveTask(result, 3); @@ -390,6 +421,7 @@ namespace Cysharp.Threading.Tasks handle = default; progress = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } From 2cf06af4331b0ac59b46a9c5d13c946b6309a3c1 Mon Sep 17 00:00:00 2001 From: hadashiA Date: Fri, 27 Oct 2023 16:45:41 +0900 Subject: [PATCH 04/13] Change AsyncEnumerable factory to use OperationCanceledException --- .../Linq/UnityExtensions/EveryUpdate.cs | 18 ++++++-- .../Linq/UnityExtensions/EveryValueChanged.cs | 41 +++++++++++++++---- .../Runtime/Linq/UnityExtensions/Timer.cs | 29 +++++++++---- 3 files changed, 69 insertions(+), 19 deletions(-) diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryUpdate.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryUpdate.cs index 09c5cf9..8f09110 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryUpdate.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryUpdate.cs @@ -56,10 +56,14 @@ namespace Cysharp.Threading.Tasks.Linq public UniTask MoveNextAsync() { - // return false instead of throw - if (disposed || cancellationToken.IsCancellationRequested) return CompletedTasks.False; - + if (disposed) return CompletedTasks.False; + completionSource.Reset(); + + if (cancellationToken.IsCancellationRequested) + { + completionSource.TrySetCanceled(cancellationToken); + } return new UniTask(this, completionSource.Version); } @@ -76,7 +80,13 @@ namespace Cysharp.Threading.Tasks.Linq public bool MoveNext() { - if (disposed || cancellationToken.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested) + { + completionSource.TrySetCanceled(cancellationToken); + return false; + } + + if (disposed) { completionSource.TrySetResult(false); return false; diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryValueChanged.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryValueChanged.cs index 8b19f64..ef5739c 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryValueChanged.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/EveryValueChanged.cs @@ -85,8 +85,15 @@ namespace Cysharp.Threading.Tasks.Linq public UniTask MoveNextAsync() { - // return false instead of throw - if (disposed || cancellationToken.IsCancellationRequested) return CompletedTasks.False; + if (disposed) return CompletedTasks.False; + + completionSource.Reset(); + + if (cancellationToken.IsCancellationRequested) + { + completionSource.TrySetCanceled(cancellationToken); + return new UniTask(this, completionSource.Version); + } if (first) { @@ -99,7 +106,6 @@ namespace Cysharp.Threading.Tasks.Linq return CompletedTasks.True; } - completionSource.Reset(); return new UniTask(this, completionSource.Version); } @@ -107,6 +113,7 @@ namespace Cysharp.Threading.Tasks.Linq { if (!disposed) { + cancellationTokenRegistration.Dispose(); disposed = true; TaskTracker.RemoveTracking(this); } @@ -115,13 +122,18 @@ namespace Cysharp.Threading.Tasks.Linq public bool MoveNext() { - if (disposed || cancellationToken.IsCancellationRequested || targetAsUnityObject == null) // destroyed = cancel. + if (disposed || targetAsUnityObject == null) { completionSource.TrySetResult(false); DisposeAsync().Forget(); return false; } - + + if (cancellationToken.IsCancellationRequested) + { + completionSource.TrySetCanceled(cancellationToken); + return false; + } TProperty nextValue = default(TProperty); try { @@ -205,8 +217,16 @@ namespace Cysharp.Threading.Tasks.Linq public UniTask MoveNextAsync() { - if (disposed || cancellationToken.IsCancellationRequested) return CompletedTasks.False; + if (disposed) return CompletedTasks.False; + completionSource.Reset(); + + if (cancellationToken.IsCancellationRequested) + { + completionSource.TrySetCanceled(cancellationToken); + return new UniTask(this, completionSource.Version); + } + if (first) { first = false; @@ -218,7 +238,6 @@ namespace Cysharp.Threading.Tasks.Linq return CompletedTasks.True; } - completionSource.Reset(); return new UniTask(this, completionSource.Version); } @@ -235,13 +254,19 @@ namespace Cysharp.Threading.Tasks.Linq public bool MoveNext() { - if (disposed || cancellationToken.IsCancellationRequested || !target.TryGetTarget(out var t)) + if (disposed || !target.TryGetTarget(out var t)) { completionSource.TrySetResult(false); DisposeAsync().Forget(); return false; } + if (cancellationToken.IsCancellationRequested) + { + completionSource.TrySetCanceled(cancellationToken); + return false; + } + TProperty nextValue = default(TProperty); try { diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/Timer.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/Timer.cs index a3bab3c..b8aabf2 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/Timer.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/Linq/UnityExtensions/Timer.cs @@ -109,7 +109,6 @@ namespace Cysharp.Threading.Tasks.Linq this.ignoreTimeScale = ignoreTimeScale; this.cancellationToken = cancellationToken; - if (cancelImmediately && cancellationToken.CanBeCanceled) { cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => @@ -128,12 +127,16 @@ namespace Cysharp.Threading.Tasks.Linq public UniTask MoveNextAsync() { // return false instead of throw - if (disposed || cancellationToken.IsCancellationRequested || completed) return CompletedTasks.False; + if (disposed || completed) return CompletedTasks.False; // reset value here. this.elapsed = 0; completionSource.Reset(); + if (cancellationToken.IsCancellationRequested) + { + completionSource.TrySetCanceled(cancellationToken); + } return new UniTask(this, completionSource.Version); } @@ -150,11 +153,16 @@ namespace Cysharp.Threading.Tasks.Linq public bool MoveNext() { - if (disposed || cancellationToken.IsCancellationRequested) + if (disposed) { completionSource.TrySetResult(false); return false; } + if (cancellationToken.IsCancellationRequested) + { + completionSource.TrySetCanceled(cancellationToken); + return false; + } if (dueTimePhase) { @@ -261,13 +269,15 @@ namespace Cysharp.Threading.Tasks.Linq public UniTask MoveNextAsync() { - // return false instead of throw - if (disposed || cancellationToken.IsCancellationRequested || completed) return CompletedTasks.False; + if (disposed || completed) return CompletedTasks.False; + if (cancellationToken.IsCancellationRequested) + { + completionSource.TrySetCanceled(cancellationToken); + } // reset value here. this.currentFrame = 0; - completionSource.Reset(); return new UniTask(this, completionSource.Version); } @@ -285,7 +295,12 @@ namespace Cysharp.Threading.Tasks.Linq public bool MoveNext() { - if (disposed || cancellationToken.IsCancellationRequested) + if (cancellationToken.IsCancellationRequested) + { + completionSource.TrySetCanceled(cancellationToken); + return false; + } + if (disposed) { completionSource.TrySetResult(false); return false; From 24afc4f3ebe7f00388ad1e576aebbdf3cc074225 Mon Sep 17 00:00:00 2001 From: hadashiA Date: Fri, 27 Oct 2023 17:48:17 +0900 Subject: [PATCH 05/13] Add cancelImmediately flag for assetbundle extensions --- .../UniTask/Runtime/UnityAsyncExtensions.cs | 69 +++++++++++++++---- 1 file changed, 57 insertions(+), 12 deletions(-) diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs index db22575..235faa0 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs @@ -215,12 +215,17 @@ namespace Cysharp.Threading.Tasks return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } - public static UniTask ToUniTask(this ResourceRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask WithCancellation(this ResourceRequest asyncOperation, CancellationToken cancellationToken, bool cancelImmediately) + { + return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately); + } + + public static UniTask ToUniTask(this ResourceRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.asset); - return new UniTask(ResourceRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); + return new UniTask(ResourceRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token); } public struct ResourceRequestAwaiter : ICriticalNotifyCompletion @@ -281,15 +286,15 @@ namespace Cysharp.Threading.Tasks ResourceRequest asyncOperation; IProgress progress; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; UniTaskCompletionSourceCore core; ResourceRequestConfiguredSource() { - } - public static IUniTaskSource Create(ResourceRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(ResourceRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -304,6 +309,15 @@ namespace Cysharp.Threading.Tasks result.asyncOperation = asyncOperation; result.progress = progress; result.cancellationToken = cancellationToken; + + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var source = (ResourceRequestConfiguredSource)state; + source.core.TrySetCanceled(source.cancellationToken); + }, result); + } TaskTracker.TrackActiveTask(result, 3); @@ -374,6 +388,7 @@ namespace Cysharp.Threading.Tasks asyncOperation = default; progress = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } @@ -394,12 +409,17 @@ namespace Cysharp.Threading.Tasks return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } - public static UniTask ToUniTask(this AssetBundleRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask WithCancellation(this AssetBundleRequest asyncOperation, CancellationToken cancellationToken, bool cancelImmediately) + { + return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately); + } + + public static UniTask ToUniTask(this AssetBundleRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.asset); - return new UniTask(AssetBundleRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); + return new UniTask(AssetBundleRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token); } public struct AssetBundleRequestAwaiter : ICriticalNotifyCompletion @@ -460,15 +480,15 @@ namespace Cysharp.Threading.Tasks AssetBundleRequest asyncOperation; IProgress progress; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; UniTaskCompletionSourceCore core; AssetBundleRequestConfiguredSource() { - } - public static IUniTaskSource Create(AssetBundleRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(AssetBundleRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -483,6 +503,15 @@ namespace Cysharp.Threading.Tasks result.asyncOperation = asyncOperation; result.progress = progress; result.cancellationToken = cancellationToken; + + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var source = (AssetBundleRequestConfiguredSource)state; + source.core.TrySetCanceled(source.cancellationToken); + }, result); + } TaskTracker.TrackActiveTask(result, 3); @@ -553,6 +582,7 @@ namespace Cysharp.Threading.Tasks asyncOperation = default; progress = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } @@ -574,12 +604,17 @@ namespace Cysharp.Threading.Tasks return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } - public static UniTask ToUniTask(this AssetBundleCreateRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask WithCancellation(this AssetBundleCreateRequest asyncOperation, CancellationToken cancellationToken, bool cancelImmediately) + { + return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately); + } + + public static UniTask ToUniTask(this AssetBundleCreateRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.assetBundle); - return new UniTask(AssetBundleCreateRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); + return new UniTask(AssetBundleCreateRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token); } public struct AssetBundleCreateRequestAwaiter : ICriticalNotifyCompletion @@ -640,15 +675,15 @@ namespace Cysharp.Threading.Tasks AssetBundleCreateRequest asyncOperation; IProgress progress; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; UniTaskCompletionSourceCore core; AssetBundleCreateRequestConfiguredSource() { - } - public static IUniTaskSource Create(AssetBundleCreateRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(AssetBundleCreateRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -663,6 +698,15 @@ namespace Cysharp.Threading.Tasks result.asyncOperation = asyncOperation; result.progress = progress; result.cancellationToken = cancellationToken; + + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var source = (AssetBundleCreateRequestConfiguredSource)state; + source.core.TrySetCanceled(source.cancellationToken); + }, result); + } TaskTracker.TrackActiveTask(result, 3); @@ -733,6 +777,7 @@ namespace Cysharp.Threading.Tasks asyncOperation = default; progress = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } From f0adf36633ea17ec3a97c46918536c05184e804a Mon Sep 17 00:00:00 2001 From: hadashiA Date: Fri, 27 Oct 2023 19:38:37 +0900 Subject: [PATCH 06/13] Use AsyncOperation.completed handler with new optional argument --- ...cExtensions.AssetBundleRequestAllAssets.cs | 130 +++- .../UniTask/Runtime/UnityAsyncExtensions.cs | 685 +++++++++++++++--- .../UniTask/Runtime/UnityAsyncExtensions.tt | 148 +++- 3 files changed, 855 insertions(+), 108 deletions(-) diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AssetBundleRequestAllAssets.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AssetBundleRequestAllAssets.cs index 95f00b2..f6fccc0 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AssetBundleRequestAllAssets.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AssetBundleRequestAllAssets.cs @@ -24,17 +24,24 @@ namespace Cysharp.Threading.Tasks return AwaitForAllAssets(asyncOperation, null, PlayerLoopTiming.Update, cancellationToken: cancellationToken); } - public static UniTask AwaitForAllAssets(this AssetBundleRequest asyncOperation, CancellationToken cancellationToken, bool cancelImmediately) + public static UniTask AwaitForAllAssets(this AssetBundleRequest asyncOperation, bool handleImmediately, CancellationToken cancellationToken) { - return AwaitForAllAssets(asyncOperation, null, PlayerLoopTiming.Update, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately); + if (handleImmediately) + { + Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); + if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); + if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.allAssets); + return new UniTask(AssetBundleRequestAllAssetsCallbackHandlerSource.Create(asyncOperation, cancellationToken, out var token), token); + } + return AwaitForAllAssets(asyncOperation, progress: null, cancellationToken: cancellationToken); } - public static UniTask AwaitForAllAssets(this AssetBundleRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) + public static UniTask AwaitForAllAssets(this AssetBundleRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.allAssets); - return new UniTask(AssetBundleRequestAllAssetsConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token); + return new UniTask(AssetBundleRequestAllAssetsConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); } public struct AssetBundleRequestAllAssetsAwaiter : ICriticalNotifyCompletion @@ -85,6 +92,108 @@ namespace Cysharp.Threading.Tasks asyncOperation.completed += continuationAction; } } + + sealed class AssetBundleRequestAllAssetsCallbackHandlerSource : IUniTaskSource, ITaskPoolNode + { + static TaskPool pool; + AssetBundleRequestAllAssetsCallbackHandlerSource nextNode; + public ref AssetBundleRequestAllAssetsCallbackHandlerSource NextNode => ref nextNode; + + static AssetBundleRequestAllAssetsCallbackHandlerSource() + { + TaskPool.RegisterSizeGetter(typeof(AssetBundleRequestConfiguredSource), () => pool.Size); + } + + AssetBundleRequest asyncOperation; + CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; + + UniTaskCompletionSourceCore core; + + AssetBundleRequestAllAssetsCallbackHandlerSource() + { + } + + public static IUniTaskSource Create(AssetBundleRequest asyncOperation, CancellationToken cancellationToken, out short token) + { + if (cancellationToken.IsCancellationRequested) + { + return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); + } + + if (!pool.TryPop(out var result)) + { + result = new AssetBundleRequestAllAssetsCallbackHandlerSource(); + } + + result.asyncOperation = asyncOperation; + result.cancellationToken = cancellationToken; + + asyncOperation.completed += result.AsyncOperationCompletedHandler; + + if (cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var source = (AssetBundleRequestAllAssetsCallbackHandlerSource)state; + source.core.TrySetCanceled(source.cancellationToken); + }, result); + } + + TaskTracker.TrackActiveTask(result, 3); + + token = result.core.Version; + return result; + } + + public UnityEngine.Object[] GetResult(short token) + { + try + { + return core.GetResult(token); + } + finally + { + TryReturn(); + } + } + + void IUniTaskSource.GetResult(short token) + { + GetResult(token); + } + + public UniTaskStatus GetStatus(short token) + { + return core.GetStatus(token); + } + + public UniTaskStatus UnsafeGetStatus() + { + return core.UnsafeGetStatus(); + } + + public void OnCompleted(Action continuation, object state, short token) + { + core.OnCompleted(continuation, state, token); + } + + bool TryReturn() + { + TaskTracker.RemoveTracking(this); + core.Reset(); + asyncOperation.completed -= AsyncOperationCompletedHandler; + asyncOperation = default; + cancellationToken = default; + cancellationTokenRegistration.Dispose(); + return pool.TryPush(this); + } + + void AsyncOperationCompletedHandler(AsyncOperation _) + { + core.TrySetResult(asyncOperation.allAssets); + } + } sealed class AssetBundleRequestAllAssetsConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { @@ -100,7 +209,6 @@ namespace Cysharp.Threading.Tasks AssetBundleRequest asyncOperation; IProgress progress; CancellationToken cancellationToken; - CancellationTokenRegistration cancellationTokenRegistration; UniTaskCompletionSourceCore core; @@ -108,7 +216,7 @@ namespace Cysharp.Threading.Tasks { } - public static IUniTaskSource Create(AssetBundleRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, bool cancelImmediately, out short token) + public static IUniTaskSource Create(AssetBundleRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -123,15 +231,6 @@ namespace Cysharp.Threading.Tasks result.asyncOperation = asyncOperation; result.progress = progress; result.cancellationToken = cancellationToken; - - if (cancelImmediately && cancellationToken.CanBeCanceled) - { - result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => - { - var promise = (AssetBundleRequestAllAssetsConfiguredSource)state; - promise.core.TrySetCanceled(promise.cancellationToken); - }, result); - } TaskTracker.TrackActiveTask(result, 3); @@ -202,7 +301,6 @@ namespace Cysharp.Threading.Tasks asyncOperation = default; progress = default; cancellationToken = default; - cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs index 235faa0..069ee5a 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs @@ -15,32 +15,35 @@ namespace Cysharp.Threading.Tasks { #region AsyncOperation -#if !UNITY_2023_1_OR_NEWER - // from Unity2023.1.0a15, AsyncOperationAwaitableExtensions.GetAwaiter is defined in UnityEngine. - public static AsyncOperationAwaiter GetAwaiter(this AsyncOperation asyncOperation) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); return new AsyncOperationAwaiter(asyncOperation); } -#endif public static UniTask WithCancellation(this AsyncOperation asyncOperation, CancellationToken cancellationToken) { return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } - - public static UniTask WithCancellation(this AsyncOperation asyncOperation, CancellationToken cancellationToken, bool cancelImmediately) + + public static UniTask WithCancellation(this AsyncOperation asyncOperation, bool handleImmediately, CancellationToken cancellationToken) { - return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately); + if (handleImmediately) + { + Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); + if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); + if (asyncOperation.isDone) return UniTask.CompletedTask; + return new UniTask(AsyncOperationCallbackHandlerSource.Create(asyncOperation, cancellationToken, out var token), token); + } + return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } - public static UniTask ToUniTask(this AsyncOperation asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) + public static UniTask ToUniTask(this AsyncOperation asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); if (asyncOperation.isDone) return UniTask.CompletedTask; - return new UniTask(AsyncOperationConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token); + return new UniTask(AsyncOperationConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); } public struct AsyncOperationAwaiter : ICriticalNotifyCompletion @@ -83,6 +86,106 @@ namespace Cysharp.Threading.Tasks } } + sealed class AsyncOperationCallbackHandlerSource : IUniTaskSource, ITaskPoolNode + { + static TaskPool pool; + AsyncOperationCallbackHandlerSource nextNode; + public ref AsyncOperationCallbackHandlerSource NextNode => ref nextNode; + + static AsyncOperationCallbackHandlerSource() + { + TaskPool.RegisterSizeGetter(typeof(AsyncOperationCallbackHandlerSource), () => pool.Size); + } + + AsyncOperation asyncOperation; + IProgress progress; + CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; + + UniTaskCompletionSourceCore core; + + AsyncOperationCallbackHandlerSource() + { + } + + public static IUniTaskSource Create(AsyncOperation asyncOperation, CancellationToken cancellationToken, out short token) + { + if (cancellationToken.IsCancellationRequested) + { + return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); + } + + if (!pool.TryPop(out var result)) + { + result = new AsyncOperationCallbackHandlerSource(); + } + + result.asyncOperation = asyncOperation; + result.cancellationToken = cancellationToken; + + asyncOperation.completed += result.AsyncOperationCompletedHandler; + + if (cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var source = (AsyncOperationCallbackHandlerSource)state; + source.core.TrySetCanceled(source.cancellationToken); + }, result); + } + + TaskTracker.TrackActiveTask(result, 3); + + token = result.core.Version; + return result; + } + + public void GetResult(short token) + { + try + { + core.GetResult(token); + } + finally + { + TryReturn(); + } + } + + + public UniTaskStatus GetStatus(short token) + { + return core.GetStatus(token); + } + + public UniTaskStatus UnsafeGetStatus() + { + return core.UnsafeGetStatus(); + } + + public void OnCompleted(Action continuation, object state, short token) + { + core.OnCompleted(continuation, state, token); + } + + bool TryReturn() + { + TaskTracker.RemoveTracking(this); + core.Reset(); + asyncOperation.completed -= AsyncOperationCompletedHandler; + asyncOperation = default; + progress = default; + cancellationToken = default; + cancellationTokenRegistration.Dispose(); + return pool.TryPush(this); + } + + void AsyncOperationCompletedHandler(AsyncOperation _) + { + core.TrySetResult(AsyncUnit.Default); + } + } + sealed class AsyncOperationConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; @@ -97,7 +200,6 @@ namespace Cysharp.Threading.Tasks AsyncOperation asyncOperation; IProgress progress; CancellationToken cancellationToken; - CancellationTokenRegistration cancellationTokenRegistration; UniTaskCompletionSourceCore core; @@ -105,7 +207,7 @@ namespace Cysharp.Threading.Tasks { } - public static IUniTaskSource Create(AsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, bool cancelImmediately, out short token) + public static IUniTaskSource Create(AsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -116,16 +218,6 @@ namespace Cysharp.Threading.Tasks { result = new AsyncOperationConfiguredSource(); } - - - if (cancelImmediately && cancellationToken.CanBeCanceled) - { - result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => - { - var promise = (AsyncOperationConfiguredSource)state; - promise.core.TrySetCanceled(promise.cancellationToken); - }, result); - } result.asyncOperation = asyncOperation; result.progress = progress; @@ -151,6 +243,7 @@ namespace Cysharp.Threading.Tasks } } + public UniTaskStatus GetStatus(short token) { return core.GetStatus(token); @@ -195,7 +288,6 @@ namespace Cysharp.Threading.Tasks asyncOperation = default; progress = default; cancellationToken = default; - cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } @@ -215,17 +307,24 @@ namespace Cysharp.Threading.Tasks return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } - public static UniTask WithCancellation(this ResourceRequest asyncOperation, CancellationToken cancellationToken, bool cancelImmediately) + public static UniTask WithCancellation(this ResourceRequest asyncOperation, bool handleImmediately, CancellationToken cancellationToken) { - return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately); + if (handleImmediately) + { + Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); + if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); + if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.asset); + return new UniTask(ResourceRequestCallbackHandlerSource.Create(asyncOperation, cancellationToken, out var token), token); + } + return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } - public static UniTask ToUniTask(this ResourceRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) + public static UniTask ToUniTask(this ResourceRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.asset); - return new UniTask(ResourceRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token); + return new UniTask(ResourceRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); } public struct ResourceRequestAwaiter : ICriticalNotifyCompletion @@ -272,6 +371,110 @@ namespace Cysharp.Threading.Tasks } } + sealed class ResourceRequestCallbackHandlerSource : IUniTaskSource, ITaskPoolNode + { + static TaskPool pool; + ResourceRequestCallbackHandlerSource nextNode; + public ref ResourceRequestCallbackHandlerSource NextNode => ref nextNode; + + static ResourceRequestCallbackHandlerSource() + { + TaskPool.RegisterSizeGetter(typeof(ResourceRequestCallbackHandlerSource), () => pool.Size); + } + + ResourceRequest asyncOperation; + IProgress progress; + CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; + + UniTaskCompletionSourceCore core; + + ResourceRequestCallbackHandlerSource() + { + } + + public static IUniTaskSource Create(ResourceRequest asyncOperation, CancellationToken cancellationToken, out short token) + { + if (cancellationToken.IsCancellationRequested) + { + return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); + } + + if (!pool.TryPop(out var result)) + { + result = new ResourceRequestCallbackHandlerSource(); + } + + result.asyncOperation = asyncOperation; + result.cancellationToken = cancellationToken; + + asyncOperation.completed += result.AsyncOperationCompletedHandler; + + if (cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var source = (ResourceRequestCallbackHandlerSource)state; + source.core.TrySetCanceled(source.cancellationToken); + }, result); + } + + TaskTracker.TrackActiveTask(result, 3); + + token = result.core.Version; + return result; + } + + public UnityEngine.Object GetResult(short token) + { + try + { + return core.GetResult(token); + } + finally + { + TryReturn(); + } + } + + void IUniTaskSource.GetResult(short token) + { + GetResult(token); + } + + public UniTaskStatus GetStatus(short token) + { + return core.GetStatus(token); + } + + public UniTaskStatus UnsafeGetStatus() + { + return core.UnsafeGetStatus(); + } + + public void OnCompleted(Action continuation, object state, short token) + { + core.OnCompleted(continuation, state, token); + } + + bool TryReturn() + { + TaskTracker.RemoveTracking(this); + core.Reset(); + asyncOperation.completed -= AsyncOperationCompletedHandler; + asyncOperation = default; + progress = default; + cancellationToken = default; + cancellationTokenRegistration.Dispose(); + return pool.TryPush(this); + } + + void AsyncOperationCompletedHandler(AsyncOperation _) + { + core.TrySetResult(asyncOperation.asset); + } + } + sealed class ResourceRequestConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; @@ -286,7 +489,6 @@ namespace Cysharp.Threading.Tasks ResourceRequest asyncOperation; IProgress progress; CancellationToken cancellationToken; - CancellationTokenRegistration cancellationTokenRegistration; UniTaskCompletionSourceCore core; @@ -294,7 +496,7 @@ namespace Cysharp.Threading.Tasks { } - public static IUniTaskSource Create(ResourceRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, bool cancelImmediately, out short token) + public static IUniTaskSource Create(ResourceRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -309,15 +511,6 @@ namespace Cysharp.Threading.Tasks result.asyncOperation = asyncOperation; result.progress = progress; result.cancellationToken = cancellationToken; - - if (cancelImmediately && cancellationToken.CanBeCanceled) - { - result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => - { - var source = (ResourceRequestConfiguredSource)state; - source.core.TrySetCanceled(source.cancellationToken); - }, result); - } TaskTracker.TrackActiveTask(result, 3); @@ -388,7 +581,6 @@ namespace Cysharp.Threading.Tasks asyncOperation = default; progress = default; cancellationToken = default; - cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } @@ -409,17 +601,24 @@ namespace Cysharp.Threading.Tasks return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } - public static UniTask WithCancellation(this AssetBundleRequest asyncOperation, CancellationToken cancellationToken, bool cancelImmediately) + public static UniTask WithCancellation(this AssetBundleRequest asyncOperation, bool handleImmediately, CancellationToken cancellationToken) { - return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately); + if (handleImmediately) + { + Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); + if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); + if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.asset); + return new UniTask(AssetBundleRequestCallbackHandlerSource.Create(asyncOperation, cancellationToken, out var token), token); + } + return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } - public static UniTask ToUniTask(this AssetBundleRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) + public static UniTask ToUniTask(this AssetBundleRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.asset); - return new UniTask(AssetBundleRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token); + return new UniTask(AssetBundleRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); } public struct AssetBundleRequestAwaiter : ICriticalNotifyCompletion @@ -466,6 +665,110 @@ namespace Cysharp.Threading.Tasks } } + sealed class AssetBundleRequestCallbackHandlerSource : IUniTaskSource, ITaskPoolNode + { + static TaskPool pool; + AssetBundleRequestCallbackHandlerSource nextNode; + public ref AssetBundleRequestCallbackHandlerSource NextNode => ref nextNode; + + static AssetBundleRequestCallbackHandlerSource() + { + TaskPool.RegisterSizeGetter(typeof(AssetBundleRequestCallbackHandlerSource), () => pool.Size); + } + + AssetBundleRequest asyncOperation; + IProgress progress; + CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; + + UniTaskCompletionSourceCore core; + + AssetBundleRequestCallbackHandlerSource() + { + } + + public static IUniTaskSource Create(AssetBundleRequest asyncOperation, CancellationToken cancellationToken, out short token) + { + if (cancellationToken.IsCancellationRequested) + { + return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); + } + + if (!pool.TryPop(out var result)) + { + result = new AssetBundleRequestCallbackHandlerSource(); + } + + result.asyncOperation = asyncOperation; + result.cancellationToken = cancellationToken; + + asyncOperation.completed += result.AsyncOperationCompletedHandler; + + if (cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var source = (AssetBundleRequestCallbackHandlerSource)state; + source.core.TrySetCanceled(source.cancellationToken); + }, result); + } + + TaskTracker.TrackActiveTask(result, 3); + + token = result.core.Version; + return result; + } + + public UnityEngine.Object GetResult(short token) + { + try + { + return core.GetResult(token); + } + finally + { + TryReturn(); + } + } + + void IUniTaskSource.GetResult(short token) + { + GetResult(token); + } + + public UniTaskStatus GetStatus(short token) + { + return core.GetStatus(token); + } + + public UniTaskStatus UnsafeGetStatus() + { + return core.UnsafeGetStatus(); + } + + public void OnCompleted(Action continuation, object state, short token) + { + core.OnCompleted(continuation, state, token); + } + + bool TryReturn() + { + TaskTracker.RemoveTracking(this); + core.Reset(); + asyncOperation.completed -= AsyncOperationCompletedHandler; + asyncOperation = default; + progress = default; + cancellationToken = default; + cancellationTokenRegistration.Dispose(); + return pool.TryPush(this); + } + + void AsyncOperationCompletedHandler(AsyncOperation _) + { + core.TrySetResult(asyncOperation.asset); + } + } + sealed class AssetBundleRequestConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; @@ -480,7 +783,6 @@ namespace Cysharp.Threading.Tasks AssetBundleRequest asyncOperation; IProgress progress; CancellationToken cancellationToken; - CancellationTokenRegistration cancellationTokenRegistration; UniTaskCompletionSourceCore core; @@ -488,7 +790,7 @@ namespace Cysharp.Threading.Tasks { } - public static IUniTaskSource Create(AssetBundleRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, bool cancelImmediately, out short token) + public static IUniTaskSource Create(AssetBundleRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -503,15 +805,6 @@ namespace Cysharp.Threading.Tasks result.asyncOperation = asyncOperation; result.progress = progress; result.cancellationToken = cancellationToken; - - if (cancelImmediately && cancellationToken.CanBeCanceled) - { - result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => - { - var source = (AssetBundleRequestConfiguredSource)state; - source.core.TrySetCanceled(source.cancellationToken); - }, result); - } TaskTracker.TrackActiveTask(result, 3); @@ -582,7 +875,6 @@ namespace Cysharp.Threading.Tasks asyncOperation = default; progress = default; cancellationToken = default; - cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } @@ -604,17 +896,24 @@ namespace Cysharp.Threading.Tasks return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } - public static UniTask WithCancellation(this AssetBundleCreateRequest asyncOperation, CancellationToken cancellationToken, bool cancelImmediately) + public static UniTask WithCancellation(this AssetBundleCreateRequest asyncOperation, bool handleImmediately, CancellationToken cancellationToken) { - return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately); + if (handleImmediately) + { + Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); + if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); + if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.assetBundle); + return new UniTask(AssetBundleCreateRequestCallbackHandlerSource.Create(asyncOperation, cancellationToken, out var token), token); + } + return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } - public static UniTask ToUniTask(this AssetBundleCreateRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) + public static UniTask ToUniTask(this AssetBundleCreateRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.assetBundle); - return new UniTask(AssetBundleCreateRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token); + return new UniTask(AssetBundleCreateRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); } public struct AssetBundleCreateRequestAwaiter : ICriticalNotifyCompletion @@ -661,6 +960,110 @@ namespace Cysharp.Threading.Tasks } } + sealed class AssetBundleCreateRequestCallbackHandlerSource : IUniTaskSource, ITaskPoolNode + { + static TaskPool pool; + AssetBundleCreateRequestCallbackHandlerSource nextNode; + public ref AssetBundleCreateRequestCallbackHandlerSource NextNode => ref nextNode; + + static AssetBundleCreateRequestCallbackHandlerSource() + { + TaskPool.RegisterSizeGetter(typeof(AssetBundleCreateRequestCallbackHandlerSource), () => pool.Size); + } + + AssetBundleCreateRequest asyncOperation; + IProgress progress; + CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; + + UniTaskCompletionSourceCore core; + + AssetBundleCreateRequestCallbackHandlerSource() + { + } + + public static IUniTaskSource Create(AssetBundleCreateRequest asyncOperation, CancellationToken cancellationToken, out short token) + { + if (cancellationToken.IsCancellationRequested) + { + return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); + } + + if (!pool.TryPop(out var result)) + { + result = new AssetBundleCreateRequestCallbackHandlerSource(); + } + + result.asyncOperation = asyncOperation; + result.cancellationToken = cancellationToken; + + asyncOperation.completed += result.AsyncOperationCompletedHandler; + + if (cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var source = (AssetBundleCreateRequestCallbackHandlerSource)state; + source.core.TrySetCanceled(source.cancellationToken); + }, result); + } + + TaskTracker.TrackActiveTask(result, 3); + + token = result.core.Version; + return result; + } + + public AssetBundle GetResult(short token) + { + try + { + return core.GetResult(token); + } + finally + { + TryReturn(); + } + } + + void IUniTaskSource.GetResult(short token) + { + GetResult(token); + } + + public UniTaskStatus GetStatus(short token) + { + return core.GetStatus(token); + } + + public UniTaskStatus UnsafeGetStatus() + { + return core.UnsafeGetStatus(); + } + + public void OnCompleted(Action continuation, object state, short token) + { + core.OnCompleted(continuation, state, token); + } + + bool TryReturn() + { + TaskTracker.RemoveTracking(this); + core.Reset(); + asyncOperation.completed -= AsyncOperationCompletedHandler; + asyncOperation = default; + progress = default; + cancellationToken = default; + cancellationTokenRegistration.Dispose(); + return pool.TryPush(this); + } + + void AsyncOperationCompletedHandler(AsyncOperation _) + { + core.TrySetResult(asyncOperation.assetBundle); + } + } + sealed class AssetBundleCreateRequestConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; @@ -675,7 +1078,6 @@ namespace Cysharp.Threading.Tasks AssetBundleCreateRequest asyncOperation; IProgress progress; CancellationToken cancellationToken; - CancellationTokenRegistration cancellationTokenRegistration; UniTaskCompletionSourceCore core; @@ -683,7 +1085,7 @@ namespace Cysharp.Threading.Tasks { } - public static IUniTaskSource Create(AssetBundleCreateRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, bool cancelImmediately, out short token) + public static IUniTaskSource Create(AssetBundleCreateRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -698,15 +1100,6 @@ namespace Cysharp.Threading.Tasks result.asyncOperation = asyncOperation; result.progress = progress; result.cancellationToken = cancellationToken; - - if (cancelImmediately && cancellationToken.CanBeCanceled) - { - result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => - { - var source = (AssetBundleCreateRequestConfiguredSource)state; - source.core.TrySetCanceled(source.cancellationToken); - }, result); - } TaskTracker.TrackActiveTask(result, 3); @@ -777,7 +1170,6 @@ namespace Cysharp.Threading.Tasks asyncOperation = default; progress = default; cancellationToken = default; - cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } @@ -799,12 +1191,26 @@ namespace Cysharp.Threading.Tasks return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } - public static UniTask WithCancellation(this UnityWebRequestAsyncOperation asyncOperation, CancellationToken cancellationToken, bool cancelImmediately) + public static UniTask WithCancellation(this UnityWebRequestAsyncOperation asyncOperation, bool handleImmediately, CancellationToken cancellationToken) { - return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately); + if (handleImmediately) + { + Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); + if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); + if (asyncOperation.isDone) + { + if (asyncOperation.webRequest.IsError()) + { + return UniTask.FromException(new UnityWebRequestException(asyncOperation.webRequest)); + } + return UniTask.FromResult(asyncOperation.webRequest); + } + return new UniTask(UnityWebRequestAsyncOperationCallbackHandlerSource.Create(asyncOperation, cancellationToken, out var token), token); + } + return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } - public static UniTask ToUniTask(this UnityWebRequestAsyncOperation asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) + public static UniTask ToUniTask(this UnityWebRequestAsyncOperation asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); @@ -816,7 +1222,7 @@ namespace Cysharp.Threading.Tasks } return UniTask.FromResult(asyncOperation.webRequest); } - return new UniTask(UnityWebRequestAsyncOperationConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token); + return new UniTask(UnityWebRequestAsyncOperationConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); } public struct UnityWebRequestAsyncOperationAwaiter : ICriticalNotifyCompletion @@ -871,6 +1277,118 @@ namespace Cysharp.Threading.Tasks } } + sealed class UnityWebRequestAsyncOperationCallbackHandlerSource : IUniTaskSource, ITaskPoolNode + { + static TaskPool pool; + UnityWebRequestAsyncOperationCallbackHandlerSource nextNode; + public ref UnityWebRequestAsyncOperationCallbackHandlerSource NextNode => ref nextNode; + + static UnityWebRequestAsyncOperationCallbackHandlerSource() + { + TaskPool.RegisterSizeGetter(typeof(UnityWebRequestAsyncOperationCallbackHandlerSource), () => pool.Size); + } + + UnityWebRequestAsyncOperation asyncOperation; + IProgress progress; + CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; + + UniTaskCompletionSourceCore core; + + UnityWebRequestAsyncOperationCallbackHandlerSource() + { + } + + public static IUniTaskSource Create(UnityWebRequestAsyncOperation asyncOperation, CancellationToken cancellationToken, out short token) + { + if (cancellationToken.IsCancellationRequested) + { + return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); + } + + if (!pool.TryPop(out var result)) + { + result = new UnityWebRequestAsyncOperationCallbackHandlerSource(); + } + + result.asyncOperation = asyncOperation; + result.cancellationToken = cancellationToken; + + asyncOperation.completed += result.AsyncOperationCompletedHandler; + + if (cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var source = (UnityWebRequestAsyncOperationCallbackHandlerSource)state; + source.asyncOperation.webRequest.Abort(); + source.core.TrySetCanceled(source.cancellationToken); + }, result); + } + + TaskTracker.TrackActiveTask(result, 3); + + token = result.core.Version; + return result; + } + + public UnityWebRequest GetResult(short token) + { + try + { + return core.GetResult(token); + } + finally + { + TryReturn(); + } + } + + void IUniTaskSource.GetResult(short token) + { + GetResult(token); + } + + public UniTaskStatus GetStatus(short token) + { + return core.GetStatus(token); + } + + public UniTaskStatus UnsafeGetStatus() + { + return core.UnsafeGetStatus(); + } + + public void OnCompleted(Action continuation, object state, short token) + { + core.OnCompleted(continuation, state, token); + } + + bool TryReturn() + { + TaskTracker.RemoveTracking(this); + core.Reset(); + asyncOperation.completed -= AsyncOperationCompletedHandler; + asyncOperation = default; + progress = default; + cancellationToken = default; + cancellationTokenRegistration.Dispose(); + return pool.TryPush(this); + } + + void AsyncOperationCompletedHandler(AsyncOperation _) + { + if (asyncOperation.webRequest.IsError()) + { + core.TrySetException(new UnityWebRequestException(asyncOperation.webRequest)); + } + else + { + core.TrySetResult(asyncOperation.webRequest); + } + } + } + sealed class UnityWebRequestAsyncOperationConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; @@ -885,7 +1403,6 @@ namespace Cysharp.Threading.Tasks UnityWebRequestAsyncOperation asyncOperation; IProgress progress; CancellationToken cancellationToken; - CancellationTokenRegistration cancellationTokenRegistration; UniTaskCompletionSourceCore core; @@ -893,7 +1410,7 @@ namespace Cysharp.Threading.Tasks { } - public static IUniTaskSource Create(UnityWebRequestAsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, bool cancelImmediately, out short token) + public static IUniTaskSource Create(UnityWebRequestAsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -908,15 +1425,6 @@ namespace Cysharp.Threading.Tasks result.asyncOperation = asyncOperation; result.progress = progress; result.cancellationToken = cancellationToken; - - if (cancelImmediately && cancellationToken.CanBeCanceled) - { - result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => - { - var promise = (UnityWebRequestAsyncOperationConfiguredSource)state; - promise.core.TrySetCanceled(promise.cancellationToken); - }, result); - } TaskTracker.TrackActiveTask(result, 3); @@ -974,11 +1482,7 @@ namespace Cysharp.Threading.Tasks if (asyncOperation.isDone) { - if (asyncOperation.webRequest == null) - { - core.TrySetException(new ObjectDisposedException("The webRequest has been destroyed.")); - } - else if (asyncOperation.webRequest.IsError()) + if (asyncOperation.webRequest.IsError()) { core.TrySetException(new UnityWebRequestException(asyncOperation.webRequest)); } @@ -999,7 +1503,6 @@ namespace Cysharp.Threading.Tasks asyncOperation = default; progress = default; cancellationToken = default; - cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } } diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt index 65dac9e..f74fa0b 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt @@ -54,6 +54,29 @@ namespace Cysharp.Threading.Tasks return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } + public static <#= ToUniTaskReturnType(t.returnType) #> WithCancellation(this <#= t.typeName #> asyncOperation, bool handleImmediately, CancellationToken cancellationToken) + { + if (handleImmediately) + { + Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); + if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<#= IsVoid(t) ? "" : "<" + t.returnType + ">" #>(cancellationToken); +<# if(IsUnityWebRequest(t)) { #> + if (asyncOperation.isDone) + { + if (asyncOperation.webRequest.IsError()) + { + return UniTask.FromException(new UnityWebRequestException(asyncOperation.webRequest)); + } + return UniTask.FromResult(asyncOperation.webRequest); + } +<# } else { #> + if (asyncOperation.isDone) return <#= IsVoid(t) ? "UniTask.CompletedTask" : $"UniTask.FromResult(asyncOperation.{t.returnField})" #>; +<# } #> + return new <#= ToUniTaskReturnType(t.returnType) #>(<#= t.typeName #>CallbackHandlerSource.Create(asyncOperation, cancellationToken, out var token), token); + } + return ToUniTask(asyncOperation, cancellationToken: cancellationToken); + } + public static <#= ToUniTaskReturnType(t.returnType) #> ToUniTask(this <#= t.typeName #> asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); @@ -137,6 +160,130 @@ namespace Cysharp.Threading.Tasks } } + sealed class <#= t.typeName #>CallbackHandlerSource : <#= ToIUniTaskSourceReturnType(t.returnType) #>, ITaskPoolNode<<#= t.typeName #>CallbackHandlerSource> + { + static TaskPool<<#= t.typeName #>CallbackHandlerSource> pool; + <#= t.typeName #>CallbackHandlerSource nextNode; + public ref <#= t.typeName #>CallbackHandlerSource NextNode => ref nextNode; + + static <#= t.typeName #>CallbackHandlerSource() + { + TaskPool.RegisterSizeGetter(typeof(<#= t.typeName #>CallbackHandlerSource), () => pool.Size); + } + + <#= t.typeName #> asyncOperation; + IProgress progress; + CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; + + UniTaskCompletionSourceCore<<#= IsVoid(t) ? "AsyncUnit" : t.returnType #>> core; + + <#= t.typeName #>CallbackHandlerSource() + { + } + + public static <#= ToIUniTaskSourceReturnType(t.returnType) #> Create(<#= t.typeName #> asyncOperation, CancellationToken cancellationToken, out short token) + { + if (cancellationToken.IsCancellationRequested) + { + return AutoResetUniTaskCompletionSource<#= IsVoid(t) ? "" : $"<{t.returnType}>" #>.CreateFromCanceled(cancellationToken, out token); + } + + if (!pool.TryPop(out var result)) + { + result = new <#= t.typeName #>CallbackHandlerSource(); + } + + result.asyncOperation = asyncOperation; + result.cancellationToken = cancellationToken; + + asyncOperation.completed += result.AsyncOperationCompletedHandler; + + if (cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var source = (<#= t.typeName #>CallbackHandlerSource)state; +<# if(IsUnityWebRequest(t)) { #> + source.asyncOperation.webRequest.Abort(); +<# } #> + source.core.TrySetCanceled(source.cancellationToken); + }, result); + } + + TaskTracker.TrackActiveTask(result, 3); + + token = result.core.Version; + return result; + } + + public <#= t.returnType #> GetResult(short token) + { + try + { +<# if (!IsVoid(t)) { #> + return core.GetResult(token); +<# } else { #> + core.GetResult(token); +<# } #> + } + finally + { + TryReturn(); + } + } + +<# if (!IsVoid(t)) { #> + void IUniTaskSource.GetResult(short token) + { + GetResult(token); + } +<# } #> + + public UniTaskStatus GetStatus(short token) + { + return core.GetStatus(token); + } + + public UniTaskStatus UnsafeGetStatus() + { + return core.UnsafeGetStatus(); + } + + public void OnCompleted(Action continuation, object state, short token) + { + core.OnCompleted(continuation, state, token); + } + + bool TryReturn() + { + TaskTracker.RemoveTracking(this); + core.Reset(); + asyncOperation.completed -= AsyncOperationCompletedHandler; + asyncOperation = default; + progress = default; + cancellationToken = default; + cancellationTokenRegistration.Dispose(); + return pool.TryPush(this); + } + + void AsyncOperationCompletedHandler(AsyncOperation _) + { +<# if(IsUnityWebRequest(t)) { #> + if (asyncOperation.webRequest.IsError()) + { + core.TrySetException(new UnityWebRequestException(asyncOperation.webRequest)); + } + else + { + core.TrySetResult(asyncOperation.webRequest); + } +<# } else { #> + core.TrySetResult(<#= IsVoid(t) ? "AsyncUnit.Default" : $"asyncOperation.{t.returnField}" #>); +<# } #> + } + } + sealed class <#= t.typeName #>ConfiguredSource : <#= ToIUniTaskSourceReturnType(t.returnType) #>, IPlayerLoopItem, ITaskPoolNode<<#= t.typeName #>ConfiguredSource> { static TaskPool<<#= t.typeName #>ConfiguredSource> pool; @@ -156,7 +303,6 @@ namespace Cysharp.Threading.Tasks <#= t.typeName #>ConfiguredSource() { - } public static <#= ToIUniTaskSourceReturnType(t.returnType) #> Create(<#= t.typeName #> asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, out short token) From 55be4dba829f9bcc4617c2371764f123d6c26d27 Mon Sep 17 00:00:00 2001 From: hadashiA Date: Fri, 27 Oct 2023 20:11:20 +0900 Subject: [PATCH 07/13] Add callback handler to AsyncOperationConfiguredSource instead to create other sources --- .../UniTask/Runtime/UnityAsyncExtensions.cs | 821 +++++------------- .../UniTask/Runtime/UnityAsyncExtensions.tt | 206 ++--- 2 files changed, 295 insertions(+), 732 deletions(-) diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs index 069ee5a..dc24ad0 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs @@ -28,22 +28,15 @@ namespace Cysharp.Threading.Tasks public static UniTask WithCancellation(this AsyncOperation asyncOperation, bool handleImmediately, CancellationToken cancellationToken) { - if (handleImmediately) - { - Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); - if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); - if (asyncOperation.isDone) return UniTask.CompletedTask; - return new UniTask(AsyncOperationCallbackHandlerSource.Create(asyncOperation, cancellationToken, out var token), token); - } - return ToUniTask(asyncOperation, cancellationToken: cancellationToken); + return ToUniTask(asyncOperation, handleImmediately: handleImmediately, cancellationToken: cancellationToken); } - public static UniTask ToUniTask(this AsyncOperation asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask ToUniTask(this AsyncOperation asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, bool handleImmediately = false, CancellationToken cancellationToken = default(CancellationToken)) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); if (asyncOperation.isDone) return UniTask.CompletedTask; - return new UniTask(AsyncOperationConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); + return new UniTask(AsyncOperationConfiguredSource.Create(asyncOperation, timing, progress, handleImmediately, cancellationToken, out var token), token); } public struct AsyncOperationAwaiter : ICriticalNotifyCompletion @@ -86,106 +79,6 @@ namespace Cysharp.Threading.Tasks } } - sealed class AsyncOperationCallbackHandlerSource : IUniTaskSource, ITaskPoolNode - { - static TaskPool pool; - AsyncOperationCallbackHandlerSource nextNode; - public ref AsyncOperationCallbackHandlerSource NextNode => ref nextNode; - - static AsyncOperationCallbackHandlerSource() - { - TaskPool.RegisterSizeGetter(typeof(AsyncOperationCallbackHandlerSource), () => pool.Size); - } - - AsyncOperation asyncOperation; - IProgress progress; - CancellationToken cancellationToken; - CancellationTokenRegistration cancellationTokenRegistration; - - UniTaskCompletionSourceCore core; - - AsyncOperationCallbackHandlerSource() - { - } - - public static IUniTaskSource Create(AsyncOperation asyncOperation, CancellationToken cancellationToken, out short token) - { - if (cancellationToken.IsCancellationRequested) - { - return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); - } - - if (!pool.TryPop(out var result)) - { - result = new AsyncOperationCallbackHandlerSource(); - } - - result.asyncOperation = asyncOperation; - result.cancellationToken = cancellationToken; - - asyncOperation.completed += result.AsyncOperationCompletedHandler; - - if (cancellationToken.CanBeCanceled) - { - result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => - { - var source = (AsyncOperationCallbackHandlerSource)state; - source.core.TrySetCanceled(source.cancellationToken); - }, result); - } - - TaskTracker.TrackActiveTask(result, 3); - - token = result.core.Version; - return result; - } - - public void GetResult(short token) - { - try - { - core.GetResult(token); - } - finally - { - TryReturn(); - } - } - - - public UniTaskStatus GetStatus(short token) - { - return core.GetStatus(token); - } - - public UniTaskStatus UnsafeGetStatus() - { - return core.UnsafeGetStatus(); - } - - public void OnCompleted(Action continuation, object state, short token) - { - core.OnCompleted(continuation, state, token); - } - - bool TryReturn() - { - TaskTracker.RemoveTracking(this); - core.Reset(); - asyncOperation.completed -= AsyncOperationCompletedHandler; - asyncOperation = default; - progress = default; - cancellationToken = default; - cancellationTokenRegistration.Dispose(); - return pool.TryPush(this); - } - - void AsyncOperationCompletedHandler(AsyncOperation _) - { - core.TrySetResult(AsyncUnit.Default); - } - } - sealed class AsyncOperationConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; @@ -200,14 +93,18 @@ namespace Cysharp.Threading.Tasks AsyncOperation asyncOperation; IProgress progress; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; + bool completed; UniTaskCompletionSourceCore core; + Action continuationAction; + AsyncOperationConfiguredSource() { } - public static IUniTaskSource Create(AsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(AsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress progress, bool handleImmediately, CancellationToken cancellationToken, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -222,6 +119,22 @@ namespace Cysharp.Threading.Tasks result.asyncOperation = asyncOperation; result.progress = progress; result.cancellationToken = cancellationToken; + result.completed = false; + + if (handleImmediately) + { + result.continuationAction = result.Continuation; + asyncOperation.completed += result.continuationAction; + + if (cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var source = (AsyncOperationConfiguredSource)state; + source.core.TrySetCanceled(source.cancellationToken); + }, result); + } + } TaskTracker.TrackActiveTask(result, 3); @@ -285,11 +198,33 @@ namespace Cysharp.Threading.Tasks { TaskTracker.RemoveTracking(this); core.Reset(); + asyncOperation.completed -= continuationAction; asyncOperation = default; progress = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } + + void Continuation(AsyncOperation _) + { + if (completed) + { + TryReturn(); + } + else + { + completed = true; + if (cancellationToken.IsCancellationRequested) + { + core.TrySetCanceled(cancellationToken); + } + else + { + core.TrySetResult(AsyncUnit.Default); + } + } + } } #endregion @@ -309,22 +244,15 @@ namespace Cysharp.Threading.Tasks public static UniTask WithCancellation(this ResourceRequest asyncOperation, bool handleImmediately, CancellationToken cancellationToken) { - if (handleImmediately) - { - Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); - if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); - if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.asset); - return new UniTask(ResourceRequestCallbackHandlerSource.Create(asyncOperation, cancellationToken, out var token), token); - } - return ToUniTask(asyncOperation, cancellationToken: cancellationToken); + return ToUniTask(asyncOperation, handleImmediately: handleImmediately, cancellationToken: cancellationToken); } - public static UniTask ToUniTask(this ResourceRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask ToUniTask(this ResourceRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, bool handleImmediately = false, CancellationToken cancellationToken = default(CancellationToken)) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.asset); - return new UniTask(ResourceRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); + return new UniTask(ResourceRequestConfiguredSource.Create(asyncOperation, timing, progress, handleImmediately, cancellationToken, out var token), token); } public struct ResourceRequestAwaiter : ICriticalNotifyCompletion @@ -371,110 +299,6 @@ namespace Cysharp.Threading.Tasks } } - sealed class ResourceRequestCallbackHandlerSource : IUniTaskSource, ITaskPoolNode - { - static TaskPool pool; - ResourceRequestCallbackHandlerSource nextNode; - public ref ResourceRequestCallbackHandlerSource NextNode => ref nextNode; - - static ResourceRequestCallbackHandlerSource() - { - TaskPool.RegisterSizeGetter(typeof(ResourceRequestCallbackHandlerSource), () => pool.Size); - } - - ResourceRequest asyncOperation; - IProgress progress; - CancellationToken cancellationToken; - CancellationTokenRegistration cancellationTokenRegistration; - - UniTaskCompletionSourceCore core; - - ResourceRequestCallbackHandlerSource() - { - } - - public static IUniTaskSource Create(ResourceRequest asyncOperation, CancellationToken cancellationToken, out short token) - { - if (cancellationToken.IsCancellationRequested) - { - return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); - } - - if (!pool.TryPop(out var result)) - { - result = new ResourceRequestCallbackHandlerSource(); - } - - result.asyncOperation = asyncOperation; - result.cancellationToken = cancellationToken; - - asyncOperation.completed += result.AsyncOperationCompletedHandler; - - if (cancellationToken.CanBeCanceled) - { - result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => - { - var source = (ResourceRequestCallbackHandlerSource)state; - source.core.TrySetCanceled(source.cancellationToken); - }, result); - } - - TaskTracker.TrackActiveTask(result, 3); - - token = result.core.Version; - return result; - } - - public UnityEngine.Object GetResult(short token) - { - try - { - return core.GetResult(token); - } - finally - { - TryReturn(); - } - } - - void IUniTaskSource.GetResult(short token) - { - GetResult(token); - } - - public UniTaskStatus GetStatus(short token) - { - return core.GetStatus(token); - } - - public UniTaskStatus UnsafeGetStatus() - { - return core.UnsafeGetStatus(); - } - - public void OnCompleted(Action continuation, object state, short token) - { - core.OnCompleted(continuation, state, token); - } - - bool TryReturn() - { - TaskTracker.RemoveTracking(this); - core.Reset(); - asyncOperation.completed -= AsyncOperationCompletedHandler; - asyncOperation = default; - progress = default; - cancellationToken = default; - cancellationTokenRegistration.Dispose(); - return pool.TryPush(this); - } - - void AsyncOperationCompletedHandler(AsyncOperation _) - { - core.TrySetResult(asyncOperation.asset); - } - } - sealed class ResourceRequestConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; @@ -489,14 +313,18 @@ namespace Cysharp.Threading.Tasks ResourceRequest asyncOperation; IProgress progress; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; + bool completed; UniTaskCompletionSourceCore core; + Action continuationAction; + ResourceRequestConfiguredSource() { } - public static IUniTaskSource Create(ResourceRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(ResourceRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, bool handleImmediately, CancellationToken cancellationToken, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -511,6 +339,22 @@ namespace Cysharp.Threading.Tasks result.asyncOperation = asyncOperation; result.progress = progress; result.cancellationToken = cancellationToken; + result.completed = false; + + if (handleImmediately) + { + result.continuationAction = result.Continuation; + asyncOperation.completed += result.continuationAction; + + if (cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var source = (ResourceRequestConfiguredSource)state; + source.core.TrySetCanceled(source.cancellationToken); + }, result); + } + } TaskTracker.TrackActiveTask(result, 3); @@ -578,11 +422,33 @@ namespace Cysharp.Threading.Tasks { TaskTracker.RemoveTracking(this); core.Reset(); + asyncOperation.completed -= continuationAction; asyncOperation = default; progress = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } + + void Continuation(AsyncOperation _) + { + if (completed) + { + TryReturn(); + } + else + { + completed = true; + if (cancellationToken.IsCancellationRequested) + { + core.TrySetCanceled(cancellationToken); + } + else + { + core.TrySetResult(asyncOperation.asset); + } + } + } } #endregion @@ -603,22 +469,15 @@ namespace Cysharp.Threading.Tasks public static UniTask WithCancellation(this AssetBundleRequest asyncOperation, bool handleImmediately, CancellationToken cancellationToken) { - if (handleImmediately) - { - Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); - if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); - if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.asset); - return new UniTask(AssetBundleRequestCallbackHandlerSource.Create(asyncOperation, cancellationToken, out var token), token); - } - return ToUniTask(asyncOperation, cancellationToken: cancellationToken); + return ToUniTask(asyncOperation, handleImmediately: handleImmediately, cancellationToken: cancellationToken); } - public static UniTask ToUniTask(this AssetBundleRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask ToUniTask(this AssetBundleRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, bool handleImmediately = false, CancellationToken cancellationToken = default(CancellationToken)) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.asset); - return new UniTask(AssetBundleRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); + return new UniTask(AssetBundleRequestConfiguredSource.Create(asyncOperation, timing, progress, handleImmediately, cancellationToken, out var token), token); } public struct AssetBundleRequestAwaiter : ICriticalNotifyCompletion @@ -665,110 +524,6 @@ namespace Cysharp.Threading.Tasks } } - sealed class AssetBundleRequestCallbackHandlerSource : IUniTaskSource, ITaskPoolNode - { - static TaskPool pool; - AssetBundleRequestCallbackHandlerSource nextNode; - public ref AssetBundleRequestCallbackHandlerSource NextNode => ref nextNode; - - static AssetBundleRequestCallbackHandlerSource() - { - TaskPool.RegisterSizeGetter(typeof(AssetBundleRequestCallbackHandlerSource), () => pool.Size); - } - - AssetBundleRequest asyncOperation; - IProgress progress; - CancellationToken cancellationToken; - CancellationTokenRegistration cancellationTokenRegistration; - - UniTaskCompletionSourceCore core; - - AssetBundleRequestCallbackHandlerSource() - { - } - - public static IUniTaskSource Create(AssetBundleRequest asyncOperation, CancellationToken cancellationToken, out short token) - { - if (cancellationToken.IsCancellationRequested) - { - return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); - } - - if (!pool.TryPop(out var result)) - { - result = new AssetBundleRequestCallbackHandlerSource(); - } - - result.asyncOperation = asyncOperation; - result.cancellationToken = cancellationToken; - - asyncOperation.completed += result.AsyncOperationCompletedHandler; - - if (cancellationToken.CanBeCanceled) - { - result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => - { - var source = (AssetBundleRequestCallbackHandlerSource)state; - source.core.TrySetCanceled(source.cancellationToken); - }, result); - } - - TaskTracker.TrackActiveTask(result, 3); - - token = result.core.Version; - return result; - } - - public UnityEngine.Object GetResult(short token) - { - try - { - return core.GetResult(token); - } - finally - { - TryReturn(); - } - } - - void IUniTaskSource.GetResult(short token) - { - GetResult(token); - } - - public UniTaskStatus GetStatus(short token) - { - return core.GetStatus(token); - } - - public UniTaskStatus UnsafeGetStatus() - { - return core.UnsafeGetStatus(); - } - - public void OnCompleted(Action continuation, object state, short token) - { - core.OnCompleted(continuation, state, token); - } - - bool TryReturn() - { - TaskTracker.RemoveTracking(this); - core.Reset(); - asyncOperation.completed -= AsyncOperationCompletedHandler; - asyncOperation = default; - progress = default; - cancellationToken = default; - cancellationTokenRegistration.Dispose(); - return pool.TryPush(this); - } - - void AsyncOperationCompletedHandler(AsyncOperation _) - { - core.TrySetResult(asyncOperation.asset); - } - } - sealed class AssetBundleRequestConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; @@ -783,14 +538,18 @@ namespace Cysharp.Threading.Tasks AssetBundleRequest asyncOperation; IProgress progress; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; + bool completed; UniTaskCompletionSourceCore core; + Action continuationAction; + AssetBundleRequestConfiguredSource() { } - public static IUniTaskSource Create(AssetBundleRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(AssetBundleRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, bool handleImmediately, CancellationToken cancellationToken, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -805,6 +564,22 @@ namespace Cysharp.Threading.Tasks result.asyncOperation = asyncOperation; result.progress = progress; result.cancellationToken = cancellationToken; + result.completed = false; + + if (handleImmediately) + { + result.continuationAction = result.Continuation; + asyncOperation.completed += result.continuationAction; + + if (cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var source = (AssetBundleRequestConfiguredSource)state; + source.core.TrySetCanceled(source.cancellationToken); + }, result); + } + } TaskTracker.TrackActiveTask(result, 3); @@ -872,11 +647,33 @@ namespace Cysharp.Threading.Tasks { TaskTracker.RemoveTracking(this); core.Reset(); + asyncOperation.completed -= continuationAction; asyncOperation = default; progress = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } + + void Continuation(AsyncOperation _) + { + if (completed) + { + TryReturn(); + } + else + { + completed = true; + if (cancellationToken.IsCancellationRequested) + { + core.TrySetCanceled(cancellationToken); + } + else + { + core.TrySetResult(asyncOperation.asset); + } + } + } } #endregion @@ -898,22 +695,15 @@ namespace Cysharp.Threading.Tasks public static UniTask WithCancellation(this AssetBundleCreateRequest asyncOperation, bool handleImmediately, CancellationToken cancellationToken) { - if (handleImmediately) - { - Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); - if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); - if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.assetBundle); - return new UniTask(AssetBundleCreateRequestCallbackHandlerSource.Create(asyncOperation, cancellationToken, out var token), token); - } - return ToUniTask(asyncOperation, cancellationToken: cancellationToken); + return ToUniTask(asyncOperation, handleImmediately: handleImmediately, cancellationToken: cancellationToken); } - public static UniTask ToUniTask(this AssetBundleCreateRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask ToUniTask(this AssetBundleCreateRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, bool handleImmediately = false, CancellationToken cancellationToken = default(CancellationToken)) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.assetBundle); - return new UniTask(AssetBundleCreateRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); + return new UniTask(AssetBundleCreateRequestConfiguredSource.Create(asyncOperation, timing, progress, handleImmediately, cancellationToken, out var token), token); } public struct AssetBundleCreateRequestAwaiter : ICriticalNotifyCompletion @@ -960,110 +750,6 @@ namespace Cysharp.Threading.Tasks } } - sealed class AssetBundleCreateRequestCallbackHandlerSource : IUniTaskSource, ITaskPoolNode - { - static TaskPool pool; - AssetBundleCreateRequestCallbackHandlerSource nextNode; - public ref AssetBundleCreateRequestCallbackHandlerSource NextNode => ref nextNode; - - static AssetBundleCreateRequestCallbackHandlerSource() - { - TaskPool.RegisterSizeGetter(typeof(AssetBundleCreateRequestCallbackHandlerSource), () => pool.Size); - } - - AssetBundleCreateRequest asyncOperation; - IProgress progress; - CancellationToken cancellationToken; - CancellationTokenRegistration cancellationTokenRegistration; - - UniTaskCompletionSourceCore core; - - AssetBundleCreateRequestCallbackHandlerSource() - { - } - - public static IUniTaskSource Create(AssetBundleCreateRequest asyncOperation, CancellationToken cancellationToken, out short token) - { - if (cancellationToken.IsCancellationRequested) - { - return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); - } - - if (!pool.TryPop(out var result)) - { - result = new AssetBundleCreateRequestCallbackHandlerSource(); - } - - result.asyncOperation = asyncOperation; - result.cancellationToken = cancellationToken; - - asyncOperation.completed += result.AsyncOperationCompletedHandler; - - if (cancellationToken.CanBeCanceled) - { - result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => - { - var source = (AssetBundleCreateRequestCallbackHandlerSource)state; - source.core.TrySetCanceled(source.cancellationToken); - }, result); - } - - TaskTracker.TrackActiveTask(result, 3); - - token = result.core.Version; - return result; - } - - public AssetBundle GetResult(short token) - { - try - { - return core.GetResult(token); - } - finally - { - TryReturn(); - } - } - - void IUniTaskSource.GetResult(short token) - { - GetResult(token); - } - - public UniTaskStatus GetStatus(short token) - { - return core.GetStatus(token); - } - - public UniTaskStatus UnsafeGetStatus() - { - return core.UnsafeGetStatus(); - } - - public void OnCompleted(Action continuation, object state, short token) - { - core.OnCompleted(continuation, state, token); - } - - bool TryReturn() - { - TaskTracker.RemoveTracking(this); - core.Reset(); - asyncOperation.completed -= AsyncOperationCompletedHandler; - asyncOperation = default; - progress = default; - cancellationToken = default; - cancellationTokenRegistration.Dispose(); - return pool.TryPush(this); - } - - void AsyncOperationCompletedHandler(AsyncOperation _) - { - core.TrySetResult(asyncOperation.assetBundle); - } - } - sealed class AssetBundleCreateRequestConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; @@ -1078,14 +764,18 @@ namespace Cysharp.Threading.Tasks AssetBundleCreateRequest asyncOperation; IProgress progress; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; + bool completed; UniTaskCompletionSourceCore core; + Action continuationAction; + AssetBundleCreateRequestConfiguredSource() { } - public static IUniTaskSource Create(AssetBundleCreateRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(AssetBundleCreateRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, bool handleImmediately, CancellationToken cancellationToken, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -1100,6 +790,22 @@ namespace Cysharp.Threading.Tasks result.asyncOperation = asyncOperation; result.progress = progress; result.cancellationToken = cancellationToken; + result.completed = false; + + if (handleImmediately) + { + result.continuationAction = result.Continuation; + asyncOperation.completed += result.continuationAction; + + if (cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var source = (AssetBundleCreateRequestConfiguredSource)state; + source.core.TrySetCanceled(source.cancellationToken); + }, result); + } + } TaskTracker.TrackActiveTask(result, 3); @@ -1167,11 +873,33 @@ namespace Cysharp.Threading.Tasks { TaskTracker.RemoveTracking(this); core.Reset(); + asyncOperation.completed -= continuationAction; asyncOperation = default; progress = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } + + void Continuation(AsyncOperation _) + { + if (completed) + { + TryReturn(); + } + else + { + completed = true; + if (cancellationToken.IsCancellationRequested) + { + core.TrySetCanceled(cancellationToken); + } + else + { + core.TrySetResult(asyncOperation.assetBundle); + } + } + } } #endregion @@ -1193,24 +921,10 @@ namespace Cysharp.Threading.Tasks public static UniTask WithCancellation(this UnityWebRequestAsyncOperation asyncOperation, bool handleImmediately, CancellationToken cancellationToken) { - if (handleImmediately) - { - Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); - if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); - if (asyncOperation.isDone) - { - if (asyncOperation.webRequest.IsError()) - { - return UniTask.FromException(new UnityWebRequestException(asyncOperation.webRequest)); - } - return UniTask.FromResult(asyncOperation.webRequest); - } - return new UniTask(UnityWebRequestAsyncOperationCallbackHandlerSource.Create(asyncOperation, cancellationToken, out var token), token); - } - return ToUniTask(asyncOperation, cancellationToken: cancellationToken); + return ToUniTask(asyncOperation, handleImmediately: handleImmediately, cancellationToken: cancellationToken); } - public static UniTask ToUniTask(this UnityWebRequestAsyncOperation asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask ToUniTask(this UnityWebRequestAsyncOperation asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, bool handleImmediately = false, CancellationToken cancellationToken = default(CancellationToken)) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); @@ -1222,7 +936,7 @@ namespace Cysharp.Threading.Tasks } return UniTask.FromResult(asyncOperation.webRequest); } - return new UniTask(UnityWebRequestAsyncOperationConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); + return new UniTask(UnityWebRequestAsyncOperationConfiguredSource.Create(asyncOperation, timing, progress, handleImmediately, cancellationToken, out var token), token); } public struct UnityWebRequestAsyncOperationAwaiter : ICriticalNotifyCompletion @@ -1277,118 +991,6 @@ namespace Cysharp.Threading.Tasks } } - sealed class UnityWebRequestAsyncOperationCallbackHandlerSource : IUniTaskSource, ITaskPoolNode - { - static TaskPool pool; - UnityWebRequestAsyncOperationCallbackHandlerSource nextNode; - public ref UnityWebRequestAsyncOperationCallbackHandlerSource NextNode => ref nextNode; - - static UnityWebRequestAsyncOperationCallbackHandlerSource() - { - TaskPool.RegisterSizeGetter(typeof(UnityWebRequestAsyncOperationCallbackHandlerSource), () => pool.Size); - } - - UnityWebRequestAsyncOperation asyncOperation; - IProgress progress; - CancellationToken cancellationToken; - CancellationTokenRegistration cancellationTokenRegistration; - - UniTaskCompletionSourceCore core; - - UnityWebRequestAsyncOperationCallbackHandlerSource() - { - } - - public static IUniTaskSource Create(UnityWebRequestAsyncOperation asyncOperation, CancellationToken cancellationToken, out short token) - { - if (cancellationToken.IsCancellationRequested) - { - return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); - } - - if (!pool.TryPop(out var result)) - { - result = new UnityWebRequestAsyncOperationCallbackHandlerSource(); - } - - result.asyncOperation = asyncOperation; - result.cancellationToken = cancellationToken; - - asyncOperation.completed += result.AsyncOperationCompletedHandler; - - if (cancellationToken.CanBeCanceled) - { - result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => - { - var source = (UnityWebRequestAsyncOperationCallbackHandlerSource)state; - source.asyncOperation.webRequest.Abort(); - source.core.TrySetCanceled(source.cancellationToken); - }, result); - } - - TaskTracker.TrackActiveTask(result, 3); - - token = result.core.Version; - return result; - } - - public UnityWebRequest GetResult(short token) - { - try - { - return core.GetResult(token); - } - finally - { - TryReturn(); - } - } - - void IUniTaskSource.GetResult(short token) - { - GetResult(token); - } - - public UniTaskStatus GetStatus(short token) - { - return core.GetStatus(token); - } - - public UniTaskStatus UnsafeGetStatus() - { - return core.UnsafeGetStatus(); - } - - public void OnCompleted(Action continuation, object state, short token) - { - core.OnCompleted(continuation, state, token); - } - - bool TryReturn() - { - TaskTracker.RemoveTracking(this); - core.Reset(); - asyncOperation.completed -= AsyncOperationCompletedHandler; - asyncOperation = default; - progress = default; - cancellationToken = default; - cancellationTokenRegistration.Dispose(); - return pool.TryPush(this); - } - - void AsyncOperationCompletedHandler(AsyncOperation _) - { - if (asyncOperation.webRequest.IsError()) - { - core.TrySetException(new UnityWebRequestException(asyncOperation.webRequest)); - } - else - { - core.TrySetResult(asyncOperation.webRequest); - } - } - } - sealed class UnityWebRequestAsyncOperationConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; @@ -1403,14 +1005,18 @@ namespace Cysharp.Threading.Tasks UnityWebRequestAsyncOperation asyncOperation; IProgress progress; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; + bool completed; UniTaskCompletionSourceCore core; + Action continuationAction; + UnityWebRequestAsyncOperationConfiguredSource() { } - public static IUniTaskSource Create(UnityWebRequestAsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(UnityWebRequestAsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress progress, bool handleImmediately, CancellationToken cancellationToken, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -1425,6 +1031,23 @@ namespace Cysharp.Threading.Tasks result.asyncOperation = asyncOperation; result.progress = progress; result.cancellationToken = cancellationToken; + result.completed = false; + + if (handleImmediately) + { + result.continuationAction = result.Continuation; + asyncOperation.completed += result.continuationAction; + + if (cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var source = (UnityWebRequestAsyncOperationConfiguredSource)state; + source.asyncOperation.webRequest.Abort(); + source.core.TrySetCanceled(source.cancellationToken); + }, result); + } + } TaskTracker.TrackActiveTask(result, 3); @@ -1500,11 +1123,37 @@ namespace Cysharp.Threading.Tasks { TaskTracker.RemoveTracking(this); core.Reset(); + asyncOperation.completed -= continuationAction; asyncOperation = default; progress = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } + + void Continuation(AsyncOperation _) + { + if (completed) + { + TryReturn(); + } + else + { + completed = true; + if (cancellationToken.IsCancellationRequested) + { + core.TrySetCanceled(cancellationToken); + } + else if (asyncOperation.webRequest.IsError()) + { + core.TrySetException(new UnityWebRequestException(asyncOperation.webRequest)); + } + else + { + core.TrySetResult(asyncOperation.webRequest); + } + } + } } #endregion diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt index f74fa0b..105893d 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt @@ -56,28 +56,10 @@ namespace Cysharp.Threading.Tasks public static <#= ToUniTaskReturnType(t.returnType) #> WithCancellation(this <#= t.typeName #> asyncOperation, bool handleImmediately, CancellationToken cancellationToken) { - if (handleImmediately) - { - Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); - if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<#= IsVoid(t) ? "" : "<" + t.returnType + ">" #>(cancellationToken); -<# if(IsUnityWebRequest(t)) { #> - if (asyncOperation.isDone) - { - if (asyncOperation.webRequest.IsError()) - { - return UniTask.FromException(new UnityWebRequestException(asyncOperation.webRequest)); - } - return UniTask.FromResult(asyncOperation.webRequest); - } -<# } else { #> - if (asyncOperation.isDone) return <#= IsVoid(t) ? "UniTask.CompletedTask" : $"UniTask.FromResult(asyncOperation.{t.returnField})" #>; -<# } #> - return new <#= ToUniTaskReturnType(t.returnType) #>(<#= t.typeName #>CallbackHandlerSource.Create(asyncOperation, cancellationToken, out var token), token); - } - return ToUniTask(asyncOperation, cancellationToken: cancellationToken); + return ToUniTask(asyncOperation, handleImmediately: handleImmediately, cancellationToken: cancellationToken); } - public static <#= ToUniTaskReturnType(t.returnType) #> ToUniTask(this <#= t.typeName #> asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static <#= ToUniTaskReturnType(t.returnType) #> ToUniTask(this <#= t.typeName #> asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, bool handleImmediately = false, CancellationToken cancellationToken = default(CancellationToken)) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<#= IsVoid(t) ? "" : "<" + t.returnType + ">" #>(cancellationToken); @@ -93,7 +75,7 @@ namespace Cysharp.Threading.Tasks <# } else { #> if (asyncOperation.isDone) return <#= IsVoid(t) ? "UniTask.CompletedTask" : $"UniTask.FromResult(asyncOperation.{t.returnField})" #>; <# } #> - return new <#= ToUniTaskReturnType(t.returnType) #>(<#= t.typeName #>ConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); + return new <#= ToUniTaskReturnType(t.returnType) #>(<#= t.typeName #>ConfiguredSource.Create(asyncOperation, timing, progress, handleImmediately, cancellationToken, out var token), token); } public struct <#= t.typeName #>Awaiter : ICriticalNotifyCompletion @@ -160,130 +142,6 @@ namespace Cysharp.Threading.Tasks } } - sealed class <#= t.typeName #>CallbackHandlerSource : <#= ToIUniTaskSourceReturnType(t.returnType) #>, ITaskPoolNode<<#= t.typeName #>CallbackHandlerSource> - { - static TaskPool<<#= t.typeName #>CallbackHandlerSource> pool; - <#= t.typeName #>CallbackHandlerSource nextNode; - public ref <#= t.typeName #>CallbackHandlerSource NextNode => ref nextNode; - - static <#= t.typeName #>CallbackHandlerSource() - { - TaskPool.RegisterSizeGetter(typeof(<#= t.typeName #>CallbackHandlerSource), () => pool.Size); - } - - <#= t.typeName #> asyncOperation; - IProgress progress; - CancellationToken cancellationToken; - CancellationTokenRegistration cancellationTokenRegistration; - - UniTaskCompletionSourceCore<<#= IsVoid(t) ? "AsyncUnit" : t.returnType #>> core; - - <#= t.typeName #>CallbackHandlerSource() - { - } - - public static <#= ToIUniTaskSourceReturnType(t.returnType) #> Create(<#= t.typeName #> asyncOperation, CancellationToken cancellationToken, out short token) - { - if (cancellationToken.IsCancellationRequested) - { - return AutoResetUniTaskCompletionSource<#= IsVoid(t) ? "" : $"<{t.returnType}>" #>.CreateFromCanceled(cancellationToken, out token); - } - - if (!pool.TryPop(out var result)) - { - result = new <#= t.typeName #>CallbackHandlerSource(); - } - - result.asyncOperation = asyncOperation; - result.cancellationToken = cancellationToken; - - asyncOperation.completed += result.AsyncOperationCompletedHandler; - - if (cancellationToken.CanBeCanceled) - { - result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => - { - var source = (<#= t.typeName #>CallbackHandlerSource)state; -<# if(IsUnityWebRequest(t)) { #> - source.asyncOperation.webRequest.Abort(); -<# } #> - source.core.TrySetCanceled(source.cancellationToken); - }, result); - } - - TaskTracker.TrackActiveTask(result, 3); - - token = result.core.Version; - return result; - } - - public <#= t.returnType #> GetResult(short token) - { - try - { -<# if (!IsVoid(t)) { #> - return core.GetResult(token); -<# } else { #> - core.GetResult(token); -<# } #> - } - finally - { - TryReturn(); - } - } - -<# if (!IsVoid(t)) { #> - void IUniTaskSource.GetResult(short token) - { - GetResult(token); - } -<# } #> - - public UniTaskStatus GetStatus(short token) - { - return core.GetStatus(token); - } - - public UniTaskStatus UnsafeGetStatus() - { - return core.UnsafeGetStatus(); - } - - public void OnCompleted(Action continuation, object state, short token) - { - core.OnCompleted(continuation, state, token); - } - - bool TryReturn() - { - TaskTracker.RemoveTracking(this); - core.Reset(); - asyncOperation.completed -= AsyncOperationCompletedHandler; - asyncOperation = default; - progress = default; - cancellationToken = default; - cancellationTokenRegistration.Dispose(); - return pool.TryPush(this); - } - - void AsyncOperationCompletedHandler(AsyncOperation _) - { -<# if(IsUnityWebRequest(t)) { #> - if (asyncOperation.webRequest.IsError()) - { - core.TrySetException(new UnityWebRequestException(asyncOperation.webRequest)); - } - else - { - core.TrySetResult(asyncOperation.webRequest); - } -<# } else { #> - core.TrySetResult(<#= IsVoid(t) ? "AsyncUnit.Default" : $"asyncOperation.{t.returnField}" #>); -<# } #> - } - } - sealed class <#= t.typeName #>ConfiguredSource : <#= ToIUniTaskSourceReturnType(t.returnType) #>, IPlayerLoopItem, ITaskPoolNode<<#= t.typeName #>ConfiguredSource> { static TaskPool<<#= t.typeName #>ConfiguredSource> pool; @@ -298,14 +156,18 @@ namespace Cysharp.Threading.Tasks <#= t.typeName #> asyncOperation; IProgress progress; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; + bool completed; UniTaskCompletionSourceCore<<#= IsVoid(t) ? "AsyncUnit" : t.returnType #>> core; + Action continuationAction; + <#= t.typeName #>ConfiguredSource() { } - public static <#= ToIUniTaskSourceReturnType(t.returnType) #> Create(<#= t.typeName #> asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, out short token) + public static <#= ToIUniTaskSourceReturnType(t.returnType) #> Create(<#= t.typeName #> asyncOperation, PlayerLoopTiming timing, IProgress progress, bool handleImmediately, CancellationToken cancellationToken, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -320,6 +182,25 @@ namespace Cysharp.Threading.Tasks result.asyncOperation = asyncOperation; result.progress = progress; result.cancellationToken = cancellationToken; + result.completed = false; + + if (handleImmediately) + { + result.continuationAction = result.Continuation; + asyncOperation.completed += result.continuationAction; + + if (cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var source = (<#= t.typeName #>ConfiguredSource)state; +<# if(IsUnityWebRequest(t)) { #> + source.asyncOperation.webRequest.Abort(); +<# } #> + source.core.TrySetCanceled(source.cancellationToken); + }, result); + } + } TaskTracker.TrackActiveTask(result, 3); @@ -407,11 +288,44 @@ namespace Cysharp.Threading.Tasks { TaskTracker.RemoveTracking(this); core.Reset(); + asyncOperation.completed -= continuationAction; asyncOperation = default; progress = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } + + void Continuation(AsyncOperation _) + { + if (completed) + { + TryReturn(); + } + else + { + completed = true; + if (cancellationToken.IsCancellationRequested) + { + core.TrySetCanceled(cancellationToken); + } +<# if(IsUnityWebRequest(t)) { #> + else if (asyncOperation.webRequest.IsError()) + { + core.TrySetException(new UnityWebRequestException(asyncOperation.webRequest)); + } + else + { + core.TrySetResult(asyncOperation.webRequest); + } +<# } else { #> + else + { + core.TrySetResult(<#= IsVoid(t) ? "AsyncUnit.Default" : $"asyncOperation.{t.returnField}" #>); + } +<# } #> + } + } } #endregion From 0a203c8db9fc0bf9b68a3c71a9b1c6cd545672fe Mon Sep 17 00:00:00 2001 From: hadashiA Date: Thu, 2 Nov 2023 09:58:52 +0900 Subject: [PATCH 08/13] Cache Action in advance --- .../Plugins/UniTask/Runtime/UnityAsyncExtensions.cs | 10 +++++----- .../Plugins/UniTask/Runtime/UnityAsyncExtensions.tt | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs index dc24ad0..915d55d 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs @@ -102,6 +102,7 @@ namespace Cysharp.Threading.Tasks AsyncOperationConfiguredSource() { + continuationAction = Continuation; } public static IUniTaskSource Create(AsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress progress, bool handleImmediately, CancellationToken cancellationToken, out short token) @@ -123,7 +124,6 @@ namespace Cysharp.Threading.Tasks if (handleImmediately) { - result.continuationAction = result.Continuation; asyncOperation.completed += result.continuationAction; if (cancellationToken.CanBeCanceled) @@ -322,6 +322,7 @@ namespace Cysharp.Threading.Tasks ResourceRequestConfiguredSource() { + continuationAction = Continuation; } public static IUniTaskSource Create(ResourceRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, bool handleImmediately, CancellationToken cancellationToken, out short token) @@ -343,7 +344,6 @@ namespace Cysharp.Threading.Tasks if (handleImmediately) { - result.continuationAction = result.Continuation; asyncOperation.completed += result.continuationAction; if (cancellationToken.CanBeCanceled) @@ -547,6 +547,7 @@ namespace Cysharp.Threading.Tasks AssetBundleRequestConfiguredSource() { + continuationAction = Continuation; } public static IUniTaskSource Create(AssetBundleRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, bool handleImmediately, CancellationToken cancellationToken, out short token) @@ -568,7 +569,6 @@ namespace Cysharp.Threading.Tasks if (handleImmediately) { - result.continuationAction = result.Continuation; asyncOperation.completed += result.continuationAction; if (cancellationToken.CanBeCanceled) @@ -773,6 +773,7 @@ namespace Cysharp.Threading.Tasks AssetBundleCreateRequestConfiguredSource() { + continuationAction = Continuation; } public static IUniTaskSource Create(AssetBundleCreateRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, bool handleImmediately, CancellationToken cancellationToken, out short token) @@ -794,7 +795,6 @@ namespace Cysharp.Threading.Tasks if (handleImmediately) { - result.continuationAction = result.Continuation; asyncOperation.completed += result.continuationAction; if (cancellationToken.CanBeCanceled) @@ -1014,6 +1014,7 @@ namespace Cysharp.Threading.Tasks UnityWebRequestAsyncOperationConfiguredSource() { + continuationAction = Continuation; } public static IUniTaskSource Create(UnityWebRequestAsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress progress, bool handleImmediately, CancellationToken cancellationToken, out short token) @@ -1035,7 +1036,6 @@ namespace Cysharp.Threading.Tasks if (handleImmediately) { - result.continuationAction = result.Continuation; asyncOperation.completed += result.continuationAction; if (cancellationToken.CanBeCanceled) diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt index 105893d..e3e57d4 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt @@ -165,6 +165,7 @@ namespace Cysharp.Threading.Tasks <#= t.typeName #>ConfiguredSource() { + continuationAction = Continuation; } public static <#= ToIUniTaskSourceReturnType(t.returnType) #> Create(<#= t.typeName #> asyncOperation, PlayerLoopTiming timing, IProgress progress, bool handleImmediately, CancellationToken cancellationToken, out short token) @@ -186,7 +187,6 @@ namespace Cysharp.Threading.Tasks if (handleImmediately) { - result.continuationAction = result.Continuation; asyncOperation.completed += result.continuationAction; if (cancellationToken.CanBeCanceled) From 39cf81d2ab57fa616716515b9ba4b1fcda9915cf Mon Sep 17 00:00:00 2001 From: hadashiA Date: Thu, 2 Nov 2023 10:29:42 +0900 Subject: [PATCH 09/13] Add test for unity AsyncOperation --- .../Assets/Tests/AsyncOperationTest.cs | 42 ++++ .../Assets/Tests/AsyncOperationTest.cs.meta | 3 + src/UniTask/Assets/Tests/Resources.meta | 8 + .../Assets/Tests/Resources/sample_texture.png | Bin 0 -> 16432 bytes .../Tests/Resources/sample_texture.png.meta | 208 ++++++++++++++++++ 5 files changed, 261 insertions(+) create mode 100644 src/UniTask/Assets/Tests/AsyncOperationTest.cs create mode 100644 src/UniTask/Assets/Tests/AsyncOperationTest.cs.meta create mode 100644 src/UniTask/Assets/Tests/Resources.meta create mode 100644 src/UniTask/Assets/Tests/Resources/sample_texture.png create mode 100644 src/UniTask/Assets/Tests/Resources/sample_texture.png.meta diff --git a/src/UniTask/Assets/Tests/AsyncOperationTest.cs b/src/UniTask/Assets/Tests/AsyncOperationTest.cs new file mode 100644 index 0000000..fb29218 --- /dev/null +++ b/src/UniTask/Assets/Tests/AsyncOperationTest.cs @@ -0,0 +1,42 @@ +using System.Collections; +using System.Threading; +using Cysharp.Threading.Tasks; +using FluentAssertions; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +#if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6)) +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member + +namespace Cysharp.Threading.TasksTests +{ + public class AsyncOperationTest + { + [UnityTest] + public IEnumerator ResourcesLoad_CancelOnPlayerLoop() => UniTask.ToCoroutine(async () => + { + var cts = new CancellationTokenSource(); + var task = Resources.LoadAsync("sample_texture").ToUniTask(cancellationToken: cts.Token, handleImmediately: false); + + cts.Cancel(); + task.Status.Should().Be(UniTaskStatus.Pending); + + await UniTask.NextFrame(); + task.Status.Should().Be(UniTaskStatus.Canceled); + }); + + [Test] + public void ResourcesLoad_CancelImmediately() + { + { + var cts = new CancellationTokenSource(); + var task = Resources.LoadAsync("sample_texture").ToUniTask(cancellationToken: cts.Token, handleImmediately: true); + + cts.Cancel(); + task.Status.Should().Be(UniTaskStatus.Canceled); + } + } + } +} +#endif diff --git a/src/UniTask/Assets/Tests/AsyncOperationTest.cs.meta b/src/UniTask/Assets/Tests/AsyncOperationTest.cs.meta new file mode 100644 index 0000000..fed3f0d --- /dev/null +++ b/src/UniTask/Assets/Tests/AsyncOperationTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 295d574a16494d6aa4d02fcb32179e39 +timeCreated: 1698887128 \ No newline at end of file diff --git a/src/UniTask/Assets/Tests/Resources.meta b/src/UniTask/Assets/Tests/Resources.meta new file mode 100644 index 0000000..d568559 --- /dev/null +++ b/src/UniTask/Assets/Tests/Resources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8d82913edf6ac48aca30f66ae9ba42d6 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/UniTask/Assets/Tests/Resources/sample_texture.png b/src/UniTask/Assets/Tests/Resources/sample_texture.png new file mode 100644 index 0000000000000000000000000000000000000000..2da89097b331de4063b57842c7a775cb47399716 GIT binary patch literal 16432 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 Date: Thu, 2 Nov 2023 12:01:51 +0900 Subject: [PATCH 10/13] Use AsyncOperation.completed callback usually --- .../UniTask/Runtime/UnityAsyncExtensions.cs | 162 ++++++++++-------- .../UniTask/Runtime/UnityAsyncExtensions.tt | 34 ++-- 2 files changed, 104 insertions(+), 92 deletions(-) diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs index 915d55d..fb84fca 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs @@ -26,17 +26,17 @@ namespace Cysharp.Threading.Tasks return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } - public static UniTask WithCancellation(this AsyncOperation asyncOperation, bool handleImmediately, CancellationToken cancellationToken) + public static UniTask WithCancellation(this AsyncOperation asyncOperation, CancellationToken cancellationToken, bool cancelImmediately) { - return ToUniTask(asyncOperation, handleImmediately: handleImmediately, cancellationToken: cancellationToken); + return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately); } - public static UniTask ToUniTask(this AsyncOperation asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, bool handleImmediately = false, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask ToUniTask(this AsyncOperation asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); if (asyncOperation.isDone) return UniTask.CompletedTask; - return new UniTask(AsyncOperationConfiguredSource.Create(asyncOperation, timing, progress, handleImmediately, cancellationToken, out var token), token); + return new UniTask(AsyncOperationConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token); } public struct AsyncOperationAwaiter : ICriticalNotifyCompletion @@ -105,7 +105,7 @@ namespace Cysharp.Threading.Tasks continuationAction = Continuation; } - public static IUniTaskSource Create(AsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress progress, bool handleImmediately, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(AsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -121,19 +121,16 @@ namespace Cysharp.Threading.Tasks result.progress = progress; result.cancellationToken = cancellationToken; result.completed = false; + + asyncOperation.completed += result.continuationAction; - if (handleImmediately) + if (cancelImmediately && cancellationToken.CanBeCanceled) { - asyncOperation.completed += result.continuationAction; - - if (cancellationToken.CanBeCanceled) + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => { - result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => - { - var source = (AsyncOperationConfiguredSource)state; - source.core.TrySetCanceled(source.cancellationToken); - }, result); - } + var source = (AsyncOperationConfiguredSource)state; + source.core.TrySetCanceled(source.cancellationToken); + }, result); } TaskTracker.TrackActiveTask(result, 3); @@ -180,6 +177,11 @@ namespace Cysharp.Threading.Tasks return false; } + if (asyncOperation == null) + { + return false; + } + if (progress != null) { progress.Report(asyncOperation.progress); @@ -242,17 +244,17 @@ namespace Cysharp.Threading.Tasks return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } - public static UniTask WithCancellation(this ResourceRequest asyncOperation, bool handleImmediately, CancellationToken cancellationToken) + public static UniTask WithCancellation(this ResourceRequest asyncOperation, CancellationToken cancellationToken, bool cancelImmediately) { - return ToUniTask(asyncOperation, handleImmediately: handleImmediately, cancellationToken: cancellationToken); + return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately); } - public static UniTask ToUniTask(this ResourceRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, bool handleImmediately = false, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask ToUniTask(this ResourceRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.asset); - return new UniTask(ResourceRequestConfiguredSource.Create(asyncOperation, timing, progress, handleImmediately, cancellationToken, out var token), token); + return new UniTask(ResourceRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token); } public struct ResourceRequestAwaiter : ICriticalNotifyCompletion @@ -325,7 +327,7 @@ namespace Cysharp.Threading.Tasks continuationAction = Continuation; } - public static IUniTaskSource Create(ResourceRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, bool handleImmediately, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(ResourceRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -341,19 +343,16 @@ namespace Cysharp.Threading.Tasks result.progress = progress; result.cancellationToken = cancellationToken; result.completed = false; + + asyncOperation.completed += result.continuationAction; - if (handleImmediately) + if (cancelImmediately && cancellationToken.CanBeCanceled) { - asyncOperation.completed += result.continuationAction; - - if (cancellationToken.CanBeCanceled) + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => { - result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => - { - var source = (ResourceRequestConfiguredSource)state; - source.core.TrySetCanceled(source.cancellationToken); - }, result); - } + var source = (ResourceRequestConfiguredSource)state; + source.core.TrySetCanceled(source.cancellationToken); + }, result); } TaskTracker.TrackActiveTask(result, 3); @@ -404,6 +403,11 @@ namespace Cysharp.Threading.Tasks return false; } + if (asyncOperation == null) + { + return false; + } + if (progress != null) { progress.Report(asyncOperation.progress); @@ -467,17 +471,17 @@ namespace Cysharp.Threading.Tasks return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } - public static UniTask WithCancellation(this AssetBundleRequest asyncOperation, bool handleImmediately, CancellationToken cancellationToken) + public static UniTask WithCancellation(this AssetBundleRequest asyncOperation, CancellationToken cancellationToken, bool cancelImmediately) { - return ToUniTask(asyncOperation, handleImmediately: handleImmediately, cancellationToken: cancellationToken); + return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately); } - public static UniTask ToUniTask(this AssetBundleRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, bool handleImmediately = false, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask ToUniTask(this AssetBundleRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.asset); - return new UniTask(AssetBundleRequestConfiguredSource.Create(asyncOperation, timing, progress, handleImmediately, cancellationToken, out var token), token); + return new UniTask(AssetBundleRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token); } public struct AssetBundleRequestAwaiter : ICriticalNotifyCompletion @@ -550,7 +554,7 @@ namespace Cysharp.Threading.Tasks continuationAction = Continuation; } - public static IUniTaskSource Create(AssetBundleRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, bool handleImmediately, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(AssetBundleRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -566,19 +570,16 @@ namespace Cysharp.Threading.Tasks result.progress = progress; result.cancellationToken = cancellationToken; result.completed = false; + + asyncOperation.completed += result.continuationAction; - if (handleImmediately) + if (cancelImmediately && cancellationToken.CanBeCanceled) { - asyncOperation.completed += result.continuationAction; - - if (cancellationToken.CanBeCanceled) + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => { - result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => - { - var source = (AssetBundleRequestConfiguredSource)state; - source.core.TrySetCanceled(source.cancellationToken); - }, result); - } + var source = (AssetBundleRequestConfiguredSource)state; + source.core.TrySetCanceled(source.cancellationToken); + }, result); } TaskTracker.TrackActiveTask(result, 3); @@ -629,6 +630,11 @@ namespace Cysharp.Threading.Tasks return false; } + if (asyncOperation == null) + { + return false; + } + if (progress != null) { progress.Report(asyncOperation.progress); @@ -693,17 +699,17 @@ namespace Cysharp.Threading.Tasks return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } - public static UniTask WithCancellation(this AssetBundleCreateRequest asyncOperation, bool handleImmediately, CancellationToken cancellationToken) + public static UniTask WithCancellation(this AssetBundleCreateRequest asyncOperation, CancellationToken cancellationToken, bool cancelImmediately) { - return ToUniTask(asyncOperation, handleImmediately: handleImmediately, cancellationToken: cancellationToken); + return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately); } - public static UniTask ToUniTask(this AssetBundleCreateRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, bool handleImmediately = false, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask ToUniTask(this AssetBundleCreateRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.assetBundle); - return new UniTask(AssetBundleCreateRequestConfiguredSource.Create(asyncOperation, timing, progress, handleImmediately, cancellationToken, out var token), token); + return new UniTask(AssetBundleCreateRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token); } public struct AssetBundleCreateRequestAwaiter : ICriticalNotifyCompletion @@ -776,7 +782,7 @@ namespace Cysharp.Threading.Tasks continuationAction = Continuation; } - public static IUniTaskSource Create(AssetBundleCreateRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, bool handleImmediately, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(AssetBundleCreateRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -792,19 +798,16 @@ namespace Cysharp.Threading.Tasks result.progress = progress; result.cancellationToken = cancellationToken; result.completed = false; + + asyncOperation.completed += result.continuationAction; - if (handleImmediately) + if (cancelImmediately && cancellationToken.CanBeCanceled) { - asyncOperation.completed += result.continuationAction; - - if (cancellationToken.CanBeCanceled) + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => { - result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => - { - var source = (AssetBundleCreateRequestConfiguredSource)state; - source.core.TrySetCanceled(source.cancellationToken); - }, result); - } + var source = (AssetBundleCreateRequestConfiguredSource)state; + source.core.TrySetCanceled(source.cancellationToken); + }, result); } TaskTracker.TrackActiveTask(result, 3); @@ -855,6 +858,11 @@ namespace Cysharp.Threading.Tasks return false; } + if (asyncOperation == null) + { + return false; + } + if (progress != null) { progress.Report(asyncOperation.progress); @@ -919,12 +927,12 @@ namespace Cysharp.Threading.Tasks return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } - public static UniTask WithCancellation(this UnityWebRequestAsyncOperation asyncOperation, bool handleImmediately, CancellationToken cancellationToken) + public static UniTask WithCancellation(this UnityWebRequestAsyncOperation asyncOperation, CancellationToken cancellationToken, bool cancelImmediately) { - return ToUniTask(asyncOperation, handleImmediately: handleImmediately, cancellationToken: cancellationToken); + return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately); } - public static UniTask ToUniTask(this UnityWebRequestAsyncOperation asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, bool handleImmediately = false, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask ToUniTask(this UnityWebRequestAsyncOperation asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); @@ -936,7 +944,7 @@ namespace Cysharp.Threading.Tasks } return UniTask.FromResult(asyncOperation.webRequest); } - return new UniTask(UnityWebRequestAsyncOperationConfiguredSource.Create(asyncOperation, timing, progress, handleImmediately, cancellationToken, out var token), token); + return new UniTask(UnityWebRequestAsyncOperationConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token); } public struct UnityWebRequestAsyncOperationAwaiter : ICriticalNotifyCompletion @@ -1017,7 +1025,7 @@ namespace Cysharp.Threading.Tasks continuationAction = Continuation; } - public static IUniTaskSource Create(UnityWebRequestAsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress progress, bool handleImmediately, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(UnityWebRequestAsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -1033,20 +1041,17 @@ namespace Cysharp.Threading.Tasks result.progress = progress; result.cancellationToken = cancellationToken; result.completed = false; + + asyncOperation.completed += result.continuationAction; - if (handleImmediately) + if (cancelImmediately && cancellationToken.CanBeCanceled) { - asyncOperation.completed += result.continuationAction; - - if (cancellationToken.CanBeCanceled) + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => { - result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => - { - var source = (UnityWebRequestAsyncOperationConfiguredSource)state; - source.asyncOperation.webRequest.Abort(); - source.core.TrySetCanceled(source.cancellationToken); - }, result); - } + var source = (UnityWebRequestAsyncOperationConfiguredSource)state; + source.asyncOperation.webRequest.Abort(); + source.core.TrySetCanceled(source.cancellationToken); + }, result); } TaskTracker.TrackActiveTask(result, 3); @@ -1098,6 +1103,11 @@ namespace Cysharp.Threading.Tasks return false; } + if (asyncOperation == null) + { + return false; + } + if (progress != null) { progress.Report(asyncOperation.progress); diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt index e3e57d4..86dd72a 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt @@ -54,12 +54,12 @@ namespace Cysharp.Threading.Tasks return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } - public static <#= ToUniTaskReturnType(t.returnType) #> WithCancellation(this <#= t.typeName #> asyncOperation, bool handleImmediately, CancellationToken cancellationToken) + public static <#= ToUniTaskReturnType(t.returnType) #> WithCancellation(this <#= t.typeName #> asyncOperation, CancellationToken cancellationToken, bool cancelImmediately) { - return ToUniTask(asyncOperation, handleImmediately: handleImmediately, cancellationToken: cancellationToken); + return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately); } - public static <#= ToUniTaskReturnType(t.returnType) #> ToUniTask(this <#= t.typeName #> asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, bool handleImmediately = false, CancellationToken cancellationToken = default(CancellationToken)) + public static <#= ToUniTaskReturnType(t.returnType) #> ToUniTask(this <#= t.typeName #> asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<#= IsVoid(t) ? "" : "<" + t.returnType + ">" #>(cancellationToken); @@ -75,7 +75,7 @@ namespace Cysharp.Threading.Tasks <# } else { #> if (asyncOperation.isDone) return <#= IsVoid(t) ? "UniTask.CompletedTask" : $"UniTask.FromResult(asyncOperation.{t.returnField})" #>; <# } #> - return new <#= ToUniTaskReturnType(t.returnType) #>(<#= t.typeName #>ConfiguredSource.Create(asyncOperation, timing, progress, handleImmediately, cancellationToken, out var token), token); + return new <#= ToUniTaskReturnType(t.returnType) #>(<#= t.typeName #>ConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token); } public struct <#= t.typeName #>Awaiter : ICriticalNotifyCompletion @@ -168,7 +168,7 @@ namespace Cysharp.Threading.Tasks continuationAction = Continuation; } - public static <#= ToIUniTaskSourceReturnType(t.returnType) #> Create(<#= t.typeName #> asyncOperation, PlayerLoopTiming timing, IProgress progress, bool handleImmediately, CancellationToken cancellationToken, out short token) + public static <#= ToIUniTaskSourceReturnType(t.returnType) #> Create(<#= t.typeName #> asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -184,22 +184,19 @@ namespace Cysharp.Threading.Tasks result.progress = progress; result.cancellationToken = cancellationToken; result.completed = false; + + asyncOperation.completed += result.continuationAction; - if (handleImmediately) + if (cancelImmediately && cancellationToken.CanBeCanceled) { - asyncOperation.completed += result.continuationAction; - - if (cancellationToken.CanBeCanceled) + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => { - result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => - { - var source = (<#= t.typeName #>ConfiguredSource)state; + var source = (<#= t.typeName #>ConfiguredSource)state; <# if(IsUnityWebRequest(t)) { #> - source.asyncOperation.webRequest.Abort(); + source.asyncOperation.webRequest.Abort(); <# } #> - source.core.TrySetCanceled(source.cancellationToken); - }, result); - } + source.core.TrySetCanceled(source.cancellationToken); + }, result); } TaskTracker.TrackActiveTask(result, 3); @@ -259,6 +256,11 @@ namespace Cysharp.Threading.Tasks return false; } + if (asyncOperation == null) + { + return false; + } + if (progress != null) { progress.Report(asyncOperation.progress); From 1b76f7760855c62307a0dc3b7371ab72a1028568 Mon Sep 17 00:00:00 2001 From: hadashiA Date: Thu, 2 Nov 2023 12:02:04 +0900 Subject: [PATCH 11/13] Add test --- .../Assets/Tests/AsyncOperationTest.cs | 52 ++++++++++++++++++- 1 file changed, 50 insertions(+), 2 deletions(-) diff --git a/src/UniTask/Assets/Tests/AsyncOperationTest.cs b/src/UniTask/Assets/Tests/AsyncOperationTest.cs index fb29218..0eaa188 100644 --- a/src/UniTask/Assets/Tests/AsyncOperationTest.cs +++ b/src/UniTask/Assets/Tests/AsyncOperationTest.cs @@ -4,6 +4,7 @@ using Cysharp.Threading.Tasks; using FluentAssertions; using NUnit.Framework; using UnityEngine; +using UnityEngine.Networking; using UnityEngine.TestTools; #if CSHARP_7_OR_LATER || (UNITY_2018_3_OR_NEWER && (NET_STANDARD_2_0 || NET_4_6)) @@ -13,11 +14,20 @@ namespace Cysharp.Threading.TasksTests { public class AsyncOperationTest { + [UnityTest] + public IEnumerator ResourcesLoad_Completed() => UniTask.ToCoroutine(async () => + { + var asyncOperation = Resources.LoadAsync("sample_texture"); + await asyncOperation.ToUniTask(); + asyncOperation.isDone.Should().BeTrue(); + asyncOperation.asset.GetType().Should().Be(typeof(Texture2D)); + }); + [UnityTest] public IEnumerator ResourcesLoad_CancelOnPlayerLoop() => UniTask.ToCoroutine(async () => { var cts = new CancellationTokenSource(); - var task = Resources.LoadAsync("sample_texture").ToUniTask(cancellationToken: cts.Token, handleImmediately: false); + var task = Resources.LoadAsync("sample_texture").ToUniTask(cancellationToken: cts.Token, cancelImmediately: false); cts.Cancel(); task.Status.Should().Be(UniTaskStatus.Pending); @@ -31,12 +41,50 @@ namespace Cysharp.Threading.TasksTests { { var cts = new CancellationTokenSource(); - var task = Resources.LoadAsync("sample_texture").ToUniTask(cancellationToken: cts.Token, handleImmediately: true); + var task = Resources.LoadAsync("sample_texture").ToUniTask(cancellationToken: cts.Token, cancelImmediately: true); cts.Cancel(); task.Status.Should().Be(UniTaskStatus.Canceled); } } + +#if ENABLE_UNITYWEBREQUEST && (!UNITY_2019_1_OR_NEWER || UNITASK_WEBREQUEST_SUPPORT) + [UnityTest] + public IEnumerator UnityWebRequest_Completed() => UniTask.ToCoroutine(async () => + { + var filePath = System.IO.Path.Combine(Application.dataPath, "Tests", "Resources", "sample_texture.png"); + var asyncOperation = UnityWebRequest.Get($"file://{filePath}").SendWebRequest(); + await asyncOperation.ToUniTask(); + + asyncOperation.isDone.Should().BeTrue(); + asyncOperation.webRequest.result.Should().Be(UnityWebRequest.Result.Success); + }); + + [UnityTest] + public IEnumerator UnityWebRequest_CancelOnPlayerLoop() => UniTask.ToCoroutine(async () => + { + var cts = new CancellationTokenSource(); + var filePath = System.IO.Path.Combine(Application.dataPath, "Tests", "Resources", "sample_texture.png"); + var task = UnityWebRequest.Get($"file://{filePath}").SendWebRequest().ToUniTask(cancellationToken: cts.Token); + + cts.Cancel(); + task.Status.Should().Be(UniTaskStatus.Pending); + + await UniTask.NextFrame(); + task.Status.Should().Be(UniTaskStatus.Canceled); + }); + + [Test] + public void UnityWebRequest_CancelImmediately() + { + var cts = new CancellationTokenSource(); + cts.Cancel(); + var filePath = System.IO.Path.Combine(Application.dataPath, "Tests", "Resources", "sample_texture.png"); + var task = UnityWebRequest.Get($"file://{filePath}").SendWebRequest().ToUniTask(cancellationToken: cts.Token, cancelImmediately: true); + + task.Status.Should().Be(UniTaskStatus.Canceled); + } +#endif } } #endif From 370425578f40d5728078f560982546f51b3f8aec Mon Sep 17 00:00:00 2001 From: hadashiA Date: Thu, 2 Nov 2023 12:21:59 +0900 Subject: [PATCH 12/13] Fix AssetBundleRequestAll --- ...cExtensions.AssetBundleRequestAllAssets.cs | 163 ++++++------------ .../UnityAsyncExtensions.AsyncGPUReadback.cs | 2 +- .../UniTask/Runtime/UnityAsyncExtensions.cs | 39 +++-- .../UniTask/Runtime/UnityAsyncExtensions.tt | 11 +- 4 files changed, 78 insertions(+), 137 deletions(-) diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AssetBundleRequestAllAssets.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AssetBundleRequestAllAssets.cs index f6fccc0..5d34692 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AssetBundleRequestAllAssets.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AssetBundleRequestAllAssets.cs @@ -24,24 +24,17 @@ namespace Cysharp.Threading.Tasks return AwaitForAllAssets(asyncOperation, null, PlayerLoopTiming.Update, cancellationToken: cancellationToken); } - public static UniTask AwaitForAllAssets(this AssetBundleRequest asyncOperation, bool handleImmediately, CancellationToken cancellationToken) + public static UniTask AwaitForAllAssets(this AssetBundleRequest asyncOperation, CancellationToken cancellationToken, bool cancelImmediately) { - if (handleImmediately) - { - Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); - if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); - if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.allAssets); - return new UniTask(AssetBundleRequestAllAssetsCallbackHandlerSource.Create(asyncOperation, cancellationToken, out var token), token); - } - return AwaitForAllAssets(asyncOperation, progress: null, cancellationToken: cancellationToken); + return AwaitForAllAssets(asyncOperation, progress: null, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately); } - public static UniTask AwaitForAllAssets(this AssetBundleRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) + public static UniTask AwaitForAllAssets(this AssetBundleRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.allAssets); - return new UniTask(AssetBundleRequestAllAssetsConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); + return new UniTask(AssetBundleRequestAllAssetsConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token); } public struct AssetBundleRequestAllAssetsAwaiter : ICriticalNotifyCompletion @@ -92,108 +85,6 @@ namespace Cysharp.Threading.Tasks asyncOperation.completed += continuationAction; } } - - sealed class AssetBundleRequestAllAssetsCallbackHandlerSource : IUniTaskSource, ITaskPoolNode - { - static TaskPool pool; - AssetBundleRequestAllAssetsCallbackHandlerSource nextNode; - public ref AssetBundleRequestAllAssetsCallbackHandlerSource NextNode => ref nextNode; - - static AssetBundleRequestAllAssetsCallbackHandlerSource() - { - TaskPool.RegisterSizeGetter(typeof(AssetBundleRequestConfiguredSource), () => pool.Size); - } - - AssetBundleRequest asyncOperation; - CancellationToken cancellationToken; - CancellationTokenRegistration cancellationTokenRegistration; - - UniTaskCompletionSourceCore core; - - AssetBundleRequestAllAssetsCallbackHandlerSource() - { - } - - public static IUniTaskSource Create(AssetBundleRequest asyncOperation, CancellationToken cancellationToken, out short token) - { - if (cancellationToken.IsCancellationRequested) - { - return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); - } - - if (!pool.TryPop(out var result)) - { - result = new AssetBundleRequestAllAssetsCallbackHandlerSource(); - } - - result.asyncOperation = asyncOperation; - result.cancellationToken = cancellationToken; - - asyncOperation.completed += result.AsyncOperationCompletedHandler; - - if (cancellationToken.CanBeCanceled) - { - result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => - { - var source = (AssetBundleRequestAllAssetsCallbackHandlerSource)state; - source.core.TrySetCanceled(source.cancellationToken); - }, result); - } - - TaskTracker.TrackActiveTask(result, 3); - - token = result.core.Version; - return result; - } - - public UnityEngine.Object[] GetResult(short token) - { - try - { - return core.GetResult(token); - } - finally - { - TryReturn(); - } - } - - void IUniTaskSource.GetResult(short token) - { - GetResult(token); - } - - public UniTaskStatus GetStatus(short token) - { - return core.GetStatus(token); - } - - public UniTaskStatus UnsafeGetStatus() - { - return core.UnsafeGetStatus(); - } - - public void OnCompleted(Action continuation, object state, short token) - { - core.OnCompleted(continuation, state, token); - } - - bool TryReturn() - { - TaskTracker.RemoveTracking(this); - core.Reset(); - asyncOperation.completed -= AsyncOperationCompletedHandler; - asyncOperation = default; - cancellationToken = default; - cancellationTokenRegistration.Dispose(); - return pool.TryPush(this); - } - - void AsyncOperationCompletedHandler(AsyncOperation _) - { - core.TrySetResult(asyncOperation.allAssets); - } - } sealed class AssetBundleRequestAllAssetsConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { @@ -209,14 +100,19 @@ namespace Cysharp.Threading.Tasks AssetBundleRequest asyncOperation; IProgress progress; CancellationToken cancellationToken; + CancellationTokenRegistration cancellationTokenRegistration; + bool completed; UniTaskCompletionSourceCore core; + Action continuationAction; + AssetBundleRequestAllAssetsConfiguredSource() { + continuationAction = Continuation; } - public static IUniTaskSource Create(AssetBundleRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, out short token) + public static IUniTaskSource Create(AssetBundleRequest asyncOperation, PlayerLoopTiming timing, IProgress progress, CancellationToken cancellationToken, bool cancelImmediately, out short token) { if (cancellationToken.IsCancellationRequested) { @@ -231,6 +127,18 @@ namespace Cysharp.Threading.Tasks result.asyncOperation = asyncOperation; result.progress = progress; result.cancellationToken = cancellationToken; + result.completed = false; + + asyncOperation.completed += result.continuationAction; + + if (cancelImmediately && cancellationToken.CanBeCanceled) + { + result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state => + { + var source = (AssetBundleRequestAllAssetsConfiguredSource)state; + source.core.TrySetCanceled(source.cancellationToken); + }, result); + } TaskTracker.TrackActiveTask(result, 3); @@ -274,6 +182,12 @@ namespace Cysharp.Threading.Tasks public bool MoveNext() { + // Already completed + if (completed || asyncOperation == null) + { + return false; + } + if (cancellationToken.IsCancellationRequested) { core.TrySetCanceled(cancellationToken); @@ -301,8 +215,29 @@ namespace Cysharp.Threading.Tasks asyncOperation = default; progress = default; cancellationToken = default; + cancellationTokenRegistration.Dispose(); return pool.TryPush(this); } + + void Continuation(AsyncOperation _) + { + if (completed) + { + TryReturn(); + } + else + { + completed = true; + if (cancellationToken.IsCancellationRequested) + { + core.TrySetCanceled(cancellationToken); + } + else + { + core.TrySetResult(asyncOperation.allAssets); + } + } + } } } } diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AsyncGPUReadback.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AsyncGPUReadback.cs index 0faf0be..5d73dc1 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AsyncGPUReadback.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AsyncGPUReadback.cs @@ -30,7 +30,7 @@ namespace Cysharp.Threading.Tasks if (asyncOperation.done) return UniTask.FromResult(asyncOperation); return new UniTask(AsyncGPUReadbackRequestAwaiterConfiguredSource.Create(asyncOperation, timing, cancellationToken, cancelImmediately, out var token), token); } - + sealed class AsyncGPUReadbackRequestAwaiterConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs index fb84fca..7d86361 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs @@ -171,14 +171,15 @@ namespace Cysharp.Threading.Tasks public bool MoveNext() { - if (cancellationToken.IsCancellationRequested) + // Already completed + if (completed || asyncOperation == null) { - core.TrySetCanceled(cancellationToken); return false; } - if (asyncOperation == null) + if (cancellationToken.IsCancellationRequested) { + core.TrySetCanceled(cancellationToken); return false; } @@ -397,14 +398,15 @@ namespace Cysharp.Threading.Tasks public bool MoveNext() { - if (cancellationToken.IsCancellationRequested) + // Already completed + if (completed || asyncOperation == null) { - core.TrySetCanceled(cancellationToken); return false; } - if (asyncOperation == null) + if (cancellationToken.IsCancellationRequested) { + core.TrySetCanceled(cancellationToken); return false; } @@ -624,14 +626,15 @@ namespace Cysharp.Threading.Tasks public bool MoveNext() { - if (cancellationToken.IsCancellationRequested) + // Already completed + if (completed || asyncOperation == null) { - core.TrySetCanceled(cancellationToken); return false; } - if (asyncOperation == null) + if (cancellationToken.IsCancellationRequested) { + core.TrySetCanceled(cancellationToken); return false; } @@ -852,14 +855,15 @@ namespace Cysharp.Threading.Tasks public bool MoveNext() { - if (cancellationToken.IsCancellationRequested) + // Already completed + if (completed || asyncOperation == null) { - core.TrySetCanceled(cancellationToken); return false; } - if (asyncOperation == null) + if (cancellationToken.IsCancellationRequested) { + core.TrySetCanceled(cancellationToken); return false; } @@ -1096,6 +1100,12 @@ namespace Cysharp.Threading.Tasks public bool MoveNext() { + // Already completed + if (completed || asyncOperation == null) + { + return false; + } + if (cancellationToken.IsCancellationRequested) { asyncOperation.webRequest.Abort(); @@ -1103,11 +1113,6 @@ namespace Cysharp.Threading.Tasks return false; } - if (asyncOperation == null) - { - return false; - } - if (progress != null) { progress.Report(asyncOperation.progress); diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt index 86dd72a..6c6ff91 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt @@ -247,6 +247,12 @@ namespace Cysharp.Threading.Tasks public bool MoveNext() { + // Already completed + if (completed || asyncOperation == null) + { + return false; + } + if (cancellationToken.IsCancellationRequested) { <# if(IsUnityWebRequest(t)) { #> @@ -256,11 +262,6 @@ namespace Cysharp.Threading.Tasks return false; } - if (asyncOperation == null) - { - return false; - } - if (progress != null) { progress.Report(asyncOperation.progress); From ad23f7fb29b8c706dd8acc1b7858cdd4968ce7b4 Mon Sep 17 00:00:00 2001 From: hadashiA Date: Thu, 2 Nov 2023 12:52:24 +0900 Subject: [PATCH 13/13] Fix `#if` directive that should be in .tt --- .../Plugins/UniTask/Runtime/UnityAsyncExtensions.cs | 3 +++ .../Plugins/UniTask/Runtime/UnityAsyncExtensions.tt | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs index 7d86361..b9cd1c9 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs @@ -15,11 +15,14 @@ namespace Cysharp.Threading.Tasks { #region AsyncOperation +#if !UNITY_2023_1_OR_NEWER + // from Unity2023.1.0a15, AsyncOperationAwaitableExtensions.GetAwaiter is defined in UnityEngine. public static AsyncOperationAwaiter GetAwaiter(this AsyncOperation asyncOperation) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); return new AsyncOperationAwaiter(asyncOperation); } +#endif public static UniTask WithCancellation(this AsyncOperation asyncOperation, CancellationToken cancellationToken) { diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt index 6c6ff91..0516fef 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt @@ -16,6 +16,7 @@ Func ToUniTaskReturnType = x => (x == "void") ? "UniTask" : $"UniTask<{x}>"; Func ToIUniTaskSourceReturnType = x => (x == "void") ? "IUniTaskSource" : $"IUniTaskSource<{x}>"; + Func<(string typeName, string returnType, string returnField), bool> IsAsyncOperationBase = x => x.typeName == "AsyncOperation"; Func<(string typeName, string returnType, string returnField), bool> IsUnityWebRequest = x => x.returnType == "UnityWebRequest"; Func<(string typeName, string returnType, string returnField), bool> IsAssetBundleModule = x => x.typeName == "AssetBundleRequest" || x.typeName == "AssetBundleCreateRequest"; Func<(string typeName, string returnType, string returnField), bool> IsVoid = x => x.returnType == "void"; @@ -43,11 +44,18 @@ namespace Cysharp.Threading.Tasks <# } #> #region <#= t.typeName #> +<# if (IsAsyncOperationBase(t)) { #> +#if !UNITY_2023_1_OR_NEWER + // from Unity2023.1.0a15, AsyncOperationAwaitableExtensions.GetAwaiter is defined in UnityEngine. +<# } #> public static <#= t.typeName #>Awaiter GetAwaiter(this <#= t.typeName #> asyncOperation) { Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); return new <#= t.typeName #>Awaiter(asyncOperation); } +<# if (IsAsyncOperationBase(t)) { #> +#endif +<# } #> public static <#= ToUniTaskReturnType(t.returnType) #> WithCancellation(this <#= t.typeName #> asyncOperation, CancellationToken cancellationToken) {