Equivalent of ContinueWith(delegate, CancellationToken) with await continuation - c#

I have that situation:
private Task LongRunningTask = /* Something */;
private void DoSomethingMore(Task previousTask) { }
public Task IndependentlyCancelableSuccessorTask(CancellationToken cancellationToken)
{
return LongRunningTask.ContinueWith(DoSomethingMore, cancellationToken);
}
In particular, the behavior that interests me here is detailed in MSDN's page about Continuation Tasks in the following terms:
A continuation goes into the Canceled state in these scenarios:
[...]
When the continuation was passed a System.Threading.CancellationToken as an argument and the IsCancellationRequested property of the token is true before the continuation runs. In such a case, the continuation does not start and it transitions to the Canceled state.
The code above works. However, I am in the process of converting as many as possible of my continuations to using the await keyword.
Is there an equivalent using await that would allow the continuation to be canceled before the awaited task completes?

The following should do it, albeit it looks a bit awkward:
private Task LongRunningTask = /* Something */;
private void DoSomethingMore() { }
public async Task IndependentlyCancelableSuccessorTask(
CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
var tcs = new TaskCompletionSource<bool>();
using (cancellationToken.Register(() => tcs.TrySetCanceled()))
await Task.WhenAny(LongRunningTask, tcs.Task);
cancellationToken.ThrowIfCancellationRequested();
DoSomethingMore();
}
[UPDATE] Following svick's suggestion, here it is shaped as a helper, based on Stephen Toub's Implementing Then with Await pattern:
public static class TaskExt
{
/// <summary>
/// Use: await LongRunningTask.Then(DoSomethingMore, cancellationToken)
/// </summary>
public static async Task Then(
this Task antecedent, Action continuation, CancellationToken token)
{
await antecedent.When(token);
continuation();
}
/// <summary>
/// Use: await LongRunningTask.When(cancellationToken)
/// </summary>
public static async Task When(
this Task antecedent, CancellationToken token)
{
token.ThrowIfCancellationRequested();
var tcs = new TaskCompletionSource<Empty>();
using (token.Register(() => tcs.TrySetCanceled()))
await Task.WhenAny(antecedent, tcs.Task);
token.ThrowIfCancellationRequested();
}
struct Empty { };
}
Perhaps, the first ThrowIfCancellationRequested() is redundant, but I haven't thoroughly considered all edge cases.

