Merge pull request #517 from Cysharp/hadashiA/cancel-immediately-flag

Add a flag to cancel immediately instead of player loop
master
hadashiA 2023-11-02 13:52:12 +09:00 committed by GitHub
commit 0970ae8c31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1144 additions and 157 deletions

View File

@ -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. 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 handling
--- ---
Timeout is a variation of cancellation. You can set timeout by `CancellationTokenSouce.CancelAfterSlim(TimeSpan)` and pass CancellationToken to async methods. Timeout is a variation of cancellation. You can set timeout by `CancellationTokenSouce.CancelAfterSlim(TimeSpan)` and pass CancellationToken to async methods.

View File

@ -25,7 +25,12 @@ namespace Cysharp.Threading.Tasks
return ToUniTask(handle, cancellationToken: cancellationToken); return ToUniTask(handle, cancellationToken: cancellationToken);
} }
public static UniTask ToUniTask(this AsyncOperationHandle handle, IProgress<float> 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<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
{ {
if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken);
@ -44,7 +49,7 @@ namespace Cysharp.Threading.Tasks
return UniTask.CompletedTask; 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 public struct AsyncOperationHandleAwaiter : ICriticalNotifyCompletion
@ -106,6 +111,7 @@ namespace Cysharp.Threading.Tasks
readonly Action<AsyncOperationHandle> continuationAction; readonly Action<AsyncOperationHandle> continuationAction;
AsyncOperationHandle handle; AsyncOperationHandle handle;
CancellationToken cancellationToken; CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
IProgress<float> progress; IProgress<float> progress;
bool completed; bool completed;
@ -116,7 +122,7 @@ namespace Cysharp.Threading.Tasks
continuationAction = Continuation; continuationAction = Continuation;
} }
public static IUniTaskSource Create(AsyncOperationHandle handle, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, out short token) public static IUniTaskSource Create(AsyncOperationHandle handle, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, bool cancelImmediately, out short token)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
@ -133,6 +139,15 @@ namespace Cysharp.Threading.Tasks
result.cancellationToken = cancellationToken; result.cancellationToken = cancellationToken;
result.completed = false; 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); TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(timing, result); PlayerLoopHelper.AddAction(timing, result);
@ -219,6 +234,7 @@ namespace Cysharp.Threading.Tasks
handle = default; handle = default;
progress = default; progress = default;
cancellationToken = default; cancellationToken = default;
cancellationTokenRegistration.Dispose();
return pool.TryPush(this); return pool.TryPush(this);
} }
} }
@ -237,7 +253,12 @@ namespace Cysharp.Threading.Tasks
return ToUniTask(handle, cancellationToken: cancellationToken); return ToUniTask(handle, cancellationToken: cancellationToken);
} }
public static UniTask<T> ToUniTask<T>(this AsyncOperationHandle<T> handle, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) public static UniTask<T> WithCancellation<T>(this AsyncOperationHandle<T> handle, CancellationToken cancellationToken, bool cancelImmediately)
{
return ToUniTask(handle, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately);
}
public static UniTask<T> ToUniTask<T>(this AsyncOperationHandle<T> handle, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
{ {
if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<T>(cancellationToken); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<T>(cancellationToken);
@ -255,7 +276,7 @@ namespace Cysharp.Threading.Tasks
return UniTask.FromResult(handle.Result); return UniTask.FromResult(handle.Result);
} }
return new UniTask<T>(AsyncOperationHandleConfiguredSource<T>.Create(handle, timing, progress, cancellationToken, out var token), token); return new UniTask<T>(AsyncOperationHandleConfiguredSource<T>.Create(handle, timing, progress, cancellationToken, cancelImmediately, out var token), token);
} }
sealed class AsyncOperationHandleConfiguredSource<T> : IUniTaskSource<T>, IPlayerLoopItem, ITaskPoolNode<AsyncOperationHandleConfiguredSource<T>> sealed class AsyncOperationHandleConfiguredSource<T> : IUniTaskSource<T>, IPlayerLoopItem, ITaskPoolNode<AsyncOperationHandleConfiguredSource<T>>
@ -272,6 +293,7 @@ namespace Cysharp.Threading.Tasks
readonly Action<AsyncOperationHandle<T>> continuationAction; readonly Action<AsyncOperationHandle<T>> continuationAction;
AsyncOperationHandle<T> handle; AsyncOperationHandle<T> handle;
CancellationToken cancellationToken; CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
IProgress<float> progress; IProgress<float> progress;
bool completed; bool completed;
@ -282,7 +304,7 @@ namespace Cysharp.Threading.Tasks
continuationAction = Continuation; continuationAction = Continuation;
} }
public static IUniTaskSource<T> Create(AsyncOperationHandle<T> handle, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, out short token) public static IUniTaskSource<T> Create(AsyncOperationHandle<T> handle, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, bool cancelImmediately, out short token)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
@ -299,6 +321,15 @@ namespace Cysharp.Threading.Tasks
result.completed = false; result.completed = false;
result.progress = progress; result.progress = progress;
if (cancelImmediately && cancellationToken.CanBeCanceled)
{
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state =>
{
var promise = (AsyncOperationHandleConfiguredSource<T>)state;
promise.core.TrySetCanceled(promise.cancellationToken);
}, result);
}
TaskTracker.TrackActiveTask(result, 3); TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(timing, result); PlayerLoopHelper.AddAction(timing, result);
@ -390,6 +421,7 @@ namespace Cysharp.Threading.Tasks
handle = default; handle = default;
progress = default; progress = default;
cancellationToken = default; cancellationToken = default;
cancellationTokenRegistration.Dispose();
return pool.TryPush(this); return pool.TryPush(this);
} }
} }

View File

