Best practise for using UniTask.WhenAny? #389
-
Hi, I've noticed that using WhenAny systematically creates leaked tasks:
When either event happens, the other task stays pending until the cancellationToken is triggered. Is there a way to dissolve the tasks after the await? |
Beta Was this translation helpful? Give feedback.
Replies: 6 comments 15 replies
-
Maybe unirx can help you.
|
Beta Was this translation helpful? Give feedback.
-
Isn't this default C# behavior? My suggestion would be that if you want the other tasks to be canceled then you do indeed cancel the token. |
Beta Was this translation helpful? Give feedback.
-
any update on this? |
Beta Was this translation helpful? Give feedback.
-
So to actually cancel the remaining task(s) you'd have to create a new token source and cancel it afterwards, right? Something like this:
Does not seem overly verbose to me but maybe there's something I'm missing? |
Beta Was this translation helpful? Give feedback.
-
Just check if the cancellationToken.IsCancellationRequested and don't await then. Once you await cancelling the completion source will throw a Task Cancelled Exception at the await as intended
…On March 26, 2025 3:12:45 PM GMT+01:00, Meister der Magie ***@***.***> wrote:
Interesting approach, but I wonder how you pass a CancellationToken to the _completionSource.
Let's take the example of @Blackclaws but add a CancellationToken parameter:
´´´
async UniTask ShowScreen(CancellationToken cancellationToken) {
_completionSource.TrySetCanceled();
_completionSource = new();
/* Screen show logic here */
await _completionSource.Task; //How to stop the await if the passed cancellationToken was cancelled? I can't find a solution for this.
/* more work here */
}
´´´
--
Reply to this email directly or view it on GitHub:
#389 (reply in thread)
You are receiving this because you were mentioned.
Message ID: ***@***.***>
|
Beta Was this translation helpful? Give feedback.
-
UniTask.WhenAny does not cancel the “losers” by design (same as Task.WhenAny). So those pending OnInvokeAsync tasks keep listening until you cancel or dispose them yourself. The clean pattern is: create a linked CancellationTokenSource, start both awaits with that token and keep the event handlers so you can dispose the one that didn’t win; right after WhenAny returns, cancel the linked CTS to stop the other await and dispose its handler. This avoids dangling subscriptions and makes the intent explicit.
Why this works: WhenAny only tells you who finished first; it never cancels others by itself (that would be surprising for callers and is not how .NET’s Task.WhenAny behaves). Passing a linked token ensures both awaits can be cancelled as a group after the winner is known. Holding the AsyncUnityEventHandler instances and wrapping them in using makes sure the underlying UnityEvent subscription is disposed deterministically, so you don’t keep listening after you’re done. If you prefer constructing the handler explicitly you can also do new AsyncUnityEventHandler(button.onClick, cts.Token, callOnce: true) and call OnInvokeAsync() on it — same idea. Two small tips:
Relevant docs if you want to double-check the signatures/behavior: WhenAny over UniTask returns the winner index; GetAsyncClickEventHandler/AsyncUnityEventHandler let you manage and dispose uGUI event subscriptions; and .NET’s Task.WhenAny spec explains why “auto-cancel the others” would be unexpected. ✅ |
Beta Was this translation helpful? Give feedback.
Right, we're doing the same. However in the case where you have a screen with two possible buttons, modelling that as a series of asynchronous awaits like you're doing with the WhenAny starts making less sense.
Interacting with multiple possibilities is usually more event based. What we do is the following:
When a User navigates to a screen, we start up that screen and the screen returns a UniTask that we can await in the parent screen.
However unless its something simple like a dialog that can only have one possible interaction, we do not return a composition of UniTasks like you do. What we do instead is the following:
defined at the class level.