While this answer is conceptually the same as Noseratio's, I am not satisfied by a few details of the implementation, and as such am publishing my proposed implementation of the helper so that it can be commented on by other people on this question.
public static async Task<TResult> WhenNotCanceled<TResult>(this Task<TResult> mainTask, CancellationToken cancellationToken)
{
if (!cancellationToken.CanBeCanceled) {
return await mainTask.ConfigureAwait(false);
}
cancellationToken.ThrowIfCancellationRequested();
Task<TResult> completedTask;
var cancellationTaskSource = new TaskCompletionSource<TResult>();
using (cancellationToken.Register(() => cancellationTaskSource.TrySetCanceled(), useSynchronizationContext: false)
completedTask = await Task.WhenAny(mainTask, cancellationTaskSource.Task).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
return await completedTask.ConfigureAwait(false);
}
public static async Task WhenNotCanceled(this Task mainTask, CancellationToken cancellationToken)
{
if (!cancellationToken.CanBeCanceled) {
await mainTask.ConfigureAwait(false);
return;
}
cancellationToken.ThrowIfCancellationRequested();
Task completedTask;
var cancellationTaskSource = new TaskCompletionSource<object>();
using (cancellationToken.Register(() => cancellationTaskSource.TrySetCanceled(), useSynchronizationContext: false)
completedTask = await Task.WhenAny(mainTask, cancellationTaskSource.Task).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
await completedTask.ConfigureAwait(false);
}
Async pattern without cancel:
public async Task IndependentlyCancelableSuccessorTask()
{
await LongRunningTask;
DoSomethingMore();
}
Async pattern with cancel and WhenNotCanceled:
public async Task IndependentlyCancelableSuccessorTask(CancellationToken cancellationToken)
{
await LongRunningTask.WhenNotCanceled(cancellationToken);
DoSomethingMore();
}

My answer is only slightly different than #Jean Hominal's answer and incorporates #Noseratio's approach as well:
public static class TaskExtensionMethods
{
public static Task<TResult> OrWhenCancelled<TResult>(this Task<TResult> mainTask, CancellationToken cancellationToken)
{
if (!cancellationToken.CanBeCanceled)
return mainTask;
return OrWhenCancelled_(mainTask, cancellationToken);
}
private static async Task<TResult> OrWhenCancelled_<TResult>(this Task<TResult> mainTask, CancellationToken cancellationToken)
{
Task cancellationTask = Task.Delay(Timeout.Infinite, cancellationToken);
await Task.WhenAny(mainTask, cancellationTask).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
return await mainTask;
}
public static Task OrWhenCancelled(this Task mainTask, CancellationToken cancellationToken)
{
if (!cancellationToken.CanBeCanceled)
return mainTask;
return OrWhenCancelled_(mainTask, cancellationToken);
}
private static async Task OrWhenCancelled_(this Task mainTask, CancellationToken cancellationToken)
{
Task cancellationTask = Task.Delay(Timeout.Infinite, cancellationToken);
await Task.WhenAny(mainTask, cancellationTask).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
await mainTask;
}
}
Discussion:
All of the solutions (including this one), do not correctly handle the case where the original ContinueWith specified a TaskScheduler. Specifically, consider a TaskScheduler created TaskScheduler.FromCurrentSynchronizationContext for usage in UI scenarios. In that case, with the original ContinueWith approach you were guaranteed that the cancellation token was checked prior to running the delegate but after already getting on to Main thread (see this answer). That is, the old approach has the nice effect of checking the Cancellation token "one last time" on the main thread prior to considering the result of the task (i.e. trumping whether the main task finished or faulted). This means that in addition to using these extension methods, the new code must wrap its await in a try/finally to do its final check of the CancellationToken :(. See this question.
#Noseratio's solution could handle the above issue (if needed), but it has the downside of requiring that continuation be placed into a delegate. In my opinion, this defeats one of the big advantages of converting to using await: the code doesn't end up in a delegate, it is just after an await and reads like normal sequential code.
Notes:
I wish I could have specified that the empty lambda never runs (i.e. instead of only running on cancellation), but the .ContinueWith method doesn't allow that. So, I (mostly arbitrarily chose OnlyOnCancelled)

This answer comes from #Servy from this answer (with modifications):
public static Task WithCancellation(this Task task,
CancellationToken token)
{
return task.ContinueWith(t => t.GetAwaiter().GetResult(), token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}
public static Task<T> WithCancellation<T>(this Task<T> task,
CancellationToken token)
{
return task.ContinueWith(t => t.GetAwaiter().GetResult(), token, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
}

Related

How to cancel ValueTask<T>

I know how to cancel a Task, but couldn't find any information on how to add cancellation to ValueTask methods.
Normally I would cancel a Task like this:
public async Task<int> Foo(
CancellationToken cancellationToken)
{
TaskCompletionSource<int> tcsCancel =
new TaskCompletionSource<int>();
cancellationToken.Register(() =>
{
tcsCancel.TrySetCanceled();
});
Task<int> task = LongOperation();
var completedTask = await Task.WhenAny(
tcsCancel.Task,
task).ConfigureAwait(false);
return await completedTask.ConfigureAwait(false);
}
Or like this:
if (cancellationToken.IsCancellationRequested)
return Task.FromCanceled<int>(cancellationToken);
The things is, that ValueTask has neither FromCanceled nor WhenAny. Should I just do ...
cancellationToken.ThrowIfCancellationRequested();
The kind of "cancellation" you're referring to isn't a real cancellation; you're cancelling the wait, not the operation.
You can do the same kind of fake-cancellation with ValueTask<T>; you just need to convert it to Task<T> in order to do it:
public async ValueTask<int> Foo(CancellationToken cancellationToken)
{
using var tcsCancel = new TaskCompletionSource<int>();
using var registration = cancellationToken.Register(() =>
{
tcsCancel.TrySetCanceled();
});
ValueTask<int> valueTask = LongOperation();
Task<int> task = valueTask.AsTask();
var completedTask = await Task.WhenAny(
tcsCancel.Task,
task).ConfigureAwait(false);
return await completedTask.ConfigureAwait(false);
}

Syncronizing Async Tasks Across Threads

Trying to figure out the best way to architect a solution for this problem
HTTP Request Pipeline when Handling Authentication Middleware. runs OnTokenValidated in the JwtBearerOptions is executed in parallel to my requests code. I could run this synchronously but I would prefer not to,
Some of my request Require this claim, how can I be assure that this claim has been set?
I'm running two threads parallel,
one async function is dependent on the other thread to set a variable
I created an await manager to get my task.
However, There is a chance the await manager has not set the key for task because i didn't execute.
My code:
public class TaskManager : ITaskManager
{
Dictionary<string, Task> _taskAwaiterMap = new Dictionary<string, Task>();
public Task GetTaskForKey(string key)
{
this._taskAwaiterMap.TryGetValue(key, out var awaiter);
return awaiter;
}
public void QueueTask(string key, Task task)
{
this._taskAwaiterMap[key] = task;
}
}
Edit
public async Task GetManditoryTaskForKey(string key, int timeout)
{
var cancellationTokenSource = new CancellationTokenSource(timeout);
await GetManditoryResolverWaitTask(key, cancellationTokenSource.Token);
}
protected async Task<Task> GetManditoryResolverWaitTask(string key, CancellationToken cancellationToken)
{
while (false == this._taskAwaiterMap.TryGetValue(key, out var task))
{
if(cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}
await Task.Yield();
}
return this.GetTaskForKey(key);
}
I've figure out a way to await for my key to be set but is the an efficient way for synchronizing async tasks?
EDIT
In my Http Pipe Line I would Queue a task like so:
OnTokenValidated = async tvc => { await AuthenticationRule.ValidateToken(tvc); }
//... Source ValidateToken
//Some Potentially long Task
var awaitManager = serviceProvider.GetRequiredService<ITaskManager>();
var userClaimsTask = SetUserClaims(claimsIdentity, context, userSubjectId);
awaitManager.QueueTask(USER_CLAIM_AWAITER_KEY, userClaimsTask);
How can I efficiently synchronize my async Tasks?
I was trying to answer your previous question. :)
Anyway, I am not certain that this is the best solution for your problem because I am not familiar with the Async HTTP pipeline. But the following can be a solution for the approach you are taking:
public class TaskManager : ITaskManager
{
private readonly ConcurrentDictionary<string, TaskCompletionSource<Task>> _taskAwaiterMap = new ConcurrentDictionary<string, TaskCompletionSource<Task>>();
public async Task GetTaskForKey(string key)
{
await await this._taskAwaiterMap.GetOrAdd(key, _ => new TaskCompletionSource<Task>()).Task;
}
public void QueueTask(string key, Task task)
{
this._taskAwaiterMap.GetOrAdd(key, _ => new TaskCompletionSource<Task>()).SetResult(task);
}
}
I assume TaskManager will only live within the session. Otherwise, you will have to think of how to clean up _taskAwaiterMap.

How to dispatch every previous Task that was created by particular event

I have an event in my WPF .NET 4.5 application that can be triggered up to 10 times per second, the event is computing data that is not required that often so I'm looking for a way to remove unnecessary computation stress and call the method if event wasn't triggered for 3 seconds. I thought about using async functionality like that:
private async Task DoWork()
{
await Task.Delay(3000);
...
}
private void Event()
{
Task.Run(() => DoWork());
}
I'm not really sure about how to elegantly handle disposing of unwanted Tasks, ie. when user trigger the event, every task that was created by that event should be terminated and it should be as fast as possible. I've tried with CancellationToken but I'm not sure this is the right approach for my case
You can use CancellationTokenSource to let the code inside task know that it is canceled:
private CancellationTokenSource CancellationTokenSource { get; } = new CancellationTokenSource ();
Let's change:
private async Task DoWork()
{
await Task.Delay(3000);
...
}
To:
private async Task DoWork(int timeout = 3000)
{
await Task.Delay(timeout, CancellationTokenSource.Token);
if(!CancellationTokenSource.Token.IsCancellationRequested)
{
...
}
}
Now we can cancel our task if required:
CancellationTokenSource.Cancel();
Task.Delay will observe the CancellationToken and if it is in a Canceled state it will abort the task execution. Later in code we check whether we need to do anything or not.
That was my solution based on #Fabjan answer.
private static void RunSingleTask(ref CancellationTokenSource cts, int delay, Action func)
{
if (cts != null)
{
cts.Cancel();
cts.Dispose();
}
cts = new CancellationTokenSource();
var token = cts.Token;
Task.Run(async () =>
{
try
{
await Task.Delay(delay, token);
}
catch (TaskCanceledException)
{
return;
}
await Application.Current.Dispatcher.BeginInvoke(func);
});
}

Does cancelling a CancellationToken cause a CancellationToken Exception?

I have this code and I would like to get some clarification on the use of the CancellationToken.
I read this question about the difference between using a cancellation token and a flag:
Difference between CancellationTokenSource and exit flag for Task loop exit
One thing I noticed is that it mentions nothing about Exceptions. So here's my question. If the Disappearing() method is called then will this cause a TaskCanceledException() to occur and would this be a good reason to use the CancellationToken instead of a flag?
public partial class PhrasesFrame : Frame
{
CancellationTokenSource cts = new CancellationTokenSource();
public PhrasesFrame(PhrasesPage phrasesPage)
{
Device.BeginInvokeOnMainThread(() => ShowCards(cts.Token).ContinueWith((arg) => { }));
}
public void Disappearing()
{
cts.Cancel();
}
public async Task ShowCards(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
await PickCard();
}
}
public async Task PickCard()
{
await ShowCard();
}
private async Task ShowCard()
{
await ShowPhrase();
await ShowDetail();
}
private async Task ShowPhrase()
{
while (App.pauseCard || timer1Seconds > 0)
{
try
{
await Task.Delay(1000, tokenSource1.Token);
}
catch (TaskCanceledException)
{
// do action
break;
}
}
CancellationTokenSource.Cancel by itself doesn't throw such exception, but it "moves" all related cancellation tokens to cancelled state. When some code notifies that cancellation token is now in cancelled state - it might throw such exception. If look at your example, this part will not throw such exception:
public async Task ShowCards(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
await PickCard();
}
}
Because you just don't throw it in this block. If however you instead did something like this:
public async Task ShowCards(CancellationToken ct)
{
while (true)
{
ct.ThrowIfCancellationRequested();
await PickCard();
}
}
Then exception will be thrown, because, well, you throw it almost explicitly.
Now if look at another method from your example:
private async Task ShowPhrase()
{
while (App.pauseCard || timer1Seconds > 0)
{
try
{
await Task.Delay(1000, tokenSource1.Token);
}
catch (TaskCanceledException)
{
// do action
break;
}
}
}
If you were awaiting Task.Delay(1000, tokenSource1.Token); and then cancel tokenSource1 - then TaskCancelledException will indeed be thrown immediately, without waiting for the whole duration of Task.Delay. This is something you cannot easily achieve with just a boolean flag. If you used Thread.Sleep(1000) and boolean flag - change to that flag won't be noticed until whole duration of sleep is finished.
So to answer your question: in your example exception might or might not be thrown, depending on what part of code is currently executing at the moment you cancel your CancellationTokenSource (I assume that using two cancellation token sources with names cts and tokenSource1 is just a typo in your code, but if it's real code - then such exception cannot be thrown at all, because you cancel cts but Task.Delay waits on tokenSource1).

How to make a nonblocking wait handle?

Essentially, what I'm doing is creating a web server to handle an API call, and then when it's done continue the method execution, so essentially:
new WebServer(myAutoResetEvent);
myAutoResetEvent.WaitOne();
However, this blocks the thread until then. Is there any way to make this async? Is it fine just to wrap it in a await Task.Run() call, i.e. await Task.Run(() => myAutoResetEvent.WaitOne())?
Thanks!
Normally, the WebServer ctor should not do anything interesting. There should be a Task WebServer.RunAsync function that runs the server. You can then use the resulting task to synchronize and coordinate.
If you don't want that you can use a TaskCompletionSource<object> as a one-shot async-ready event.
I believe the ThreadPool class has a way to efficiently wait for a WaitHandle to be set but that's a worse solution.
You should not block ThreadPool threads, this is a quick way to lead to ThreadPool starvation, instead there is a provided method to asynchronously wait for WaitHandle instances, this is called ThreadPool.RegisterWaitForSingleObject.
By using ThreadPool.RegisterWaitForSingleObject a callback is registered to be invoked when the WaitHandle is available, unfortunately this is not async/await compatible out of the box, a full implementation which makes this async/await compatible is as follows:
public static class WaitHandleExtensions
{
public static Task WaitOneAsync(this WaitHandle waitHandle, CancellationToken cancellationToken)
{
return WaitOneAsync(waitHandle, Timeout.Infinite, cancellationToken);
}
public static async Task<bool> WaitOneAsync(this WaitHandle waitHandle, int timeout, CancellationToken cancellationToken)
{
// A Mutex can't use RegisterWaitForSingleObject as a Mutex requires the wait and release to be on the same thread
// but RegisterWaitForSingleObject acquires the Mutex on a ThreadPool thread.
if (waitHandle is Mutex)
throw new ArgumentException(StringResources.MutexMayNotBeUsedWithWaitOneAsyncAsThreadIdentityIsEnforced, nameof(waitHandle));
cancellationToken.ThrowIfCancellationRequested();
var tcs = new TaskCompletionSource<bool>();
var rwh = ThreadPool.RegisterWaitForSingleObject(waitHandle, OnWaitOrTimerCallback, tcs, timeout, true);
var cancellationCallback = BuildCancellationCallback(rwh, tcs);
using (cancellationToken.Register(cancellationCallback))
{
try
{
return await tcs.Task.ConfigureAwait(false);
}
finally
{
rwh.Unregister(null);
}
}
}
private static Action BuildCancellationCallback(RegisteredWaitHandle rwh, TaskCompletionSource<bool> tcs)
{
return () =>
{
if (rwh.Unregister(null))
{
tcs.SetCanceled();
}
};
}
private static void OnWaitOrTimerCallback(object state, bool timedOut)
{
var taskCompletionSource = (TaskCompletionSource<bool>)state;
taskCompletionSource.SetResult(!timedOut);
}
}
The only limitation is that this cannot be used with a Mutex.
This can be used like so:
await myAutoResetEvent.WaitOneAsync(cancellationToken).ConfigureAwait(false);
Another approach to consider would be to use HttpSelfHostServer (System.Web.Http.SelfHost.dll) and leaving all of the threading details to its implementation.
var config = new HttpSelfHostConfiguration("http://localhost:9999");
var tcs = new TaskCompletionSource<Uri>();
using (var server = new HttpSelfHostServer(config, new MessageHandler(tcs)))
{
await server.OpenAsync();
await tcs.Task;
await server.CloseAsync();
}
return tcs.Task.Result;
class MessageHandler : HttpMessageHandler
{
private readonly TaskCompletionSource<Uri> _task;
public MessageHandler(TaskCompletionSource<Uri> task)
{
_task = task;
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
_task.SetResult(request.RequestUri);
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK));
}
}

Categories