@ -4,38 +4,50 @@ namespace Cysharp.Threading.Tasks.Linq
{ {
public static partial class UniTaskAsyncEnumerable public static partial class UniTaskAsyncEnumerable
{ {
public static IUniTaskAsyncEnumerable<AsyncUnit> EveryUpdate(PlayerLoopTiming updateTiming = PlayerLoopTiming.Update) public static IUniTaskAsyncEnumerable<AsyncUnit> EveryUpdate(PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool cancelImmediately = false)
{ {
return new EveryUpdate(updateTiming); return new EveryUpdate(updateTiming, cancelImmediately);
} }
} }
internal class EveryUpdate : IUniTaskAsyncEnumerable<AsyncUnit> internal class EveryUpdate : IUniTaskAsyncEnumerable<AsyncUnit>
{ {
readonly PlayerLoopTiming updateTiming; readonly PlayerLoopTiming updateTiming;
readonly bool cancelImmediately;
public EveryUpdate(PlayerLoopTiming updateTiming) public EveryUpdate(PlayerLoopTiming updateTiming, bool cancelImmediately)
{ {
this.updateTiming = updateTiming; this.updateTiming = updateTiming;
this.cancelImmediately = cancelImmediately;
} }
public IUniTaskAsyncEnumerator<AsyncUnit> GetAsyncEnumerator(CancellationToken cancellationToken = default) public IUniTaskAsyncEnumerator<AsyncUnit> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{ {
return new _EveryUpdate(updateTiming, cancellationToken); return new _EveryUpdate(updateTiming, cancellationToken, cancelImmediately);
} }
class _EveryUpdate : MoveNextSource, IUniTaskAsyncEnumerator<AsyncUnit>, IPlayerLoopItem class _EveryUpdate : MoveNextSource, IUniTaskAsyncEnumerator<AsyncUnit>, IPlayerLoopItem
{ {
readonly PlayerLoopTiming updateTiming; readonly PlayerLoopTiming updateTiming;
CancellationToken cancellationToken; readonly CancellationToken cancellationToken;
readonly CancellationTokenRegistration cancellationTokenRegistration;
bool disposed; bool disposed;
public _EveryUpdate(PlayerLoopTiming updateTiming, CancellationToken cancellationToken) public _EveryUpdate(PlayerLoopTiming updateTiming, CancellationToken cancellationToken, bool cancelImmediately)
{ {
this.updateTiming = updateTiming; this.updateTiming = updateTiming;
this.cancellationToken = cancellationToken; 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); TaskTracker.TrackActiveTask(this, 2);
PlayerLoopHelper.AddAction(updateTiming, this); PlayerLoopHelper.AddAction(updateTiming, this);
} }
@ -44,10 +56,14 @@ namespace Cysharp.Threading.Tasks.Linq
public UniTask<bool> MoveNextAsync() public UniTask<bool> MoveNextAsync()
{ {
// return false instead of throw if (disposed) return CompletedTasks.False;
if (disposed || cancellationToken.IsCancellationRequested) return CompletedTasks.False;
completionSource.Reset(); completionSource.Reset();
if (cancellationToken.IsCancellationRequested)
{
completionSource.TrySetCanceled(cancellationToken);
}
return new UniTask<bool>(this, completionSource.Version); return new UniTask<bool>(this, completionSource.Version);
} }
@ -55,6 +71,7 @@ namespace Cysharp.Threading.Tasks.Linq
{ {
if (!disposed) if (!disposed)
{ {
cancellationTokenRegistration.Dispose();
disposed = true; disposed = true;
TaskTracker.RemoveTracking(this); TaskTracker.RemoveTracking(this);
} }
@ -63,7 +80,13 @@ namespace Cysharp.Threading.Tasks.Linq
public bool MoveNext() public bool MoveNext()
{ {
if (disposed || cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{
completionSource.TrySetCanceled(cancellationToken);
return false;
}
if (disposed)
{ {
completionSource.TrySetResult(false); completionSource.TrySetResult(false);
return false; return false;

View File

@ -7,7 +7,7 @@ namespace Cysharp.Threading.Tasks.Linq
{ {
public static partial class UniTaskAsyncEnumerable public static partial class UniTaskAsyncEnumerable
{ {
public static IUniTaskAsyncEnumerable<TProperty> EveryValueChanged<TTarget, TProperty>(TTarget target, Func<TTarget, TProperty> propertySelector, PlayerLoopTiming monitorTiming = PlayerLoopTiming.Update, IEqualityComparer<TProperty> equalityComparer = null) public static IUniTaskAsyncEnumerable<TProperty> EveryValueChanged<TTarget, TProperty>(TTarget target, Func<TTarget, TProperty> propertySelector, PlayerLoopTiming monitorTiming = PlayerLoopTiming.Update, IEqualityComparer<TProperty> equalityComparer = null, bool cancelImmediately = false)
where TTarget : class where TTarget : class
{ {
var unityObject = target as UnityEngine.Object; var unityObject = target as UnityEngine.Object;
@ -15,11 +15,11 @@ namespace Cysharp.Threading.Tasks.Linq
if (isUnityObject) if (isUnityObject)
{ {
return new EveryValueChangedUnityObject<TTarget, TProperty>(target, propertySelector, equalityComparer ?? UnityEqualityComparer.GetDefault<TProperty>(), monitorTiming); return new EveryValueChangedUnityObject<TTarget, TProperty>(target, propertySelector, equalityComparer ?? UnityEqualityComparer.GetDefault<TProperty>(), monitorTiming, cancelImmediately);
} }
else else
{ {
return new EveryValueChangedStandardObject<TTarget, TProperty>(target, propertySelector, equalityComparer ?? UnityEqualityComparer.GetDefault<TProperty>(), monitorTiming); return new EveryValueChangedStandardObject<TTarget, TProperty>(target, propertySelector, equalityComparer ?? UnityEqualityComparer.GetDefault<TProperty>(), monitorTiming, cancelImmediately);
} }
} }
} }
@ -30,18 +30,20 @@ namespace Cysharp.Threading.Tasks.Linq
readonly Func<TTarget, TProperty> propertySelector; readonly Func<TTarget, TProperty> propertySelector;
readonly IEqualityComparer<TProperty> equalityComparer; readonly IEqualityComparer<TProperty> equalityComparer;
readonly PlayerLoopTiming monitorTiming; readonly PlayerLoopTiming monitorTiming;
readonly bool cancelImmediately;
public EveryValueChangedUnityObject(TTarget target, Func<TTarget, TProperty> propertySelector, IEqualityComparer<TProperty> equalityComparer, PlayerLoopTiming monitorTiming) public EveryValueChangedUnityObject(TTarget target, Func<TTarget, TProperty> propertySelector, IEqualityComparer<TProperty> equalityComparer, PlayerLoopTiming monitorTiming, bool cancelImmediately)
{ {
this.target = target; this.target = target;
this.propertySelector = propertySelector; this.propertySelector = propertySelector;
this.equalityComparer = equalityComparer; this.equalityComparer = equalityComparer;
this.monitorTiming = monitorTiming; this.monitorTiming = monitorTiming;
this.cancelImmediately = cancelImmediately;
} }
public IUniTaskAsyncEnumerator<TProperty> GetAsyncEnumerator(CancellationToken cancellationToken = default) public IUniTaskAsyncEnumerator<TProperty> 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<TProperty>, IPlayerLoopItem sealed class _EveryValueChanged : MoveNextSource, IUniTaskAsyncEnumerator<TProperty>, IPlayerLoopItem
@ -50,13 +52,14 @@ namespace Cysharp.Threading.Tasks.Linq
readonly UnityEngine.Object targetAsUnityObject; readonly UnityEngine.Object targetAsUnityObject;
readonly IEqualityComparer<TProperty> equalityComparer; readonly IEqualityComparer<TProperty> equalityComparer;
readonly Func<TTarget, TProperty> propertySelector; readonly Func<TTarget, TProperty> propertySelector;
CancellationToken cancellationToken; readonly CancellationToken cancellationToken;
readonly CancellationTokenRegistration cancellationTokenRegistration;
bool first; bool first;
TProperty currentValue; TProperty currentValue;
bool disposed; bool disposed;
public _EveryValueChanged(TTarget target, Func<TTarget, TProperty> propertySelector, IEqualityComparer<TProperty> equalityComparer, PlayerLoopTiming monitorTiming, CancellationToken cancellationToken) public _EveryValueChanged(TTarget target, Func<TTarget, TProperty> propertySelector, IEqualityComparer<TProperty> equalityComparer, PlayerLoopTiming monitorTiming, CancellationToken cancellationToken, bool cancelImmediately)
{ {
this.target = target; this.target = target;
this.targetAsUnityObject = target as UnityEngine.Object; this.targetAsUnityObject = target as UnityEngine.Object;
@ -64,6 +67,16 @@ namespace Cysharp.Threading.Tasks.Linq
this.equalityComparer = equalityComparer; this.equalityComparer = equalityComparer;
this.cancellationToken = cancellationToken; this.cancellationToken = cancellationToken;
this.first = true; 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); TaskTracker.TrackActiveTask(this, 2);
PlayerLoopHelper.AddAction(monitorTiming, this); PlayerLoopHelper.AddAction(monitorTiming, this);
} }
@ -72,8 +85,15 @@ namespace Cysharp.Threading.Tasks.Linq
public UniTask<bool> MoveNextAsync() public UniTask<bool> MoveNextAsync()
{ {
// return false instead of throw if (disposed) return CompletedTasks.False;
if (disposed || cancellationToken.IsCancellationRequested) return CompletedTasks.False;
completionSource.Reset();
if (cancellationToken.IsCancellationRequested)
{
completionSource.TrySetCanceled(cancellationToken);
return new UniTask<bool>(this, completionSource.Version);
}
if (first) if (first)
{ {
@ -86,7 +106,6 @@ namespace Cysharp.Threading.Tasks.Linq
return CompletedTasks.True; return CompletedTasks.True;
} }
completionSource.Reset();
return new UniTask<bool>(this, completionSource.Version); return new UniTask<bool>(this, completionSource.Version);
} }
@ -94,6 +113,7 @@ namespace Cysharp.Threading.Tasks.Linq
{ {
if (!disposed) if (!disposed)
{ {
cancellationTokenRegistration.Dispose();
disposed = true; disposed = true;
TaskTracker.RemoveTracking(this); TaskTracker.RemoveTracking(this);
} }
@ -102,13 +122,18 @@ namespace Cysharp.Threading.Tasks.Linq
public bool MoveNext() public bool MoveNext()
{ {
if (disposed || cancellationToken.IsCancellationRequested || targetAsUnityObject == null) // destroyed = cancel. if (disposed || targetAsUnityObject == null)
{ {
completionSource.TrySetResult(false); completionSource.TrySetResult(false);
DisposeAsync().Forget(); DisposeAsync().Forget();
return false; return false;
} }
if (cancellationToken.IsCancellationRequested)
{
completionSource.TrySetCanceled(cancellationToken);
return false;
}
TProperty nextValue = default(TProperty); TProperty nextValue = default(TProperty);
try try
{ {
@ -139,18 +164,20 @@ namespace Cysharp.Threading.Tasks.Linq
readonly Func<TTarget, TProperty> propertySelector; readonly Func<TTarget, TProperty> propertySelector;
readonly IEqualityComparer<TProperty> equalityComparer; readonly IEqualityComparer<TProperty> equalityComparer;
readonly PlayerLoopTiming monitorTiming; readonly PlayerLoopTiming monitorTiming;
readonly bool cancelImmediately;
public EveryValueChangedStandardObject(TTarget target, Func<TTarget, TProperty> propertySelector, IEqualityComparer<TProperty> equalityComparer, PlayerLoopTiming monitorTiming) public EveryValueChangedStandardObject(TTarget target, Func<TTarget, TProperty> propertySelector, IEqualityComparer<TProperty> equalityComparer, PlayerLoopTiming monitorTiming, bool cancelImmediately)
{ {
this.target = new WeakReference<TTarget>(target, false); this.target = new WeakReference<TTarget>(target, false);
this.propertySelector = propertySelector; this.propertySelector = propertySelector;
this.equalityComparer = equalityComparer; this.equalityComparer = equalityComparer;
this.monitorTiming = monitorTiming; this.monitorTiming = monitorTiming;
this.cancelImmediately = cancelImmediately;
} }
public IUniTaskAsyncEnumerator<TProperty> GetAsyncEnumerator(CancellationToken cancellationToken = default) public IUniTaskAsyncEnumerator<TProperty> 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<TProperty>, IPlayerLoopItem sealed class _EveryValueChanged : MoveNextSource, IUniTaskAsyncEnumerator<TProperty>, IPlayerLoopItem
@ -158,19 +185,30 @@ namespace Cysharp.Threading.Tasks.Linq
readonly WeakReference<TTarget> target; readonly WeakReference<TTarget> target;
readonly IEqualityComparer<TProperty> equalityComparer; readonly IEqualityComparer<TProperty> equalityComparer;
readonly Func<TTarget, TProperty> propertySelector; readonly Func<TTarget, TProperty> propertySelector;
CancellationToken cancellationToken; readonly CancellationToken cancellationToken;
readonly CancellationTokenRegistration cancellationTokenRegistration;
bool first; bool first;
TProperty currentValue; TProperty currentValue;
bool disposed; bool disposed;
public _EveryValueChanged(WeakReference<TTarget> target, Func<TTarget, TProperty> propertySelector, IEqualityComparer<TProperty> equalityComparer, PlayerLoopTiming monitorTiming, CancellationToken cancellationToken) public _EveryValueChanged(WeakReference<TTarget> target, Func<TTarget, TProperty> propertySelector, IEqualityComparer<TProperty> equalityComparer, PlayerLoopTiming monitorTiming, CancellationToken cancellationToken, bool cancelImmediately)
{ {
this.target = target; this.target = target;
this.propertySelector = propertySelector; this.propertySelector = propertySelector;
this.equalityComparer = equalityComparer; this.equalityComparer = equalityComparer;
this.cancellationToken = cancellationToken; this.cancellationToken = cancellationToken;
this.first = true; 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); TaskTracker.TrackActiveTask(this, 2);
PlayerLoopHelper.AddAction(monitorTiming, this); PlayerLoopHelper.AddAction(monitorTiming, this);
} }
@ -179,7 +217,15 @@ namespace Cysharp.Threading.Tasks.Linq
public UniTask<bool> MoveNextAsync() public UniTask<bool> MoveNextAsync()
{ {
if (disposed || cancellationToken.IsCancellationRequested) return CompletedTasks.False; if (disposed) return CompletedTasks.False;
completionSource.Reset();
if (cancellationToken.IsCancellationRequested)
{
completionSource.TrySetCanceled(cancellationToken);
return new UniTask<bool>(this, completionSource.Version);
}
if (first) if (first)
{ {
@ -192,7 +238,6 @@ namespace Cysharp.Threading.Tasks.Linq
return CompletedTasks.True; return CompletedTasks.True;
} }
completionSource.Reset();
return new UniTask<bool>(this, completionSource.Version); return new UniTask<bool>(this, completionSource.Version);
} }
@ -200,6 +245,7 @@ namespace Cysharp.Threading.Tasks.Linq
{ {
if (!disposed) if (!disposed)
{ {
cancellationTokenRegistration.Dispose();
disposed = true; disposed = true;
TaskTracker.RemoveTracking(this); TaskTracker.RemoveTracking(this);
} }
@ -208,13 +254,19 @@ namespace Cysharp.Threading.Tasks.Linq
public bool MoveNext() public bool MoveNext()
{ {
if (disposed || cancellationToken.IsCancellationRequested || !target.TryGetTarget(out var t)) if (disposed || !target.TryGetTarget(out var t))
{ {
completionSource.TrySetResult(false); completionSource.TrySetResult(false);
DisposeAsync().Forget(); DisposeAsync().Forget();
return false; return false;
} }
if (cancellationToken.IsCancellationRequested)
{
completionSource.TrySetCanceled(cancellationToken);
return false;
}
TProperty nextValue = default(TProperty); TProperty nextValue = default(TProperty);
try try
{ {

View File

@ -6,32 +6,32 @@ namespace Cysharp.Threading.Tasks.Linq
{ {
public static partial class UniTaskAsyncEnumerable public static partial class UniTaskAsyncEnumerable
{ {
public static IUniTaskAsyncEnumerable<AsyncUnit> Timer(TimeSpan dueTime, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool ignoreTimeScale = false) public static IUniTaskAsyncEnumerable<AsyncUnit> 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<AsyncUnit> Timer(TimeSpan dueTime, TimeSpan period, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool ignoreTimeScale = false) public static IUniTaskAsyncEnumerable<AsyncUnit> 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<AsyncUnit> Interval(TimeSpan period, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool ignoreTimeScale = false) public static IUniTaskAsyncEnumerable<AsyncUnit> 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<AsyncUnit> TimerFrame(int dueTimeFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update) public static IUniTaskAsyncEnumerable<AsyncUnit> TimerFrame(int dueTimeFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool cancelImmediately = false)
{ {
if (dueTimeFrameCount < 0) if (dueTimeFrameCount < 0)
{ {
throw new ArgumentOutOfRangeException("Delay does not allow minus delayFrameCount. dueTimeFrameCount:" + dueTimeFrameCount); 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<AsyncUnit> TimerFrame(int dueTimeFrameCount, int periodFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update) public static IUniTaskAsyncEnumerable<AsyncUnit> TimerFrame(int dueTimeFrameCount, int periodFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool cancelImmediately = false)
{ {
if (dueTimeFrameCount < 0) if (dueTimeFrameCount < 0)
{ {
@ -42,16 +42,16 @@ namespace Cysharp.Threading.Tasks.Linq
throw new ArgumentOutOfRangeException("Delay does not allow minus periodFrameCount. periodFrameCount:" + dueTimeFrameCount); 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<AsyncUnit> IntervalFrame(int intervalFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update) public static IUniTaskAsyncEnumerable<AsyncUnit> IntervalFrame(int intervalFrameCount, PlayerLoopTiming updateTiming = PlayerLoopTiming.Update, bool cancelImmediately = false)
{ {
if (intervalFrameCount < 0) if (intervalFrameCount < 0)
{ {
throw new ArgumentOutOfRangeException("Delay does not allow minus intervalFrameCount. intervalFrameCount:" + intervalFrameCount); 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 dueTime;
readonly TimeSpan? period; readonly TimeSpan? period;
readonly bool ignoreTimeScale; 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.updateTiming = updateTiming;
this.dueTime = dueTime; this.dueTime = dueTime;
this.period = period; this.period = period;
this.ignoreTimeScale = ignoreTimeScale; this.ignoreTimeScale = ignoreTimeScale;
this.cancelImmediately = cancelImmediately;
} }
public IUniTaskAsyncEnumerator<AsyncUnit> GetAsyncEnumerator(CancellationToken cancellationToken = default) public IUniTaskAsyncEnumerator<AsyncUnit> 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<AsyncUnit>, IPlayerLoopItem class _Timer : MoveNextSource, IUniTaskAsyncEnumerator<AsyncUnit>, IPlayerLoopItem
@ -81,7 +83,8 @@ namespace Cysharp.Threading.Tasks.Linq
readonly float? period; readonly float? period;
readonly PlayerLoopTiming updateTiming; readonly PlayerLoopTiming updateTiming;
readonly bool ignoreTimeScale; readonly bool ignoreTimeScale;
CancellationToken cancellationToken; readonly CancellationToken cancellationToken;
readonly CancellationTokenRegistration cancellationTokenRegistration;
int initialFrame; int initialFrame;
float elapsed; float elapsed;
@ -89,7 +92,7 @@ namespace Cysharp.Threading.Tasks.Linq
bool completed; bool completed;
bool disposed; 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.dueTime = (float)dueTime.TotalSeconds;
this.period = (period == null) ? null : (float?)period.Value.TotalSeconds; this.period = (period == null) ? null : (float?)period.Value.TotalSeconds;
@ -105,6 +108,16 @@ namespace Cysharp.Threading.Tasks.Linq
this.updateTiming = updateTiming; this.updateTiming = updateTiming;
this.ignoreTimeScale = ignoreTimeScale; this.ignoreTimeScale = ignoreTimeScale;
this.cancellationToken = cancellationToken; 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); TaskTracker.TrackActiveTask(this, 2);
PlayerLoopHelper.AddAction(updateTiming, this); PlayerLoopHelper.AddAction(updateTiming, this);
} }
@ -114,12 +127,16 @@ namespace Cysharp.Threading.Tasks.Linq
public UniTask<bool> MoveNextAsync() public UniTask<bool> MoveNextAsync()
{ {
// return false instead of throw // return false instead of throw
if (disposed || cancellationToken.IsCancellationRequested || completed) return CompletedTasks.False; if (disposed || completed) return CompletedTasks.False;
// reset value here. // reset value here.
this.elapsed = 0; this.elapsed = 0;
completionSource.Reset(); completionSource.Reset();
if (cancellationToken.IsCancellationRequested)
{
completionSource.TrySetCanceled(cancellationToken);
}
return new UniTask<bool>(this, completionSource.Version); return new UniTask<bool>(this, completionSource.Version);
} }
@ -127,6 +144,7 @@ namespace Cysharp.Threading.Tasks.Linq
{ {
if (!disposed) if (!disposed)
{ {
cancellationTokenRegistration.Dispose();
disposed = true; disposed = true;
TaskTracker.RemoveTracking(this); TaskTracker.RemoveTracking(this);
} }
@ -135,11 +153,16 @@ namespace Cysharp.Threading.Tasks.Linq
public bool MoveNext() public bool MoveNext()
{ {
if (disposed || cancellationToken.IsCancellationRequested) if (disposed)
{ {
completionSource.TrySetResult(false); completionSource.TrySetResult(false);
return false; return false;
} }
if (cancellationToken.IsCancellationRequested)
{
completionSource.TrySetCanceled(cancellationToken);
return false;
}
if (dueTimePhase) if (dueTimePhase)
{ {
@ -187,24 +210,27 @@ namespace Cysharp.Threading.Tasks.Linq
readonly PlayerLoopTiming updateTiming; readonly PlayerLoopTiming updateTiming;
readonly int dueTimeFrameCount; readonly int dueTimeFrameCount;
readonly int? periodFrameCount; 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.updateTiming = updateTiming;
this.dueTimeFrameCount = dueTimeFrameCount; this.dueTimeFrameCount = dueTimeFrameCount;
this.periodFrameCount = periodFrameCount; this.periodFrameCount = periodFrameCount;
this.cancelImmediately = cancelImmediately;
} }
public IUniTaskAsyncEnumerator<AsyncUnit> GetAsyncEnumerator(CancellationToken cancellationToken = default) public IUniTaskAsyncEnumerator<AsyncUnit> GetAsyncEnumerator(CancellationToken cancellationToken = default)
{ {
return new _TimerFrame(dueTimeFrameCount, periodFrameCount, updateTiming, cancellationToken); return new _TimerFrame(dueTimeFrameCount, periodFrameCount, updateTiming, cancellationToken, cancelImmediately);
} }
class _TimerFrame : MoveNextSource, IUniTaskAsyncEnumerator<AsyncUnit>, IPlayerLoopItem class _TimerFrame : MoveNextSource, IUniTaskAsyncEnumerator<AsyncUnit>, IPlayerLoopItem
{ {
readonly int dueTimeFrameCount; readonly int dueTimeFrameCount;
readonly int? periodFrameCount; readonly int? periodFrameCount;
CancellationToken cancellationToken; readonly CancellationToken cancellationToken;
readonly CancellationTokenRegistration cancellationTokenRegistration;
int initialFrame; int initialFrame;
int currentFrame; int currentFrame;
@ -212,7 +238,7 @@ namespace Cysharp.Threading.Tasks.Linq
bool completed; bool completed;
bool disposed; 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 (dueTimeFrameCount <= 0) dueTimeFrameCount = 0;
if (periodFrameCount != null) if (periodFrameCount != null)
@ -226,6 +252,15 @@ namespace Cysharp.Threading.Tasks.Linq
this.periodFrameCount = periodFrameCount; this.periodFrameCount = periodFrameCount;
this.cancellationToken = cancellationToken; 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); TaskTracker.TrackActiveTask(this, 2);
PlayerLoopHelper.AddAction(updateTiming, this); PlayerLoopHelper.AddAction(updateTiming, this);
} }
@ -234,13 +269,15 @@ namespace Cysharp.Threading.Tasks.Linq
public UniTask<bool> MoveNextAsync() public UniTask<bool> MoveNextAsync()
{ {
// return false instead of throw if (disposed || completed) return CompletedTasks.False;
if (disposed || cancellationToken.IsCancellationRequested || completed) return CompletedTasks.False;
if (cancellationToken.IsCancellationRequested)
{
completionSource.TrySetCanceled(cancellationToken);
}
// reset value here. // reset value here.
this.currentFrame = 0; this.currentFrame = 0;
completionSource.Reset(); completionSource.Reset();
return new UniTask<bool>(this, completionSource.Version); return new UniTask<bool>(this, completionSource.Version);
} }
@ -249,6 +286,7 @@ namespace Cysharp.Threading.Tasks.Linq
{ {
if (!disposed) if (!disposed)
{ {
cancellationTokenRegistration.Dispose();
disposed = true; disposed = true;
TaskTracker.RemoveTracking(this); TaskTracker.RemoveTracking(this);
} }
@ -257,7 +295,12 @@ namespace Cysharp.Threading.Tasks.Linq
public bool MoveNext() public bool MoveNext()
{ {
if (disposed || cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{
completionSource.TrySetCanceled(cancellationToken);
return false;
}
if (disposed)
{ {
completionSource.TrySetResult(false); completionSource.TrySetResult(false);
return false; return false;

View File

@ -33,14 +33,14 @@ namespace Cysharp.Threading.Tasks
return new YieldAwaitable(timing); 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);
} }
/// <summary> /// <summary>
@ -48,7 +48,7 @@ namespace Cysharp.Threading.Tasks
/// </summary> /// </summary>
public static UniTask NextFrame() 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);
} }
/// <summary> /// <summary>
@ -56,23 +56,23 @@ namespace Cysharp.Threading.Tasks
/// </summary> /// </summary>
public static UniTask NextFrame(PlayerLoopTiming timing) 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);
} }
/// <summary> /// <summary>
/// Similar as UniTask.Yield but guaranteed run on next frame. /// Similar as UniTask.Yield but guaranteed run on next frame.
/// </summary> /// </summary>
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);
} }
/// <summary> /// <summary>
/// Similar as UniTask.Yield but guaranteed run on next frame. /// Similar as UniTask.Yield but guaranteed run on next frame.
/// </summary> /// </summary>
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 #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).")] [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 #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); return new UniTask(source, token);
} }
@ -113,50 +119,50 @@ namespace Cysharp.Threading.Tasks
/// <summary> /// <summary>
/// Same as UniTask.Yield(PlayerLoopTiming.LastFixedUpdate, cancellationToken). /// Same as UniTask.Yield(PlayerLoopTiming.LastFixedUpdate, cancellationToken).
/// </summary> /// </summary>
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) if (delayFrameCount < 0)
{ {
throw new ArgumentOutOfRangeException("Delay does not allow minus delayFrameCount. delayFrameCount:" + delayFrameCount); 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); 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; 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); 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) if (delayTimeSpan < TimeSpan.Zero)
{ {
@ -175,16 +181,16 @@ namespace Cysharp.Threading.Tasks
{ {
case DelayType.UnscaledDeltaTime: 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: 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: case DelayType.DeltaTime:
default: 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; CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
UniTaskCompletionSourceCore<object> core; UniTaskCompletionSourceCore<object> core;
YieldPromise() 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) if (cancellationToken.IsCancellationRequested)
{ {
@ -219,9 +226,17 @@ namespace Cysharp.Threading.Tasks
result = new YieldPromise(); result = new YieldPromise();
} }
result.cancellationToken = cancellationToken; 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); TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(timing, result); PlayerLoopHelper.AddAction(timing, result);
@ -274,6 +289,7 @@ namespace Cysharp.Threading.Tasks
TaskTracker.RemoveTracking(this); TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
cancellationToken = default; cancellationToken = default;
cancellationTokenRegistration.Dispose();
return pool.TryPush(this); return pool.TryPush(this);
} }
} }
@ -290,14 +306,15 @@ namespace Cysharp.Threading.Tasks
} }
int frameCount; int frameCount;
CancellationToken cancellationToken;
UniTaskCompletionSourceCore<AsyncUnit> core; UniTaskCompletionSourceCore<AsyncUnit> core;
CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
NextFramePromise() 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) if (cancellationToken.IsCancellationRequested)
{ {
@ -312,6 +329,15 @@ namespace Cysharp.Threading.Tasks
result.frameCount = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1; result.frameCount = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1;
result.cancellationToken = cancellationToken; 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); TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(timing, result); PlayerLoopHelper.AddAction(timing, result);
@ -369,6 +395,7 @@ namespace Cysharp.Threading.Tasks
TaskTracker.RemoveTracking(this); TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
cancellationToken = default; cancellationToken = default;
cancellationTokenRegistration.Dispose();
return pool.TryPush(this); return pool.TryPush(this);
} }
} }
@ -384,14 +411,15 @@ namespace Cysharp.Threading.Tasks
TaskPool.RegisterSizeGetter(typeof(WaitForEndOfFramePromise), () => pool.Size); TaskPool.RegisterSizeGetter(typeof(WaitForEndOfFramePromise), () => pool.Size);
} }
CancellationToken cancellationToken;
UniTaskCompletionSourceCore<object> core; UniTaskCompletionSourceCore<object> core;
CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
WaitForEndOfFramePromise() 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) if (cancellationToken.IsCancellationRequested)
{ {
@ -405,6 +433,15 @@ namespace Cysharp.Threading.Tasks
result.cancellationToken = cancellationToken; 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); TaskTracker.TrackActiveTask(result, 3);
coroutineRunner.StartCoroutine(result); coroutineRunner.StartCoroutine(result);
@ -446,6 +483,7 @@ namespace Cysharp.Threading.Tasks
core.Reset(); core.Reset();
Reset(); // Reset Enumerator Reset(); // Reset Enumerator
cancellationToken = default; cancellationToken = default;
cancellationTokenRegistration.Dispose();
return pool.TryPush(this); return pool.TryPush(this);
} }
@ -494,6 +532,7 @@ namespace Cysharp.Threading.Tasks
int initialFrame; int initialFrame;
int delayFrameCount; int delayFrameCount;
CancellationToken cancellationToken; CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
int currentFrameCount; int currentFrameCount;
UniTaskCompletionSourceCore<AsyncUnit> core; UniTaskCompletionSourceCore<AsyncUnit> 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) if (cancellationToken.IsCancellationRequested)
{ {
@ -518,6 +557,15 @@ namespace Cysharp.Threading.Tasks
result.cancellationToken = cancellationToken; result.cancellationToken = cancellationToken;
result.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1; 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); TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(timing, result); PlayerLoopHelper.AddAction(timing, result);
@ -604,6 +652,7 @@ namespace Cysharp.Threading.Tasks
currentFrameCount = default; currentFrameCount = default;
delayFrameCount = default; delayFrameCount = default;
cancellationToken = default; cancellationToken = default;
cancellationTokenRegistration.Dispose();
return pool.TryPush(this); return pool.TryPush(this);
} }
} }
@ -623,6 +672,7 @@ namespace Cysharp.Threading.Tasks
float delayTimeSpan; float delayTimeSpan;
float elapsed; float elapsed;
CancellationToken cancellationToken; CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
UniTaskCompletionSourceCore<object> core; UniTaskCompletionSourceCore<object> 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) if (cancellationToken.IsCancellationRequested)
{ {
@ -647,6 +697,15 @@ namespace Cysharp.Threading.Tasks
result.cancellationToken = cancellationToken; result.cancellationToken = cancellationToken;
result.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1; 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); TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(timing, result); PlayerLoopHelper.AddAction(timing, result);
@ -715,6 +774,7 @@ namespace Cysharp.Threading.Tasks
delayTimeSpan = default; delayTimeSpan = default;
elapsed = default; elapsed = default;
cancellationToken = default; cancellationToken = default;
cancellationTokenRegistration.Dispose();
return pool.TryPush(this); return pool.TryPush(this);
} }
} }
@ -734,6 +794,7 @@ namespace Cysharp.Threading.Tasks
float elapsed; float elapsed;
int initialFrame; int initialFrame;
CancellationToken cancellationToken; CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
UniTaskCompletionSourceCore<object> core; UniTaskCompletionSourceCore<object> 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) if (cancellationToken.IsCancellationRequested)
{ {
@ -758,6 +819,15 @@ namespace Cysharp.Threading.Tasks
result.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1; result.initialFrame = PlayerLoopHelper.IsMainThread ? Time.frameCount : -1;
result.cancellationToken = cancellationToken; 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); TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(timing, result); PlayerLoopHelper.AddAction(timing, result);
@ -826,6 +896,7 @@ namespace Cysharp.Threading.Tasks
delayFrameTimeSpan = default; delayFrameTimeSpan = default;
elapsed = default; elapsed = default;
cancellationToken = default; cancellationToken = default;
cancellationTokenRegistration.Dispose();
return pool.TryPush(this); return pool.TryPush(this);
} }
} }
@ -844,6 +915,7 @@ namespace Cysharp.Threading.Tasks
long delayTimeSpanTicks; long delayTimeSpanTicks;
ValueStopwatch stopwatch; ValueStopwatch stopwatch;
CancellationToken cancellationToken; CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
UniTaskCompletionSourceCore<AsyncUnit> core; UniTaskCompletionSourceCore<AsyncUnit> 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) if (cancellationToken.IsCancellationRequested)
{ {
@ -867,6 +939,15 @@ namespace Cysharp.Threading.Tasks
result.delayTimeSpanTicks = delayTimeSpan.Ticks; result.delayTimeSpanTicks = delayTimeSpan.Ticks;
result.cancellationToken = cancellationToken; 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); TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(timing, result); PlayerLoopHelper.AddAction(timing, result);
@ -931,6 +1012,7 @@ namespace Cysharp.Threading.Tasks
core.Reset(); core.Reset();
stopwatch = default; stopwatch = default;
cancellationToken = default; cancellationToken = default;
cancellationTokenRegistration.Dispose();
return pool.TryPush(this); return pool.TryPush(this);
} }
} }

View File

@ -9,30 +9,30 @@ namespace Cysharp.Threading.Tasks
{ {
public partial struct UniTask public partial struct UniTask
{ {
public static UniTask WaitUntil(Func<bool> predicate, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) public static UniTask WaitUntil(Func<bool> 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<bool> predicate, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) public static UniTask WaitWhile(Func<bool> 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<U> WaitUntilValueChanged<T, U>(T target, Func<T, U> monitorFunction, PlayerLoopTiming monitorTiming = PlayerLoopTiming.Update, IEqualityComparer<U> equalityComparer = null, CancellationToken cancellationToken = default(CancellationToken)) public static UniTask<U> WaitUntilValueChanged<T, U>(T target, Func<T, U> monitorFunction, PlayerLoopTiming monitorTiming = PlayerLoopTiming.Update, IEqualityComparer<U> equalityComparer = null, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
where T : class where T : class
{ {
var unityObject = target as UnityEngine.Object; var unityObject = target as UnityEngine.Object;
var isUnityObject = target is UnityEngine.Object; // don't use (unityObject == null) var isUnityObject = target is UnityEngine.Object; // don't use (unityObject == null)
return new UniTask<U>(isUnityObject return new UniTask<U>(isUnityObject
? WaitUntilValueChangedUnityObjectPromise<T, U>.Create(target, monitorFunction, equalityComparer, monitorTiming, cancellationToken, out var token) ? WaitUntilValueChangedUnityObjectPromise<T, U>.Create(target, monitorFunction, equalityComparer, monitorTiming, cancellationToken, cancelImmediately, out var token)
: WaitUntilValueChangedStandardObjectPromise<T, U>.Create(target, monitorFunction, equalityComparer, monitorTiming, cancellationToken, out token), token); : WaitUntilValueChangedStandardObjectPromise<T, U>.Create(target, monitorFunction, equalityComparer, monitorTiming, cancellationToken, cancelImmediately, out token), token);
} }
sealed class WaitUntilPromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<WaitUntilPromise> sealed class WaitUntilPromise : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode<WaitUntilPromise>
@ -48,6 +48,7 @@ namespace Cysharp.Threading.Tasks
Func<bool> predicate; Func<bool> predicate;
CancellationToken cancellationToken; CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
UniTaskCompletionSourceCore<object> core; UniTaskCompletionSourceCore<object> core;
@ -55,7 +56,7 @@ namespace Cysharp.Threading.Tasks
{ {
} }
public static IUniTaskSource Create(Func<bool> predicate, PlayerLoopTiming timing, CancellationToken cancellationToken, out short token) public static IUniTaskSource Create(Func<bool> predicate, PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately, out short token)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
@ -70,6 +71,15 @@ namespace Cysharp.Threading.Tasks
result.predicate = predicate; result.predicate = predicate;
result.cancellationToken = cancellationToken; 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); TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(timing, result); PlayerLoopHelper.AddAction(timing, result);
@ -136,6 +146,7 @@ namespace Cysharp.Threading.Tasks
core.Reset(); core.Reset();
predicate = default; predicate = default;
cancellationToken = default; cancellationToken = default;
cancellationTokenRegistration.Dispose();
return pool.TryPush(this); return pool.TryPush(this);
} }
} }
@ -153,6 +164,7 @@ namespace Cysharp.Threading.Tasks
Func<bool> predicate; Func<bool> predicate;
CancellationToken cancellationToken; CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
UniTaskCompletionSourceCore<object> core; UniTaskCompletionSourceCore<object> core;
@ -160,7 +172,7 @@ namespace Cysharp.Threading.Tasks
{ {
} }
public static IUniTaskSource Create(Func<bool> predicate, PlayerLoopTiming timing, CancellationToken cancellationToken, out short token) public static IUniTaskSource Create(Func<bool> predicate, PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately, out short token)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
@ -175,6 +187,15 @@ namespace Cysharp.Threading.Tasks
result.predicate = predicate; result.predicate = predicate;
result.cancellationToken = cancellationToken; 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); TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(timing, result); PlayerLoopHelper.AddAction(timing, result);
@ -241,6 +262,7 @@ namespace Cysharp.Threading.Tasks
core.Reset(); core.Reset();
predicate = default; predicate = default;
cancellationToken = default; cancellationToken = default;
cancellationTokenRegistration.Dispose();
return pool.TryPush(this); return pool.TryPush(this);
} }
} }
@ -257,6 +279,7 @@ namespace Cysharp.Threading.Tasks
} }
CancellationToken cancellationToken; CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
UniTaskCompletionSourceCore<object> core; UniTaskCompletionSourceCore<object> 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) if (cancellationToken.IsCancellationRequested)
{ {
@ -278,6 +301,15 @@ namespace Cysharp.Threading.Tasks
result.cancellationToken = cancellationToken; 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); TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(timing, result); PlayerLoopHelper.AddAction(timing, result);
@ -329,6 +361,7 @@ namespace Cysharp.Threading.Tasks
TaskTracker.RemoveTracking(this); TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
cancellationToken = default; cancellationToken = default;
cancellationTokenRegistration.Dispose();
return pool.TryPush(this); return pool.TryPush(this);
} }
} }
@ -351,6 +384,7 @@ namespace Cysharp.Threading.Tasks
Func<T, U> monitorFunction; Func<T, U> monitorFunction;
IEqualityComparer<U> equalityComparer; IEqualityComparer<U> equalityComparer;
CancellationToken cancellationToken; CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
UniTaskCompletionSourceCore<U> core; UniTaskCompletionSourceCore<U> core;
@ -358,7 +392,7 @@ namespace Cysharp.Threading.Tasks
{ {
} }
public static IUniTaskSource<U> Create(T target, Func<T, U> monitorFunction, IEqualityComparer<U> equalityComparer, PlayerLoopTiming timing, CancellationToken cancellationToken, out short token) public static IUniTaskSource<U> Create(T target, Func<T, U> monitorFunction, IEqualityComparer<U> equalityComparer, PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately, out short token)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
@ -377,6 +411,15 @@ namespace Cysharp.Threading.Tasks
result.equalityComparer = equalityComparer ?? UnityEqualityComparer.GetDefault<U>(); result.equalityComparer = equalityComparer ?? UnityEqualityComparer.GetDefault<U>();
result.cancellationToken = cancellationToken; result.cancellationToken = cancellationToken;
if (cancelImmediately && cancellationToken.CanBeCanceled)
{
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state =>
{
var promise = (WaitUntilValueChangedUnityObjectPromise<T, U>)state;
promise.core.TrySetCanceled(promise.cancellationToken);
}, result);
}
TaskTracker.TrackActiveTask(result, 3); TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(timing, result); PlayerLoopHelper.AddAction(timing, result);
@ -453,6 +496,7 @@ namespace Cysharp.Threading.Tasks
monitorFunction = default; monitorFunction = default;
equalityComparer = default; equalityComparer = default;
cancellationToken = default; cancellationToken = default;
cancellationTokenRegistration.Dispose();
return pool.TryPush(this); return pool.TryPush(this);
} }
} }
@ -474,6 +518,7 @@ namespace Cysharp.Threading.Tasks
Func<T, U> monitorFunction; Func<T, U> monitorFunction;
IEqualityComparer<U> equalityComparer; IEqualityComparer<U> equalityComparer;
CancellationToken cancellationToken; CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
UniTaskCompletionSourceCore<U> core; UniTaskCompletionSourceCore<U> core;
@ -481,7 +526,7 @@ namespace Cysharp.Threading.Tasks
{ {
} }
public static IUniTaskSource<U> Create(T target, Func<T, U> monitorFunction, IEqualityComparer<U> equalityComparer, PlayerLoopTiming timing, CancellationToken cancellationToken, out short token) public static IUniTaskSource<U> Create(T target, Func<T, U> monitorFunction, IEqualityComparer<U> equalityComparer, PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately, out short token)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
@ -499,6 +544,15 @@ namespace Cysharp.Threading.Tasks
result.equalityComparer = equalityComparer ?? UnityEqualityComparer.GetDefault<U>(); result.equalityComparer = equalityComparer ?? UnityEqualityComparer.GetDefault<U>();
result.cancellationToken = cancellationToken; result.cancellationToken = cancellationToken;
if (cancelImmediately && cancellationToken.CanBeCanceled)
{
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state =>
{
var promise = (WaitUntilValueChangedStandardObjectPromise<T, U>)state;
promise.core.TrySetCanceled(promise.cancellationToken);
}, result);
}
TaskTracker.TrackActiveTask(result, 3); TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(timing, result); PlayerLoopHelper.AddAction(timing, result);
@ -575,6 +629,7 @@ namespace Cysharp.Threading.Tasks
monitorFunction = default; monitorFunction = default;
equalityComparer = default; equalityComparer = default;
cancellationToken = default; cancellationToken = default;
cancellationTokenRegistration.Dispose();
return pool.TryPush(this); return pool.TryPush(this);
} }
} }

