From 7ac9853cf640b9049865f55e7b61270ddd3fcb02 Mon Sep 17 00:00:00 2001 From: neuecc Date: Thu, 25 Feb 2021 22:11:18 +0900 Subject: [PATCH] (BreakingChange)AsyncOperation.WithCancellation same ToUniTask(ctoken) --- README.md | 135 +++- ...cExtensions.AssetBundleRequestAllAssets.cs | 128 +--- .../UniTask/Runtime/UnityAsyncExtensions.cs | 652 +----------------- .../UniTask/Runtime/UnityAsyncExtensions.tt | 166 +---- src/UniTask/Assets/Scenes/EditorTest1.cs | 3 + src/UniTask/Assets/Scenes/SandboxMain.cs | 60 +- 6 files changed, 192 insertions(+), 952 deletions(-) diff --git a/README.md b/README.md index bafb25f..2123fc9 100644 --- a/README.md +++ b/README.md @@ -127,9 +127,6 @@ async UniTask DemoAsync() // shorthand of WhenAll, tuple can await directly var (google2, bing2, yahoo2) = await (task1, task2, task3); - - // You can handle timeouts easily - await GetTextAsync(UnityWebRequest.Get("http://unity.com")).Timeout(TimeSpan.FromMilliseconds(300)); // return async-value.(or you can use `UniTask`(no result), `UniTaskVoid`(fire and forget)). return (asset as TextAsset)?.text ?? throw new InvalidOperationException("Asset not found"); @@ -154,7 +151,7 @@ UniTask provides three pattern of extension methods. `WithCancellation` is a simple version of `ToUniTask`, both return `UniTask`. For details of cancellation, see: [Cancellation and Exception handling](#cancellation-and-exception-handling) section. -> Note: WithCancellation is returned from native timing of PlayerLoop but ToUniTask is returned from specified PlayerLoopTiming. For details of timing, see: [PlayerLoop](#playerloop) section. +> Note: await directly is returned from native timing of PlayerLoop but WithCancellation and ToUniTask are returned from specified PlayerLoopTiming. For details of timing, see: [PlayerLoop](#playerloop) section. > Note: AssetBundleRequest has `asset` and `allAssets`, default await returns `asset`. If you want to get `allAssets`, you can use `AwaitForAllAssets()` method. @@ -286,6 +283,100 @@ 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. +Timeout handling +--- +Timeout is a variation of cancellation. You can set timeout by `CancellationTokenSouce.CancelAfterSlim(TimeSpan)` and pass CancellationToken to async methods. + +```csharp +var cts = new CancellationTokenSource(); +cts.CancelAfterSlim(TimeSpan.FromSeconds(5)); // 5sec timeout. + +try +{ + await UnityWebRequest.Get("http://foo").SendWebRequest().WithCancellation(cts.Token); +} +catch (OperationCanceledException ex) +{ + if (ex.CancellationToken == cts.Token) + { + UnityEngine.Debug.Log("Timeout"); + } +} +``` + +> `CancellationTokenSouce.CancelAfter` is a standard api. However in Unity you should not use it because it depends threading timer. `CancelAfterSlim` is UniTask's extension methods, it uses PlayerLoop instead. + +If you want to use timeout with other source of cancellation, use `CancellationTokenSource.CreateLinkedTokenSource`. + +```csharp +var cancelToken = new CancellationTokenSource(); +cancelButton.onClick.AddListener(()=> +{ + cancelToken.Cancel(); // cancel from button click. +}); + +var timeoutToken = new CancellationTokenSource(); +timeoutToken.CancelAfterSlim(TimeSpan.FromSeconds(5)); // 5sec timeout. + +try +{ + // combine token + var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancelToken.Token, timeoutToken.Token); + + await UnityWebRequest.Get("http://foo").SendWebRequest().WithCancellation(linkedTokenSource.Token); +} +catch (OperationCanceledException ex) +{ + if (timeoutToken.IsCancellationRequested) + { + UnityEngine.Debug.Log("Timeout."); + } + else if (cancelToken.IsCancellationRequested) + { + UnityEngine.Debug.Log("Cancel clicked."); + } +} +``` + +Optimize for reduce allocation of CancellationTokenSource for timeout per call async method, you can use UniTask's `TimeoutController`. + +```csharp +TimeoutController timeoutController = new TimeoutController(); // setup to field for reuse. + +async UniTask FooAsync() +{ + try + { + // you can pass timeoutController.Timeout(TimeSpan) to cancellationToken. + await UnityWebRequest.Get("http://foo").SendWebRequest() + .WithCancellation(timeoutController.Timeout(TimeSpan.FromSeconds(5))); + timeoutController.Reset(); // call Reset(Stop timeout timer and ready for reuse) when succeed. + } + catch (OperationCanceledException ex) + { + if (timeoutController.IsTimeout()) + { + UnityEngine.Debug.Log("timeout"); + } + } +} +``` + +If you want to use timeout with other source of cancellation, use `new TimeoutController(CancellationToken)`. + +```csharp +TimeoutController timeoutController; +CancellationTokenSource clickCancelSource; + +void Start() +{ + this.clickCancelSource = new CancellationTokenSource(); + this.timeoutController = new TimeoutController(clickCancelSource); +} +``` + +Note: UniTask has `.Timeout`, `.TimeoutWithoutException` methods however, if possible, do not use these, please pass `CancellationToken`. Because `.Timeout` work from external of task, can not stop timeoutted task. `.Timeout` means ignore result when timeout. If you pass a `CancellationToken` to the method, it will act from inside of the task, so it is possible to stop a running task. + Progress --- Some async operations for unity have `ToUniTask(IProgress progress = null, ...)` extension methods. @@ -366,7 +457,7 @@ It indicates when to run, you can check [PlayerLoopList.md](https://gist.github. `AsyncOperation` is returned from native timing. For example, await `SceneManager.LoadSceneAsync` is returned from `EarlyUpdate.UpdatePreloading` and after being called, the loaded scene's `Start` is called from `EarlyUpdate.ScriptRunDelayedStartupFrame`. Also `await UnityWebRequest` is returned from `EarlyUpdate.ExecuteMainThreadJobs`. -In UniTask, await directly and `WithCancellation` use native timing, `ToUniTask` uses specified timing. This is usually not a particular problem, but with `LoadSceneAsync`, it causes a different order of Start and continuation after await. So it is recommended not to use `LoadSceneAsync.ToUniTask`. +In UniTask, await directly uses native timing, `WithCancellation` and `ToUniTask` use specified timing. This is usually not a particular problem, but with `LoadSceneAsync`, it causes a different order of Start and continuation after await. So it is recommended not to use `LoadSceneAsync.ToUniTask`. In the stacktrace, you can check where it is running in playerloop. @@ -408,6 +499,37 @@ void Start() } ``` +You can optimize loop cost slightly by remove unuse PlayerLoopTiming injection. You can call `PlayerLoopHelper.Initialize(InjectPlayerLoopTimings)` on initialize. + +```csharp +var loop = PlayerLoop.GetCurrentPlayerLoop(); +PlayerLoopHelper.Initialize(ref loop, InjectPlayerLoopTimings.Minimum); // minimum is Update | FixedUpdate | LastPostLateUpdate +``` + +`InjectPlayerLoopTimings` has three preset, `All` and `Standard`(All without last except LastPostLateUpdate), `Minimum`(`Update | FixedUpdate | LastPostLateUpdate`). Default is All and you can combine custom inject timings like `InjectPlayerLoopTimings.Update | InjectPlayerLoopTimings.FixedUpdate | InjectPlayerLoopTimings.PreLateUpdate`. + +You can make error to use uninjected `PlayerLoopTiming` by [Microsoft.CodeAnalysis.BannedApiAnalyzers](https://github.com/dotnet/roslyn-analyzers/blob/master/src/Microsoft.CodeAnalysis.BannedApiAnalyzers/BannedApiAnalyzers.Help.md). For example, you can setup `BannedSymbols.txt` like this for `InjectPlayerLoopTimings.Minimum`. + +```txt +F:Cysharp.Threading.Tasks.PlayerLoopTiming.Initialization; Isn't injected this PlayerLoop in this project. +F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastInitialization; Isn't injected this PlayerLoop in this project. +F:Cysharp.Threading.Tasks.PlayerLoopTiming.EarlyUpdate; Isn't injected this PlayerLoop in this project. +F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastEarlyUpdate; Isn't injected this PlayerLoop in this project.d +F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastFixedUpdate; Isn't injected this PlayerLoop in this project. +F:Cysharp.Threading.Tasks.PlayerLoopTiming.PreUpdate; Isn't injected this PlayerLoop in this project. +F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastPreUpdate; Isn't injected this PlayerLoop in this project. +F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastUpdate; Isn't injected this PlayerLoop in this project. +F:Cysharp.Threading.Tasks.PlayerLoopTiming.PreLateUpdate; Isn't injected this PlayerLoop in this project. +F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastPreLateUpdate; Isn't injected this PlayerLoop in this project. +F:Cysharp.Threading.Tasks.PlayerLoopTiming.PostLateUpdate; Isn't injected this PlayerLoop in this project. +F:Cysharp.Threading.Tasks.PlayerLoopTiming.TimeUpdate; Isn't injected this PlayerLoop in this project. +F:Cysharp.Threading.Tasks.PlayerLoopTiming.LastTimeUpdate; Isn't injected this PlayerLoop in this project. +``` + +You can configure `RS0030` severity to error. + +![image](https://user-images.githubusercontent.com/46207/109150837-bb933880-77ac-11eb-85ba-4fd15819dbd0.png) + async void vs async UniTaskVoid --- `async void` is a standard C# task system so it does not run on UniTask systems. It is better not to use it. `async UniTaskVoid` is a lightweight version of `async UniTask` because it does not have awaitable completion and reports errors immediately to `UniTaskScheduler.UnobservedTaskException`. If you don't require awaiting (fire and forget), using `UniTaskVoid` is better. Unfortunately to dismiss warning, you're required to call `Forget()`. @@ -812,8 +934,9 @@ For UnityEditor --- UniTask can run on Unity Editor like an Editor Coroutine. However, there are some limitations. -* Delay, DelayFrame do not work correctly because they can not get deltaTime in editor. Return the result of the await immediately; you can use `DelayType.Realtime` to wait for the right time. +* UniTask.Delay's DelayType.DeltaTime, UnscaledDeltaTime do not work correctly because they can not get deltaTime in editor. Therefore run on EditMode, automatically change DelayType to `DelayType.Realtime` that wait for the right time. * All PlayerLoopTiming run on the timing `EditorApplication.update`. +* `-batchmode` with `-quit` does not work because does not run `EditorApplication.update`(quit on single frame) so should not use `-quit` and quit manually with `Environment.Exit(0)`. Compare with Standard Task API --- diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AssetBundleRequestAllAssets.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AssetBundleRequestAllAssets.cs index 9ff4ffa..90889f3 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AssetBundleRequestAllAssets.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.AssetBundleRequestAllAssets.cs @@ -21,10 +21,7 @@ namespace Cysharp.Threading.Tasks public static UniTask AwaitForAllAssets(this AssetBundleRequest asyncOperation, CancellationToken cancellationToken) { - Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); - if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); - if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.allAssets); - return new UniTask(AssetBundleRequestAllAssetsWithCancellationSource.Create(asyncOperation, cancellationToken, out var token), token); + return AwaitForAllAssets(asyncOperation, cancellationToken: cancellationToken); } public static UniTask AwaitForAllAssets(this AssetBundleRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) @@ -84,129 +81,6 @@ namespace Cysharp.Threading.Tasks } } - sealed class AssetBundleRequestAllAssetsWithCancellationSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode - { - static TaskPool pool; - AssetBundleRequestAllAssetsWithCancellationSource nextNode; - public ref AssetBundleRequestAllAssetsWithCancellationSource NextNode => ref nextNode; - - static AssetBundleRequestAllAssetsWithCancellationSource() - { - TaskPool.RegisterSizeGetter(typeof(AssetBundleRequestAllAssetsWithCancellationSource), () => pool.Size); - } - - readonly Action continuationAction; - AssetBundleRequest asyncOperation; - CancellationToken cancellationToken; - bool completed; - - UniTaskCompletionSourceCore core; - - AssetBundleRequestAllAssetsWithCancellationSource() - { - continuationAction = Continuation; - } - - public static IUniTaskSource Create(AssetBundleRequest asyncOperation, CancellationToken cancellationToken, out short token) - { - if (cancellationToken.IsCancellationRequested) - { - return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); - } - - if (!pool.TryPop(out var result)) - { - result = new AssetBundleRequestAllAssetsWithCancellationSource(); - } - - result.asyncOperation = asyncOperation; - result.cancellationToken = cancellationToken; - result.completed = false; - - TaskTracker.TrackActiveTask(result, 3); - - PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, result); - - asyncOperation.completed += result.continuationAction; - - token = result.core.Version; - return result; - } - - void Continuation(AsyncOperation _) - { - asyncOperation.completed -= continuationAction; - - if (completed) - { - TryReturn(); - } - else - { - completed = true; - if (cancellationToken.IsCancellationRequested) - { - core.TrySetCanceled(cancellationToken); - return; - } - - core.TrySetResult(asyncOperation.allAssets); - } - } - - public UnityEngine.Object[] GetResult(short token) - { - return core.GetResult(token); - } - - void IUniTaskSource.GetResult(short token) - { - GetResult(token); - } - - public UniTaskStatus GetStatus(short token) - { - return core.GetStatus(token); - } - - public UniTaskStatus UnsafeGetStatus() - { - return core.UnsafeGetStatus(); - } - - public void OnCompleted(Action continuation, object state, short token) - { - core.OnCompleted(continuation, state, token); - } - - public bool MoveNext() - { - if (completed) - { - TryReturn(); - return false; - } - - if (cancellationToken.IsCancellationRequested) - { - completed = true; - core.TrySetCanceled(cancellationToken); - return false; - } - - return true; - } - - bool TryReturn() - { - TaskTracker.RemoveTracking(this); - core.Reset(); - asyncOperation = default; - cancellationToken = default; - return pool.TryPush(this); - } - } - sealed class AssetBundleRequestAllAssetsConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs index ebb44d0..8f8e3a1 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.cs @@ -23,10 +23,7 @@ namespace Cysharp.Threading.Tasks public static UniTask WithCancellation(this AsyncOperation asyncOperation, CancellationToken cancellationToken) { - Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); - if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); - if (asyncOperation.isDone) return UniTask.CompletedTask; - return new UniTask(AsyncOperationWithCancellationSource.Create(asyncOperation, cancellationToken, out var token), token); + return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } public static UniTask ToUniTask(this AsyncOperation asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) @@ -77,125 +74,6 @@ namespace Cysharp.Threading.Tasks } } - sealed class AsyncOperationWithCancellationSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode - { - static TaskPool pool; - AsyncOperationWithCancellationSource nextNode; - public ref AsyncOperationWithCancellationSource NextNode => ref nextNode; - - static AsyncOperationWithCancellationSource() - { - TaskPool.RegisterSizeGetter(typeof(AsyncOperationWithCancellationSource), () => pool.Size); - } - - readonly Action continuationAction; - AsyncOperation asyncOperation; - CancellationToken cancellationToken; - bool completed; - - UniTaskCompletionSourceCore core; - - AsyncOperationWithCancellationSource() - { - continuationAction = Continuation; - } - - public static IUniTaskSource Create(AsyncOperation asyncOperation, CancellationToken cancellationToken, out short token) - { - if (cancellationToken.IsCancellationRequested) - { - return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); - } - - if (!pool.TryPop(out var result)) - { - result = new AsyncOperationWithCancellationSource(); - } - - result.asyncOperation = asyncOperation; - result.cancellationToken = cancellationToken; - result.completed = false; - - TaskTracker.TrackActiveTask(result, 3); - - PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, result); - - asyncOperation.completed += result.continuationAction; - - token = result.core.Version; - return result; - } - - void Continuation(AsyncOperation _) - { - asyncOperation.completed -= continuationAction; - - if (completed) - { - TryReturn(); - } - else - { - completed = true; - if (cancellationToken.IsCancellationRequested) - { - core.TrySetCanceled(cancellationToken); - return; - } - - core.TrySetResult(AsyncUnit.Default); - } - } - - public void GetResult(short token) - { - core.GetResult(token); - } - - - public UniTaskStatus GetStatus(short token) - { - return core.GetStatus(token); - } - - public UniTaskStatus UnsafeGetStatus() - { - return core.UnsafeGetStatus(); - } - - public void OnCompleted(Action continuation, object state, short token) - { - core.OnCompleted(continuation, state, token); - } - - public bool MoveNext() - { - if (completed) - { - TryReturn(); - return false; - } - - if (cancellationToken.IsCancellationRequested) - { - completed = true; - core.TrySetCanceled(cancellationToken); - return false; - } - - return true; - } - - bool TryReturn() - { - TaskTracker.RemoveTracking(this); - core.Reset(); - asyncOperation = default; - cancellationToken = default; - return pool.TryPush(this); - } - } - sealed class AsyncOperationConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; @@ -315,10 +193,7 @@ namespace Cysharp.Threading.Tasks public static UniTask WithCancellation(this ResourceRequest asyncOperation, CancellationToken cancellationToken) { - Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); - if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); - if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.asset); - return new UniTask(ResourceRequestWithCancellationSource.Create(asyncOperation, cancellationToken, out var token), token); + return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } public static UniTask ToUniTask(this ResourceRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) @@ -373,129 +248,6 @@ namespace Cysharp.Threading.Tasks } } - sealed class ResourceRequestWithCancellationSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode - { - static TaskPool pool; - ResourceRequestWithCancellationSource nextNode; - public ref ResourceRequestWithCancellationSource NextNode => ref nextNode; - - static ResourceRequestWithCancellationSource() - { - TaskPool.RegisterSizeGetter(typeof(ResourceRequestWithCancellationSource), () => pool.Size); - } - - readonly Action continuationAction; - ResourceRequest asyncOperation; - CancellationToken cancellationToken; - bool completed; - - UniTaskCompletionSourceCore core; - - ResourceRequestWithCancellationSource() - { - continuationAction = Continuation; - } - - public static IUniTaskSource Create(ResourceRequest asyncOperation, CancellationToken cancellationToken, out short token) - { - if (cancellationToken.IsCancellationRequested) - { - return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); - } - - if (!pool.TryPop(out var result)) - { - result = new ResourceRequestWithCancellationSource(); - } - - result.asyncOperation = asyncOperation; - result.cancellationToken = cancellationToken; - result.completed = false; - - TaskTracker.TrackActiveTask(result, 3); - - PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, result); - - asyncOperation.completed += result.continuationAction; - - token = result.core.Version; - return result; - } - - void Continuation(AsyncOperation _) - { - asyncOperation.completed -= continuationAction; - - if (completed) - { - TryReturn(); - } - else - { - completed = true; - if (cancellationToken.IsCancellationRequested) - { - core.TrySetCanceled(cancellationToken); - return; - } - - core.TrySetResult(asyncOperation.asset); - } - } - - public UnityEngine.Object GetResult(short token) - { - return core.GetResult(token); - } - - void IUniTaskSource.GetResult(short token) - { - GetResult(token); - } - - public UniTaskStatus GetStatus(short token) - { - return core.GetStatus(token); - } - - public UniTaskStatus UnsafeGetStatus() - { - return core.UnsafeGetStatus(); - } - - public void OnCompleted(Action continuation, object state, short token) - { - core.OnCompleted(continuation, state, token); - } - - public bool MoveNext() - { - if (completed) - { - TryReturn(); - return false; - } - - if (cancellationToken.IsCancellationRequested) - { - completed = true; - core.TrySetCanceled(cancellationToken); - return false; - } - - return true; - } - - bool TryReturn() - { - TaskTracker.RemoveTracking(this); - core.Reset(); - asyncOperation = default; - cancellationToken = default; - return pool.TryPush(this); - } - } - sealed class ResourceRequestConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; @@ -620,10 +372,7 @@ namespace Cysharp.Threading.Tasks public static UniTask WithCancellation(this AssetBundleRequest asyncOperation, CancellationToken cancellationToken) { - Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); - if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); - if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.asset); - return new UniTask(AssetBundleRequestWithCancellationSource.Create(asyncOperation, cancellationToken, out var token), token); + return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } public static UniTask ToUniTask(this AssetBundleRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) @@ -678,129 +427,6 @@ namespace Cysharp.Threading.Tasks } } - sealed class AssetBundleRequestWithCancellationSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode - { - static TaskPool pool; - AssetBundleRequestWithCancellationSource nextNode; - public ref AssetBundleRequestWithCancellationSource NextNode => ref nextNode; - - static AssetBundleRequestWithCancellationSource() - { - TaskPool.RegisterSizeGetter(typeof(AssetBundleRequestWithCancellationSource), () => pool.Size); - } - - readonly Action continuationAction; - AssetBundleRequest asyncOperation; - CancellationToken cancellationToken; - bool completed; - - UniTaskCompletionSourceCore core; - - AssetBundleRequestWithCancellationSource() - { - continuationAction = Continuation; - } - - public static IUniTaskSource Create(AssetBundleRequest asyncOperation, CancellationToken cancellationToken, out short token) - { - if (cancellationToken.IsCancellationRequested) - { - return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); - } - - if (!pool.TryPop(out var result)) - { - result = new AssetBundleRequestWithCancellationSource(); - } - - result.asyncOperation = asyncOperation; - result.cancellationToken = cancellationToken; - result.completed = false; - - TaskTracker.TrackActiveTask(result, 3); - - PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, result); - - asyncOperation.completed += result.continuationAction; - - token = result.core.Version; - return result; - } - - void Continuation(AsyncOperation _) - { - asyncOperation.completed -= continuationAction; - - if (completed) - { - TryReturn(); - } - else - { - completed = true; - if (cancellationToken.IsCancellationRequested) - { - core.TrySetCanceled(cancellationToken); - return; - } - - core.TrySetResult(asyncOperation.asset); - } - } - - public UnityEngine.Object GetResult(short token) - { - return core.GetResult(token); - } - - void IUniTaskSource.GetResult(short token) - { - GetResult(token); - } - - public UniTaskStatus GetStatus(short token) - { - return core.GetStatus(token); - } - - public UniTaskStatus UnsafeGetStatus() - { - return core.UnsafeGetStatus(); - } - - public void OnCompleted(Action continuation, object state, short token) - { - core.OnCompleted(continuation, state, token); - } - - public bool MoveNext() - { - if (completed) - { - TryReturn(); - return false; - } - - if (cancellationToken.IsCancellationRequested) - { - completed = true; - core.TrySetCanceled(cancellationToken); - return false; - } - - return true; - } - - bool TryReturn() - { - TaskTracker.RemoveTracking(this); - core.Reset(); - asyncOperation = default; - cancellationToken = default; - return pool.TryPush(this); - } - } - sealed class AssetBundleRequestConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; @@ -926,10 +552,7 @@ namespace Cysharp.Threading.Tasks public static UniTask WithCancellation(this AssetBundleCreateRequest asyncOperation, CancellationToken cancellationToken) { - Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); - if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); - if (asyncOperation.isDone) return UniTask.FromResult(asyncOperation.assetBundle); - return new UniTask(AssetBundleCreateRequestWithCancellationSource.Create(asyncOperation, cancellationToken, out var token), token); + return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } public static UniTask ToUniTask(this AssetBundleCreateRequest asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) @@ -984,129 +607,6 @@ namespace Cysharp.Threading.Tasks } } - sealed class AssetBundleCreateRequestWithCancellationSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode - { - static TaskPool pool; - AssetBundleCreateRequestWithCancellationSource nextNode; - public ref AssetBundleCreateRequestWithCancellationSource NextNode => ref nextNode; - - static AssetBundleCreateRequestWithCancellationSource() - { - TaskPool.RegisterSizeGetter(typeof(AssetBundleCreateRequestWithCancellationSource), () => pool.Size); - } - - readonly Action continuationAction; - AssetBundleCreateRequest asyncOperation; - CancellationToken cancellationToken; - bool completed; - - UniTaskCompletionSourceCore core; - - AssetBundleCreateRequestWithCancellationSource() - { - continuationAction = Continuation; - } - - public static IUniTaskSource Create(AssetBundleCreateRequest asyncOperation, CancellationToken cancellationToken, out short token) - { - if (cancellationToken.IsCancellationRequested) - { - return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); - } - - if (!pool.TryPop(out var result)) - { - result = new AssetBundleCreateRequestWithCancellationSource(); - } - - result.asyncOperation = asyncOperation; - result.cancellationToken = cancellationToken; - result.completed = false; - - TaskTracker.TrackActiveTask(result, 3); - - PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, result); - - asyncOperation.completed += result.continuationAction; - - token = result.core.Version; - return result; - } - - void Continuation(AsyncOperation _) - { - asyncOperation.completed -= continuationAction; - - if (completed) - { - TryReturn(); - } - else - { - completed = true; - if (cancellationToken.IsCancellationRequested) - { - core.TrySetCanceled(cancellationToken); - return; - } - - core.TrySetResult(asyncOperation.assetBundle); - } - } - - public AssetBundle GetResult(short token) - { - return core.GetResult(token); - } - - void IUniTaskSource.GetResult(short token) - { - GetResult(token); - } - - public UniTaskStatus GetStatus(short token) - { - return core.GetStatus(token); - } - - public UniTaskStatus UnsafeGetStatus() - { - return core.UnsafeGetStatus(); - } - - public void OnCompleted(Action continuation, object state, short token) - { - core.OnCompleted(continuation, state, token); - } - - public bool MoveNext() - { - if (completed) - { - TryReturn(); - return false; - } - - if (cancellationToken.IsCancellationRequested) - { - completed = true; - core.TrySetCanceled(cancellationToken); - return false; - } - - return true; - } - - bool TryReturn() - { - TaskTracker.RemoveTracking(this); - core.Reset(); - asyncOperation = default; - cancellationToken = default; - return pool.TryPush(this); - } - } - sealed class AssetBundleCreateRequestConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; @@ -1232,17 +732,7 @@ namespace Cysharp.Threading.Tasks public static UniTask WithCancellation(this UnityWebRequestAsyncOperation asyncOperation, CancellationToken cancellationToken) { - Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); - if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled(cancellationToken); - if (asyncOperation.isDone) - { - if (asyncOperation.webRequest.IsError()) - { - return UniTask.FromException(new UnityWebRequestException(asyncOperation.webRequest)); - } - return UniTask.FromResult(asyncOperation.webRequest); - } - return new UniTask(UnityWebRequestAsyncOperationWithCancellationSource.Create(asyncOperation, cancellationToken, out var token), token); + return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } public static UniTask ToUniTask(this UnityWebRequestAsyncOperation asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) @@ -1312,138 +802,6 @@ namespace Cysharp.Threading.Tasks } } - sealed class UnityWebRequestAsyncOperationWithCancellationSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode - { - static TaskPool pool; - UnityWebRequestAsyncOperationWithCancellationSource nextNode; - public ref UnityWebRequestAsyncOperationWithCancellationSource NextNode => ref nextNode; - - static UnityWebRequestAsyncOperationWithCancellationSource() - { - TaskPool.RegisterSizeGetter(typeof(UnityWebRequestAsyncOperationWithCancellationSource), () => pool.Size); - } - - readonly Action continuationAction; - UnityWebRequestAsyncOperation asyncOperation; - CancellationToken cancellationToken; - bool completed; - - UniTaskCompletionSourceCore core; - - UnityWebRequestAsyncOperationWithCancellationSource() - { - continuationAction = Continuation; - } - - public static IUniTaskSource Create(UnityWebRequestAsyncOperation asyncOperation, CancellationToken cancellationToken, out short token) - { - if (cancellationToken.IsCancellationRequested) - { - return AutoResetUniTaskCompletionSource.CreateFromCanceled(cancellationToken, out token); - } - - if (!pool.TryPop(out var result)) - { - result = new UnityWebRequestAsyncOperationWithCancellationSource(); - } - - result.asyncOperation = asyncOperation; - result.cancellationToken = cancellationToken; - result.completed = false; - - TaskTracker.TrackActiveTask(result, 3); - - PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, result); - - asyncOperation.completed += result.continuationAction; - - token = result.core.Version; - return result; - } - - void Continuation(AsyncOperation _) - { - asyncOperation.completed -= continuationAction; - - if (completed) - { - TryReturn(); - } - else - { - completed = true; - if (cancellationToken.IsCancellationRequested) - { - core.TrySetCanceled(cancellationToken); - return; - } - - var result = asyncOperation.webRequest; - if (result.IsError()) - { - core.TrySetException(new UnityWebRequestException(result)); - } - else - { - core.TrySetResult(result); - } - } - } - - public UnityWebRequest GetResult(short token) - { - return core.GetResult(token); - } - - void IUniTaskSource.GetResult(short token) - { - GetResult(token); - } - - public UniTaskStatus GetStatus(short token) - { - return core.GetStatus(token); - } - - public UniTaskStatus UnsafeGetStatus() - { - return core.UnsafeGetStatus(); - } - - public void OnCompleted(Action continuation, object state, short token) - { - core.OnCompleted(continuation, state, token); - } - - public bool MoveNext() - { - if (completed) - { - TryReturn(); - return false; - } - - if (cancellationToken.IsCancellationRequested) - { - completed = true; - asyncOperation.webRequest.Abort(); - core.TrySetCanceled(cancellationToken); - return false; - } - - return true; - } - - bool TryReturn() - { - TaskTracker.RemoveTracking(this); - core.Reset(); - asyncOperation = default; - cancellationToken = default; - return pool.TryPush(this); - } - } - sealed class UnityWebRequestAsyncOperationConfiguredSource : IUniTaskSource, IPlayerLoopItem, ITaskPoolNode { static TaskPool pool; diff --git a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt index 9ebc356..65dac9e 100644 --- a/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt +++ b/src/UniTask/Assets/Plugins/UniTask/Runtime/UnityAsyncExtensions.tt @@ -11,7 +11,7 @@ ("ResourceRequest", "UnityEngine.Object", "asset"), ("AssetBundleRequest", "UnityEngine.Object", "asset"), // allAssets? ("AssetBundleCreateRequest", "AssetBundle", "assetBundle"), - ("UnityWebRequestAsyncOperation", "UnityWebRequest", "webRequest") // -> #if ENABLE_UNITYWEBREQUEST + ("UnityWebRequestAsyncOperation", "UnityWebRequest", "webRequest") // -> #if ENABLE_UNITYWEBREQUEST && (!UNITY_2019_1_OR_NEWER || UNITASK_WEBREQUEST_SUPPORT) }; Func ToUniTaskReturnType = x => (x == "void") ? "UniTask" : $"UniTask<{x}>"; @@ -27,7 +27,7 @@ using System.Runtime.CompilerServices; using System.Threading; using UnityEngine; using Cysharp.Threading.Tasks.Internal; -#if ENABLE_UNITYWEBREQUEST +#if ENABLE_UNITYWEBREQUEST && (!UNITY_2019_1_OR_NEWER || UNITASK_WEBREQUEST_SUPPORT) using UnityEngine.Networking; #endif @@ -37,7 +37,7 @@ namespace Cysharp.Threading.Tasks { <# foreach(var t in types) { #> <# if(IsUnityWebRequest(t)) { #> -#if ENABLE_UNITYWEBREQUEST +#if ENABLE_UNITYWEBREQUEST && (!UNITY_2019_1_OR_NEWER || UNITASK_WEBREQUEST_SUPPORT) <# } else if(IsAssetBundleModule(t)) { #> #if UNITASK_ASSETBUNDLE_SUPPORT <# } #> @@ -51,21 +51,7 @@ namespace Cysharp.Threading.Tasks public static <#= ToUniTaskReturnType(t.returnType) #> WithCancellation(this <#= t.typeName #> asyncOperation, CancellationToken cancellationToken) { - Error.ThrowArgumentNullException(asyncOperation, nameof(asyncOperation)); - if (cancellationToken.IsCancellationRequested) return UniTask.FromCanceled<#= IsVoid(t) ? "" : "<" + t.returnType + ">" #>(cancellationToken); -<# if(IsUnityWebRequest(t)) { #> - if (asyncOperation.isDone) - { - if (asyncOperation.webRequest.IsError()) - { - return UniTask.FromException(new UnityWebRequestException(asyncOperation.webRequest)); - } - return UniTask.FromResult(asyncOperation.webRequest); - } -<# } else { #> - if (asyncOperation.isDone) return <#= IsVoid(t) ? "UniTask.CompletedTask" : $"UniTask.FromResult(asyncOperation.{t.returnField})" #>; -<# } #> - return new <#= ToUniTaskReturnType(t.returnType) #>(<#= t.typeName #>WithCancellationSource.Create(asyncOperation, cancellationToken, out var token), token); + return ToUniTask(asyncOperation, cancellationToken: cancellationToken); } public static <#= ToUniTaskReturnType(t.returnType) #> ToUniTask(this <#= t.typeName #> asyncOperation, IProgress progress = null, PlayerLoopTiming timing = PlayerLoopTiming.Update, CancellationToken cancellationToken = default(CancellationToken)) @@ -151,150 +137,6 @@ namespace Cysharp.Threading.Tasks } } - sealed class <#= t.typeName #>WithCancellationSource : <#= ToIUniTaskSourceReturnType(t.returnType) #>, IPlayerLoopItem, ITaskPoolNode<<#= t.typeName #>WithCancellationSource> - { - static TaskPool<<#= t.typeName #>WithCancellationSource> pool; - <#= t.typeName #>WithCancellationSource nextNode; - public ref <#= t.typeName #>WithCancellationSource NextNode => ref nextNode; - - static <#= t.typeName #>WithCancellationSource() - { - TaskPool.RegisterSizeGetter(typeof(<#= t.typeName #>WithCancellationSource), () => pool.Size); - } - - readonly Action continuationAction; - <#= t.typeName #> asyncOperation; - CancellationToken cancellationToken; - bool completed; - - UniTaskCompletionSourceCore<<#= IsVoid(t) ? "AsyncUnit" : t.returnType #>> core; - - <#= t.typeName #>WithCancellationSource() - { - continuationAction = Continuation; - } - - public static <#= ToIUniTaskSourceReturnType(t.returnType) #> Create(<#= t.typeName #> asyncOperation, CancellationToken cancellationToken, out short token) - { - if (cancellationToken.IsCancellationRequested) - { - return AutoResetUniTaskCompletionSource<#= IsVoid(t) ? "" : $"<{t.returnType}>" #>.CreateFromCanceled(cancellationToken, out token); - } - - if (!pool.TryPop(out var result)) - { - result = new <#= t.typeName #>WithCancellationSource(); - } - - result.asyncOperation = asyncOperation; - result.cancellationToken = cancellationToken; - result.completed = false; - - TaskTracker.TrackActiveTask(result, 3); - - PlayerLoopHelper.AddAction(PlayerLoopTiming.Update, result); - - asyncOperation.completed += result.continuationAction; - - token = result.core.Version; - return result; - } - - void Continuation(AsyncOperation _) - { - asyncOperation.completed -= continuationAction; - - if (completed) - { - TryReturn(); - } - else - { - completed = true; - if (cancellationToken.IsCancellationRequested) - { - core.TrySetCanceled(cancellationToken); - return; - } - -<# if(IsUnityWebRequest(t)) { #> - var result = asyncOperation.webRequest; - if (result.IsError()) - { - core.TrySetException(new UnityWebRequestException(result)); - } - else - { - core.TrySetResult(result); - } -<# } else { #> - core.TrySetResult(<#= IsVoid(t) ? "AsyncUnit.Default" : $"asyncOperation.{t.returnField}" #>); -<# } #> - } - } - - public <#= t.returnType #> GetResult(short token) - { -<# if (!IsVoid(t)) { #> - return core.GetResult(token); -<# } else { #> - core.GetResult(token); -<# } #> - } - -<# if (!IsVoid(t)) { #> - void IUniTaskSource.GetResult(short token) - { - GetResult(token); - } -<# } #> - - public UniTaskStatus GetStatus(short token) - { - return core.GetStatus(token); - } - - public UniTaskStatus UnsafeGetStatus() - { - return core.UnsafeGetStatus(); - } - - public void OnCompleted(Action continuation, object state, short token) - { - core.OnCompleted(continuation, state, token); - } - - public bool MoveNext() - { - if (completed) - { - TryReturn(); - return false; - } - - if (cancellationToken.IsCancellationRequested) - { - completed = true; -<# if(IsUnityWebRequest(t)) { #> - asyncOperation.webRequest.Abort(); -<# } #> - core.TrySetCanceled(cancellationToken); - return false; - } - - return true; - } - - bool TryReturn() - { - TaskTracker.RemoveTracking(this); - core.Reset(); - asyncOperation = default; - cancellationToken = default; - return pool.TryPush(this); - } - } - sealed class <#= t.typeName #>ConfiguredSource : <#= ToIUniTaskSourceReturnType(t.returnType) #>, IPlayerLoopItem, ITaskPoolNode<<#= t.typeName #>ConfiguredSource> { static TaskPool<<#= t.typeName #>ConfiguredSource> pool; diff --git a/src/UniTask/Assets/Scenes/EditorTest1.cs b/src/UniTask/Assets/Scenes/EditorTest1.cs index 4872b8f..bae759d 100644 --- a/src/UniTask/Assets/Scenes/EditorTest1.cs +++ b/src/UniTask/Assets/Scenes/EditorTest1.cs @@ -35,6 +35,9 @@ public class Test1 Debug.Log("Dosomething 2"); await UniTask.Delay(1000, DelayType.DeltaTime); Debug.Log("Dosomething 3"); + Debug.Log("and Quit."); + + Environment.Exit(0); } } diff --git a/src/UniTask/Assets/Scenes/SandboxMain.cs b/src/UniTask/Assets/Scenes/SandboxMain.cs index cd489a9..8babe00 100644 --- a/src/UniTask/Assets/Scenes/SandboxMain.cs +++ b/src/UniTask/Assets/Scenes/SandboxMain.cs @@ -542,22 +542,62 @@ public class SandboxMain : MonoBehaviour { Debug.LogError(e); return; + + } } Debug.Log("TestAsync Finished."); } - CancellationTokenSource clickCancelSource = new CancellationTokenSource(); - TimeoutController timeoutController; + + async UniTaskVoid Start() { - timeoutController = new TimeoutController(clickCancelSource); - var defaultLoop = PlayerLoop.GetDefaultPlayerLoop(); - PlayerLoopHelper.Initialize(ref defaultLoop, InjectPlayerLoopTimings.All); + // UniTask.Delay(TimeSpan.FromSeconds(1)).TimeoutWithoutException - var cts = new CancellationTokenSource(); + + var currentLoop = PlayerLoop.GetDefaultPlayerLoop(); + PlayerLoopHelper.Initialize(ref currentLoop, InjectPlayerLoopTimings.Minimum); // minimum is Update | FixedUpdate | LastPostLateUpdate + + + + + + var cancelToken = new CancellationTokenSource(); + cancelButton.onClick.AddListener(()=> + { + cancelToken.Cancel(); // cancel from button click. + }); + + var timeoutToken = new CancellationTokenSource(); + timeoutToken.CancelAfterSlim(TimeSpan.FromSeconds(5)); // 5sec timeout. + + try + { + // combine token + var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancelToken.Token, timeoutToken.Token); + + await UnityWebRequest.Get("http://foo").SendWebRequest().WithCancellation(linkedTokenSource.Token); + } + catch (OperationCanceledException ex) + { + if (timeoutToken.IsCancellationRequested) + { + UnityEngine.Debug.Log("Timeout."); + } + else if (cancelToken.IsCancellationRequested) + { + UnityEngine.Debug.Log("Cancel clicked."); + } + _ = ex; + } + + + + + // TestAsync(cts.Token).Forget(); @@ -566,13 +606,13 @@ public class SandboxMain : MonoBehaviour // try timeout try { - await UniTask.Delay(TimeSpan.FromSeconds(2), cancellationToken: timeoutController.Timeout(TimeSpan.FromSeconds(3))); + //await UniTask.Delay(TimeSpan.FromSeconds(2), cancellationToken: timeoutController.Timeout(TimeSpan.FromSeconds(3))); UnityEngine.Debug.Log("Delay Complete, Reset(and reuse)."); - timeoutController.Reset(); + //timeoutController.Reset(); } catch (OperationCanceledException ex) { - UnityEngine.Debug.Log("Timeout! FromTimeout?:" + timeoutController.IsTimeout()); + //UnityEngine.Debug.Log("Timeout! FromTimeout?:" + timeoutController.IsTimeout()); _ = ex; } @@ -581,7 +621,7 @@ public class SandboxMain : MonoBehaviour cancelButton.onClick.AddListener(UniTask.UnityAction(async () => { - clickCancelSource.Cancel(); + //clickCancelSource.Cancel(); //RunCheck(PlayerLoopTiming.Initialization).Forget(); //RunCheck(PlayerLoopTiming.LastInitialization).Forget();