diff --git a/osu.Game.Tests/NonVisual/TaskChainTest.cs b/osu.Game.Tests/NonVisual/TaskChainTest.cs index bd4f15a6eb..342f137dfd 100644 --- a/osu.Game.Tests/NonVisual/TaskChainTest.cs +++ b/osu.Game.Tests/NonVisual/TaskChainTest.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using osu.Game.Extensions; -using osu.Game.Utils; namespace osu.Game.Tests.NonVisual { diff --git a/osu.Game/Extensions/TaskExtensions.cs b/osu.Game/Extensions/TaskExtensions.cs index fd0274f39e..62b249b869 100644 --- a/osu.Game/Extensions/TaskExtensions.cs +++ b/osu.Game/Extensions/TaskExtensions.cs @@ -34,32 +34,46 @@ namespace osu.Game.Extensions }, TaskContinuationOptions.NotOnRanToCompletion); } - public static Task ContinueWithSequential(this Task task, Action continuationFunction, CancellationToken cancellationToken = default) - { - return task.ContinueWithSequential(() => Task.Run(continuationFunction, cancellationToken), cancellationToken); - } + /// + /// Add a continuation to be performed only after the attached task has completed. + /// + /// The previous task to be awaited on. + /// The action to run. + /// An optional cancellation token. Will only cancel the provided action, not the sequence. + /// A task representing the provided action. + public static Task ContinueWithSequential(this Task task, Action action, CancellationToken cancellationToken = default) => + task.ContinueWithSequential(() => Task.Run(action, cancellationToken), cancellationToken); + /// + /// Add a continuation to be performed only after the attached task has completed. + /// + /// The previous task to be awaited on. + /// The continuation to run. Generally should be an async function. + /// An optional cancellation token. Will only cancel the provided action, not the sequence. + /// A task representing the provided action. public static Task ContinueWithSequential(this Task task, Func continuationFunction, CancellationToken cancellationToken = default) { var tcs = new TaskCompletionSource(); task.ContinueWith(t => { + // the previous task has finished execution or been cancelled, so we can run the provided continuation. + if (cancellationToken.IsCancellationRequested) { tcs.SetCanceled(); } else { - continuationFunction().ContinueWith(t2 => + continuationFunction().ContinueWith(continuationTask => { - if (cancellationToken.IsCancellationRequested || t2.IsCanceled) + if (cancellationToken.IsCancellationRequested || continuationTask.IsCanceled) { tcs.TrySetCanceled(); } - else if (t2.IsFaulted) + else if (continuationTask.IsFaulted) { - tcs.TrySetException(t2.Exception); + tcs.TrySetException(continuationTask.Exception); } else { @@ -69,6 +83,8 @@ namespace osu.Game.Extensions } }, cancellationToken: default); + // importantly, we are not returning the continuation itself but rather a task which represents its status in sequential execution order. + // this will not be cancelled or completed until the previous task has also. return tcs.Task; } }