View File

@ -24,12 +24,17 @@ namespace Cysharp.Threading.Tasks
return AwaitForAllAssets(asyncOperation, null, PlayerLoopTiming.Update, cancellationToken: cancellationToken); return AwaitForAllAssets(asyncOperation, null, PlayerLoopTiming.Update, cancellationToken: cancellationToken);
} }
public static UniTask<UnityEngine.Object[]> AwaitForAllAssets(this AssetBundleRequest asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) public static UniTask<UnityEngine.Object[]> AwaitForAllAssets(this AssetBundleRequest asyncOperation, CancellationToken cancellationToken, bool cancelImmediately)
{
return AwaitForAllAssets(asyncOperation, progress: null, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately);
}
public static UniTask<UnityEngine.Object[]> AwaitForAllAssets(this AssetBundleRequest asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
{ {
Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation));
if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<UnityEngine.Object[]>(cancellationToken); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<UnityEngine.Object[]>(cancellationToken);
if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.allAssets); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.allAssets);
return new UniTask<UnityEngine.Object[]>(AssetBundleRequestAllAssetsConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); return new UniTask<UnityEngine.Object[]>(AssetBundleRequestAllAssetsConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token);
} }
public struct AssetBundleRequestAllAssetsAwaiter : ICriticalNotifyCompletion public struct AssetBundleRequestAllAssetsAwaiter : ICriticalNotifyCompletion
@ -95,15 +100,19 @@ namespace Cysharp.Threading.Tasks
AssetBundleRequest asyncOperation; AssetBundleRequest asyncOperation;
IProgress<float> progress; IProgress<float> progress;
CancellationToken cancellationToken; CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
bool completed;
UniTaskCompletionSourceCore<UnityEngine.Object[]> core; UniTaskCompletionSourceCore<UnityEngine.Object[]> core;
Action<AsyncOperation> continuationAction;
AssetBundleRequestAllAssetsConfiguredSource() AssetBundleRequestAllAssetsConfiguredSource()
{ {
continuationAction = Continuation;
} }
public static IUniTaskSource<UnityEngine.Object[]> Create(AssetBundleRequest asyncOperation, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, out short token) public static IUniTaskSource<UnityEngine.Object[]> Create(AssetBundleRequest asyncOperation, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, bool cancelImmediately, out short token)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
@ -118,6 +127,18 @@ namespace Cysharp.Threading.Tasks
result.asyncOperation = asyncOperation; result.asyncOperation = asyncOperation;
result.progress = progress; result.progress = progress;
result.cancellationToken = cancellationToken; 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); TaskTracker.TrackActiveTask(result, 3);
@ -161,6 +182,12 @@ namespace Cysharp.Threading.Tasks
public bool MoveNext() public bool MoveNext()
{ {
// Already completed
if (completed || asyncOperation == null)
{
return false;
}
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
core.TrySetCanceled(cancellationToken); core.TrySetCanceled(cancellationToken);
@ -188,8 +215,29 @@ namespace Cysharp.Threading.Tasks
asyncOperation = default; asyncOperation = default;
progress = default; progress = default;
cancellationToken = default; cancellationToken = default;
cancellationTokenRegistration.Dispose();
return pool.TryPush(this); return pool.TryPush(this);
} }
void Continuation(AsyncOperation _)
{
if (completed)
{
TryReturn();
}
else
{
completed = true;
if (cancellationToken.IsCancellationRequested)
{
core.TrySetCanceled(cancellationToken);
}
else
{
core.TrySetResult(asyncOperation.allAssets);
}
}
}
} }
} }
} }

View File

@ -20,10 +20,15 @@ namespace Cysharp.Threading.Tasks
return ToUniTask(asyncOperation, cancellationToken: cancellationToken); return ToUniTask(asyncOperation, cancellationToken: cancellationToken);
} }
public static UniTask<AsyncGPUReadbackRequest> ToUniTask(this AsyncGPUReadbackRequest asyncOperation, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) public static UniTask<AsyncGPUReadbackRequest> WithCancellation(this AsyncGPUReadbackRequest asyncOperation, CancellationToken cancellationToken, bool cancelImmediately)
{
return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately);
}
public static UniTask<AsyncGPUReadbackRequest> ToUniTask(this AsyncGPUReadbackRequest asyncOperation, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
{ {
if (asyncOperation.done) return UniTask.FromResult(asyncOperation); if (asyncOperation.done) return UniTask.FromResult(asyncOperation);
return new UniTask<AsyncGPUReadbackRequest>(AsyncGPUReadbackRequestAwaiterConfiguredSource.Create(asyncOperation, timing, cancellationToken, out var token), token); return new UniTask<AsyncGPUReadbackRequest>(AsyncGPUReadbackRequestAwaiterConfiguredSource.Create(asyncOperation, timing, cancellationToken, cancelImmediately, out var token), token);
} }
sealed class AsyncGPUReadbackRequestAwaiterConfiguredSource : IUniTaskSource<AsyncGPUReadbackRequest>, IPlayerLoopItem, ITaskPoolNode<AsyncGPUReadbackRequestAwaiterConfiguredSource> sealed class AsyncGPUReadbackRequestAwaiterConfiguredSource : IUniTaskSource<AsyncGPUReadbackRequest>, IPlayerLoopItem, ITaskPoolNode<AsyncGPUReadbackRequestAwaiterConfiguredSource>
@ -39,15 +44,15 @@ namespace Cysharp.Threading.Tasks
AsyncGPUReadbackRequest asyncOperation; AsyncGPUReadbackRequest asyncOperation;
CancellationToken cancellationToken; CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
UniTaskCompletionSourceCore<AsyncGPUReadbackRequest> core; UniTaskCompletionSourceCore<AsyncGPUReadbackRequest> core;
AsyncGPUReadbackRequestAwaiterConfiguredSource() AsyncGPUReadbackRequestAwaiterConfiguredSource()
{ {
} }
public static IUniTaskSource<AsyncGPUReadbackRequest> Create(AsyncGPUReadbackRequest asyncOperation, PlayerLoopTiming timing, CancellationToken cancellationToken, out short token) public static IUniTaskSource<AsyncGPUReadbackRequest> Create(AsyncGPUReadbackRequest asyncOperation, PlayerLoopTiming timing, CancellationToken cancellationToken, bool cancelImmediately, out short token)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
@ -62,6 +67,15 @@ namespace Cysharp.Threading.Tasks
result.asyncOperation = asyncOperation; result.asyncOperation = asyncOperation;
result.cancellationToken = cancellationToken; 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); TaskTracker.TrackActiveTask(result, 3);
PlayerLoopHelper.AddAction(timing, result); PlayerLoopHelper.AddAction(timing, result);
@ -131,6 +145,7 @@ namespace Cysharp.Threading.Tasks
core.Reset(); core.Reset();
asyncOperation = default; asyncOperation = default;
cancellationToken = default; cancellationToken = default;
cancellationTokenRegistration.Dispose();
return pool.TryPush(this); return pool.TryPush(this);
} }
} }

View File

@ -17,7 +17,6 @@ namespace Cysharp.Threading.Tasks
#if !UNITY_2023_1_OR_NEWER #if !UNITY_2023_1_OR_NEWER
// from Unity2023.1.0a15, AsyncOperationAwaitableExtensions.GetAwaiter is defined in UnityEngine. // from Unity2023.1.0a15, AsyncOperationAwaitableExtensions.GetAwaiter is defined in UnityEngine.
public static AsyncOperationAwaiter GetAwaiter(this AsyncOperation asyncOperation) public static AsyncOperationAwaiter GetAwaiter(this AsyncOperation asyncOperation)
{ {
Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation));
@ -30,12 +29,17 @@ namespace Cysharp.Threading.Tasks
return ToUniTask(asyncOperation, cancellationToken: cancellationToken); return ToUniTask(asyncOperation, cancellationToken: cancellationToken);
} }
public static UniTask ToUniTask(this AsyncOperation asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(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<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
{ {
Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation));
if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken);
if (asyncOperation.isDone) return UniTask.CompletedTask; 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 public struct AsyncOperationAwaiter : ICriticalNotifyCompletion
@ -92,15 +96,19 @@ namespace Cysharp.Threading.Tasks
AsyncOperation asyncOperation; AsyncOperation asyncOperation;
IProgress<float> progress; IProgress<float> progress;
CancellationToken cancellationToken; CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
bool completed;
UniTaskCompletionSourceCore<AsyncUnit> core; UniTaskCompletionSourceCore<AsyncUnit> core;
Action<AsyncOperation> continuationAction;
AsyncOperationConfiguredSource() AsyncOperationConfiguredSource()
{ {
continuationAction = Continuation;
} }
public static IUniTaskSource Create(AsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, out short token) public static IUniTaskSource Create(AsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, bool cancelImmediately, out short token)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
@ -115,6 +123,18 @@ namespace Cysharp.Threading.Tasks
result.asyncOperation = asyncOperation; result.asyncOperation = asyncOperation;
result.progress = progress; result.progress = progress;
result.cancellationToken = cancellationToken; result.cancellationToken = cancellationToken;
result.completed = false;
asyncOperation.completed += result.continuationAction;
if (cancelImmediately && cancellationToken.CanBeCanceled)
{
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state =>
{
var source = (AsyncOperationConfiguredSource)state;
source.core.TrySetCanceled(source.cancellationToken);
}, result);
}
TaskTracker.TrackActiveTask(result, 3); TaskTracker.TrackActiveTask(result, 3);
@ -154,6 +174,12 @@ namespace Cysharp.Threading.Tasks
public bool MoveNext() public bool MoveNext()
{ {
// Already completed
if (completed || asyncOperation == null)
{
return false;
}
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
core.TrySetCanceled(cancellationToken); core.TrySetCanceled(cancellationToken);
@ -178,11 +204,33 @@ namespace Cysharp.Threading.Tasks
{ {
TaskTracker.RemoveTracking(this); TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
asyncOperation.completed -= continuationAction;
asyncOperation = default; asyncOperation = default;
progress = default; progress = default;
cancellationToken = default; cancellationToken = default;
cancellationTokenRegistration.Dispose();
return pool.TryPush(this); 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 #endregion
@ -200,12 +248,17 @@ namespace Cysharp.Threading.Tasks
return ToUniTask(asyncOperation, cancellationToken: cancellationToken); return ToUniTask(asyncOperation, cancellationToken: cancellationToken);
} }
public static UniTask<UnityEngine.Object> ToUniTask(this ResourceRequest asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) public static UniTask<UnityEngine.Object> WithCancellation(this ResourceRequest asyncOperation, CancellationToken cancellationToken, bool cancelImmediately)
{
return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately);
}
public static UniTask<UnityEngine.Object> ToUniTask(this ResourceRequest asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
{ {
Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation));
if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<UnityEngine.Object>(cancellationToken); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<UnityEngine.Object>(cancellationToken);
if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.asset); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.asset);
return new UniTask<UnityEngine.Object>(ResourceRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); return new UniTask<UnityEngine.Object>(ResourceRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token);
} }
public struct ResourceRequestAwaiter : ICriticalNotifyCompletion public struct ResourceRequestAwaiter : ICriticalNotifyCompletion
@ -266,15 +319,19 @@ namespace Cysharp.Threading.Tasks
ResourceRequest asyncOperation; ResourceRequest asyncOperation;
IProgress<float> progress; IProgress<float> progress;
CancellationToken cancellationToken; CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
bool completed;
UniTaskCompletionSourceCore<UnityEngine.Object> core; UniTaskCompletionSourceCore<UnityEngine.Object> core;
Action<AsyncOperation> continuationAction;
ResourceRequestConfiguredSource() ResourceRequestConfiguredSource()
{ {
continuationAction = Continuation;
} }
public static IUniTaskSource<UnityEngine.Object> Create(ResourceRequest asyncOperation, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, out short token) public static IUniTaskSource<UnityEngine.Object> Create(ResourceRequest asyncOperation, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, bool cancelImmediately, out short token)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
@ -289,6 +346,18 @@ namespace Cysharp.Threading.Tasks
result.asyncOperation = asyncOperation; result.asyncOperation = asyncOperation;
result.progress = progress; result.progress = progress;
result.cancellationToken = cancellationToken; result.cancellationToken = cancellationToken;
result.completed = false;
asyncOperation.completed += result.continuationAction;
if (cancelImmediately && cancellationToken.CanBeCanceled)
{
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state =>
{
var source = (ResourceRequestConfiguredSource)state;
source.core.TrySetCanceled(source.cancellationToken);
}, result);
}
TaskTracker.TrackActiveTask(result, 3); TaskTracker.TrackActiveTask(result, 3);
@ -332,6 +401,12 @@ namespace Cysharp.Threading.Tasks
public bool MoveNext() public bool MoveNext()
{ {
// Already completed
if (completed || asyncOperation == null)
{
return false;
}
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
core.TrySetCanceled(cancellationToken); core.TrySetCanceled(cancellationToken);
@ -356,11 +431,33 @@ namespace Cysharp.Threading.Tasks
{ {
TaskTracker.RemoveTracking(this); TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
asyncOperation.completed -= continuationAction;
asyncOperation = default; asyncOperation = default;
progress = default; progress = default;
cancellationToken = default; cancellationToken = default;
cancellationTokenRegistration.Dispose();
return pool.TryPush(this); 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 #endregion
@ -379,12 +476,17 @@ namespace Cysharp.Threading.Tasks
return ToUniTask(asyncOperation, cancellationToken: cancellationToken); return ToUniTask(asyncOperation, cancellationToken: cancellationToken);
} }
public static UniTask<UnityEngine.Object> ToUniTask(this AssetBundleRequest asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) public static UniTask<UnityEngine.Object> WithCancellation(this AssetBundleRequest asyncOperation, CancellationToken cancellationToken, bool cancelImmediately)
{
return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately);
}
public static UniTask<UnityEngine.Object> ToUniTask(this AssetBundleRequest asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
{ {
Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation));
if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<UnityEngine.Object>(cancellationToken); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<UnityEngine.Object>(cancellationToken);
if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.asset); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.asset);
return new UniTask<UnityEngine.Object>(AssetBundleRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); return new UniTask<UnityEngine.Object>(AssetBundleRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token);
} }
public struct AssetBundleRequestAwaiter : ICriticalNotifyCompletion public struct AssetBundleRequestAwaiter : ICriticalNotifyCompletion
@ -445,15 +547,19 @@ namespace Cysharp.Threading.Tasks
AssetBundleRequest asyncOperation; AssetBundleRequest asyncOperation;
IProgress<float> progress; IProgress<float> progress;
CancellationToken cancellationToken; CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
bool completed;
UniTaskCompletionSourceCore<UnityEngine.Object> core; UniTaskCompletionSourceCore<UnityEngine.Object> core;
Action<AsyncOperation> continuationAction;
AssetBundleRequestConfiguredSource() AssetBundleRequestConfiguredSource()
{ {
continuationAction = Continuation;
} }
public static IUniTaskSource<UnityEngine.Object> Create(AssetBundleRequest asyncOperation, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, out short token) public static IUniTaskSource<UnityEngine.Object> Create(AssetBundleRequest asyncOperation, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, bool cancelImmediately, out short token)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
@ -468,6 +574,18 @@ namespace Cysharp.Threading.Tasks
result.asyncOperation = asyncOperation; result.asyncOperation = asyncOperation;
result.progress = progress; result.progress = progress;
result.cancellationToken = cancellationToken; result.cancellationToken = cancellationToken;
result.completed = false;
asyncOperation.completed += result.continuationAction;
if (cancelImmediately && cancellationToken.CanBeCanceled)
{
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state =>
{
var source = (AssetBundleRequestConfiguredSource)state;
source.core.TrySetCanceled(source.cancellationToken);
}, result);
}
TaskTracker.TrackActiveTask(result, 3); TaskTracker.TrackActiveTask(result, 3);
@ -511,6 +629,12 @@ namespace Cysharp.Threading.Tasks
public bool MoveNext() public bool MoveNext()
{ {
// Already completed
if (completed || asyncOperation == null)
{
return false;
}
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
core.TrySetCanceled(cancellationToken); core.TrySetCanceled(cancellationToken);
@ -535,11 +659,33 @@ namespace Cysharp.Threading.Tasks
{ {
TaskTracker.RemoveTracking(this); TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
asyncOperation.completed -= continuationAction;
asyncOperation = default; asyncOperation = default;
progress = default; progress = default;
cancellationToken = default; cancellationToken = default;
cancellationTokenRegistration.Dispose();
return pool.TryPush(this); 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 #endregion
@ -559,12 +705,17 @@ namespace Cysharp.Threading.Tasks
return ToUniTask(asyncOperation, cancellationToken: cancellationToken); return ToUniTask(asyncOperation, cancellationToken: cancellationToken);
} }
public static UniTask<AssetBundle> ToUniTask(this AssetBundleCreateRequest asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) public static UniTask<AssetBundle> WithCancellation(this AssetBundleCreateRequest asyncOperation, CancellationToken cancellationToken, bool cancelImmediately)
{
return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately);
}
public static UniTask<AssetBundle> ToUniTask(this AssetBundleCreateRequest asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
{ {
Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation));
if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<AssetBundle>(cancellationToken); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<AssetBundle>(cancellationToken);
if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.assetBundle); if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.assetBundle);
return new UniTask<AssetBundle>(AssetBundleCreateRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); return new UniTask<AssetBundle>(AssetBundleCreateRequestConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token);
} }
public struct AssetBundleCreateRequestAwaiter : ICriticalNotifyCompletion public struct AssetBundleCreateRequestAwaiter : ICriticalNotifyCompletion
@ -625,15 +776,19 @@ namespace Cysharp.Threading.Tasks
AssetBundleCreateRequest asyncOperation; AssetBundleCreateRequest asyncOperation;
IProgress<float> progress; IProgress<float> progress;
CancellationToken cancellationToken; CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
bool completed;
UniTaskCompletionSourceCore<AssetBundle> core; UniTaskCompletionSourceCore<AssetBundle> core;
Action<AsyncOperation> continuationAction;
AssetBundleCreateRequestConfiguredSource() AssetBundleCreateRequestConfiguredSource()
{ {
continuationAction = Continuation;
} }
public static IUniTaskSource<AssetBundle> Create(AssetBundleCreateRequest asyncOperation, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, out short token) public static IUniTaskSource<AssetBundle> Create(AssetBundleCreateRequest asyncOperation, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, bool cancelImmediately, out short token)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
@ -648,6 +803,18 @@ namespace Cysharp.Threading.Tasks
result.asyncOperation = asyncOperation; result.asyncOperation = asyncOperation;
result.progress = progress; result.progress = progress;
result.cancellationToken = cancellationToken; result.cancellationToken = cancellationToken;
result.completed = false;
asyncOperation.completed += result.continuationAction;
if (cancelImmediately && cancellationToken.CanBeCanceled)
{
result.cancellationTokenRegistration = cancellationToken.RegisterWithoutCaptureExecutionContext(state =>
{
var source = (AssetBundleCreateRequestConfiguredSource)state;
source.core.TrySetCanceled(source.cancellationToken);
}, result);
}
TaskTracker.TrackActiveTask(result, 3); TaskTracker.TrackActiveTask(result, 3);
@ -691,6 +858,12 @@ namespace Cysharp.Threading.Tasks
public bool MoveNext() public bool MoveNext()
{ {
// Already completed
if (completed || asyncOperation == null)
{
return false;
}
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
core.TrySetCanceled(cancellationToken); core.TrySetCanceled(cancellationToken);
@ -715,11 +888,33 @@ namespace Cysharp.Threading.Tasks
{ {
TaskTracker.RemoveTracking(this); TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
asyncOperation.completed -= continuationAction;
asyncOperation = default; asyncOperation = default;
progress = default; progress = default;
cancellationToken = default; cancellationToken = default;
cancellationTokenRegistration.Dispose();
return pool.TryPush(this); 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 #endregion
@ -739,7 +934,12 @@ namespace Cysharp.Threading.Tasks
return ToUniTask(asyncOperation, cancellationToken: cancellationToken); return ToUniTask(asyncOperation, cancellationToken: cancellationToken);
} }
public static UniTask<UnityWebRequest> ToUniTask(this UnityWebRequestAsyncOperation asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) public static UniTask<UnityWebRequest> WithCancellation(this UnityWebRequestAsyncOperation asyncOperation, CancellationToken cancellationToken, bool cancelImmediately)
{
return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately);
}
public static UniTask<UnityWebRequest> ToUniTask(this UnityWebRequestAsyncOperation asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
{ {
Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation));
if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<UnityWebRequest>(cancellationToken); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<UnityWebRequest>(cancellationToken);
@ -751,7 +951,7 @@ namespace Cysharp.Threading.Tasks
} }
return UniTask.FromResult(asyncOperation.webRequest); return UniTask.FromResult(asyncOperation.webRequest);
} }
return new UniTask<UnityWebRequest>(UnityWebRequestAsyncOperationConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, out var token), token); return new UniTask<UnityWebRequest>(UnityWebRequestAsyncOperationConfiguredSource.Create(asyncOperation, timing, progress, cancellationToken, cancelImmediately, out var token), token);
} }
public struct UnityWebRequestAsyncOperationAwaiter : ICriticalNotifyCompletion public struct UnityWebRequestAsyncOperationAwaiter : ICriticalNotifyCompletion
@ -820,15 +1020,19 @@ namespace Cysharp.Threading.Tasks
UnityWebRequestAsyncOperation asyncOperation; UnityWebRequestAsyncOperation asyncOperation;
IProgress<float> progress; IProgress<float> progress;
CancellationToken cancellationToken; CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
bool completed;
UniTaskCompletionSourceCore<UnityWebRequest> core; UniTaskCompletionSourceCore<UnityWebRequest> core;
Action<AsyncOperation> continuationAction;
UnityWebRequestAsyncOperationConfiguredSource() UnityWebRequestAsyncOperationConfiguredSource()
{ {
continuationAction = Continuation;
} }
public static IUniTaskSource<UnityWebRequest> Create(UnityWebRequestAsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, out short token) public static IUniTaskSource<UnityWebRequest> Create(UnityWebRequestAsyncOperation asyncOperation, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, bool cancelImmediately, out short token)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
@ -843,6 +1047,19 @@ namespace Cysharp.Threading.Tasks
result.asyncOperation = asyncOperation; result.asyncOperation = asyncOperation;
result.progress = progress; result.progress = progress;
result.cancellationToken = cancellationToken; result.cancellationToken = cancellationToken;
result.completed = false;
asyncOperation.completed += result.continuationAction;
if (cancelImmediately && 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); TaskTracker.TrackActiveTask(result, 3);
@ -886,6 +1103,12 @@ namespace Cysharp.Threading.Tasks
public bool MoveNext() public bool MoveNext()
{ {
// Already completed
if (completed || asyncOperation == null)
{
return false;
}
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
asyncOperation.webRequest.Abort(); asyncOperation.webRequest.Abort();
@ -900,11 +1123,7 @@ namespace Cysharp.Threading.Tasks
if (asyncOperation.isDone) if (asyncOperation.isDone)
{ {
if (asyncOperation.webRequest == null) if (asyncOperation.webRequest.IsError())
{
core.TrySetException(new ObjectDisposedException("The webRequest has been destroyed."));
}
else if (asyncOperation.webRequest.IsError())
{ {
core.TrySetException(new UnityWebRequestException(asyncOperation.webRequest)); core.TrySetException(new UnityWebRequestException(asyncOperation.webRequest));
} }
@ -922,11 +1141,37 @@ namespace Cysharp.Threading.Tasks
{ {
TaskTracker.RemoveTracking(this); TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
asyncOperation.completed -= continuationAction;
asyncOperation = default; asyncOperation = default;
progress = default; progress = default;
cancellationToken = default; cancellationToken = default;
cancellationTokenRegistration.Dispose();
return pool.TryPush(this); 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 #endregion

View File

@ -16,6 +16,7 @@
Func<string, string> ToUniTaskReturnType = x => (x == "void") ? "UniTask" : $"UniTask<{x}>"; Func<string, string> ToUniTaskReturnType = x => (x == "void") ? "UniTask" : $"UniTask<{x}>";
Func<string, string> ToIUniTaskSourceReturnType = x => (x == "void") ? "IUniTaskSource" : $"IUniTaskSource<{x}>"; Func<string, string> 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> 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> IsAssetBundleModule = x => x.typeName == "AssetBundleRequest" || x.typeName == "AssetBundleCreateRequest";
Func<(string typeName, string returnType, string returnField), bool> IsVoid = x => x.returnType == "void"; Func<(string typeName, string returnType, string returnField), bool> IsVoid = x => x.returnType == "void";
@ -43,18 +44,30 @@ namespace Cysharp.Threading.Tasks
<# } #> <# } #>
#region <#= t.typeName #> #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) public static <#= t.typeName #>Awaiter GetAwaiter(this <#= t.typeName #> asyncOperation)
{ {
Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation));
return new <#= t.typeName #>Awaiter(asyncOperation); return new <#= t.typeName #>Awaiter(asyncOperation);
} }
<# if (IsAsyncOperationBase(t)) { #>
#endif
<# } #>
public static <#= ToUniTaskReturnType(t.returnType) #> WithCancellation(this <#= t.typeName #> asyncOperation, CancellationToken cancellationToken) public static <#= ToUniTaskReturnType(t.returnType) #> WithCancellation(this <#= t.typeName #> asyncOperation, CancellationToken cancellationToken)
{ {
return ToUniTask(asyncOperation, cancellationToken: cancellationToken); return ToUniTask(asyncOperation, cancellationToken: cancellationToken);
} }
public static <#= ToUniTaskReturnType(t.returnType) #> ToUniTask(this <#= t.typeName #> asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) public static <#= ToUniTaskReturnType(t.returnType) #> WithCancellation(this <#= t.typeName #> asyncOperation, CancellationToken cancellationToken, bool cancelImmediately)
{
return ToUniTask(asyncOperation, cancellationToken: cancellationToken, cancelImmediately: cancelImmediately);
}
public static <#= ToUniTaskReturnType(t.returnType) #> ToUniTask(this <#= t.typeName #> asyncOperation, IProgress<float> progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken), bool cancelImmediately = false)
{ {
Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation));
if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<#= IsVoid(t) ? "" : "<" + t.returnType + ">" #>(cancellationToken); if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<#= IsVoid(t) ? "" : "<" + t.returnType + ">" #>(cancellationToken);
@ -70,7 +83,7 @@ namespace Cysharp.Threading.Tasks
<# } else { #> <# } else { #>
if (asyncOperation.isDone) return <#= IsVoid(t) ? "UniTask.CompletedTask" : $"UniTask.FromResult(asyncOperation.{t.returnField})" #>; 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, cancellationToken, cancelImmediately, out var token), token);
} }
public struct <#= t.typeName #>Awaiter : ICriticalNotifyCompletion public struct <#= t.typeName #>Awaiter : ICriticalNotifyCompletion
@ -151,15 +164,19 @@ namespace Cysharp.Threading.Tasks
<#= t.typeName #> asyncOperation; <#= t.typeName #> asyncOperation;
IProgress<float> progress; IProgress<float> progress;
CancellationToken cancellationToken; CancellationToken cancellationToken;
CancellationTokenRegistration cancellationTokenRegistration;
bool completed;
UniTaskCompletionSourceCore<<#= IsVoid(t) ? "AsyncUnit" : t.returnType #>> core; UniTaskCompletionSourceCore<<#= IsVoid(t) ? "AsyncUnit" : t.returnType #>> core;
Action<AsyncOperation> continuationAction;
<#= t.typeName #>ConfiguredSource() <#= t.typeName #>ConfiguredSource()
{ {
continuationAction = Continuation;
} }
public static <#= ToIUniTaskSourceReturnType(t.returnType) #> Create(<#= t.typeName #> asyncOperation, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, out short token) public static <#= ToIUniTaskSourceReturnType(t.returnType) #> Create(<#= t.typeName #> asyncOperation, PlayerLoopTiming timing, IProgress<float> progress, CancellationToken cancellationToken, bool cancelImmediately, out short token)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
@ -174,6 +191,21 @@ namespace Cysharp.Threading.Tasks
result.asyncOperation = asyncOperation; result.asyncOperation = asyncOperation;
result.progress = progress; result.progress = progress;
result.cancellationToken = cancellationToken; result.cancellationToken = cancellationToken;
result.completed = false;
asyncOperation.completed += result.continuationAction;
if (cancelImmediately && 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); TaskTracker.TrackActiveTask(result, 3);
@ -223,6 +255,12 @@ namespace Cysharp.Threading.Tasks
public bool MoveNext() public bool MoveNext()
{ {
// Already completed
if (completed || asyncOperation == null)
{
return false;
}
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
{ {
<# if(IsUnityWebRequest(t)) { #> <# if(IsUnityWebRequest(t)) { #>
@ -261,11 +299,44 @@ namespace Cysharp.Threading.Tasks
{ {
TaskTracker.RemoveTracking(this); TaskTracker.RemoveTracking(this);
core.Reset(); core.Reset();
asyncOperation.completed -= continuationAction;
asyncOperation = default; asyncOperation = default;
progress = default; progress = default;
cancellationToken = default; cancellationToken = default;
cancellationTokenRegistration.Dispose();
return pool.TryPush(this); 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 #endregion

View File

@ -0,0 +1,90 @@
using System.Collections;
using System.Threading;
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))
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
namespace Cysharp.Threading.TasksTests
{
public class AsyncOperationTest
{
[UnityTest]
public IEnumerator ResourcesLoad_Completed() => UniTask.ToCoroutine(async () =>
{
var asyncOperation = Resources.LoadAsync<Texture2D>("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<Texture>("sample_texture").ToUniTask(cancellationToken: cts.Token, cancelImmediately: 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<Texture>("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

View File

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 295d574a16494d6aa4d02fcb32179e39
timeCreated: 1698887128

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8d82913edf6ac48aca30f66ae9ba42d6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -0,0 +1,208 @@
fileFormatVersion: 2
guid: 535006a83baed4ebda99d24a909a2efe
TextureImporter:
internalIDToNameTable:
- first:
213: -2664112245596591751
second: sample_texture_0
- first:
213: -4606777057269188692
second: sample_texture_1
- first:
213: 1950921086533113773
second: sample_texture_2
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 1
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 2
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 8
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: WebGL
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: iPhone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites:
- serializedVersion: 2
name: sample_texture_0
rect:
serializedVersion: 2
x: 0
y: 76
width: 243
height: 251
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: 9796277170c270bd0800000000000000
internalID: -2664112245596591751
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: sample_texture_1
rect:
serializedVersion: 2
x: 227
y: 0
width: 190
height: 231
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: ca7fc069ca07110c0800000000000000
internalID: -4606777057269188692
vertices: []
indices:
edges: []
weights: []
- serializedVersion: 2
name: sample_texture_2
rect:
serializedVersion: 2
x: 398
y: 87
width: 202
height: 188
alignment: 0
pivot: {x: 0, y: 0}
border: {x: 0, y: 0, z: 0, w: 0}
outline: []
physicsShape: []
tessellationDetail: -1
bones: []
spriteID: da710ab4460131b10800000000000000
internalID: 1950921086533113773
vertices: []
indices:
edges: []
weights: []
outline: []
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant: