I started my first project with asp.net core and razor pages.
On a client request, a long running database operation will be started.
Now i want to recognize, when the user leaves the website, so the database operation can be cancelled.
I've tried it with a cancellationToken, but it will never be cancelled.
public async Task<JsonResult> OnPostReadAsync([DataSourceRequest] DataSourceRequest request, CancellationToken cancellationToken)
{
var messages = await _logMessageService.GetLogMessagesAsync(request, cancellationToken);
return new JsonResult(messages.ToDataSourceResult(request));
}
The function get called by an Telerik Kendo UI Grid.
Can you tell me, why the cancellation token don't get cancelled, or what other options I have to detect a abortion by the client?
Edit 1
I pass the Token through to this function call of a NpgsqlCommand:
var dataReader = await command.ExecuteReaderAsync(cancellationToken);
To cancel IO bound i.e. task which is running long , following is code that you can do which i got form the C# with CLR book.
Design extension method for task as below.
private static async Task<TResult> WithCancellation<TResult>(this Task<TResult> originalTask,
CancellationToken ct) {
// Create a Task that completes when the CancellationToken is canceled
var cancelTask = new TaskCompletionSource<Void>();
// When the CancellationToken is canceled, complete the Task
using (ct.Register(
t => ((TaskCompletionSource<Void>)t).TrySetResult(new Void()), cancelTask)) {
// Create a Task that completes when either the original or
// CancellationToken Task completes
Task any = await Task.WhenAny(originalTask, cancelTask.Task);
// If any Task completes due to CancellationToken, throw OperationCanceledException
if (any == cancelTask.Task) ct.ThrowIfCancellationRequested();
}
// await original task (synchronously); if it failed, awaiting it
// throws 1st inner exception instead of AggregateException
return await originalTask;
}
as given in following example code you can cancel it by using extension method designed above.
public static async Task Go() {
// Create a CancellationTokenSource that cancels itself after # milliseconds
var cts = new CancellationTokenSource(5000); // To cancel sooner, call cts.Cancel()
var ct = cts.Token;
try {
// I used Task.Delay for testing; replace this with another method that returns a Task
await Task.Delay(10000).WithCancellation(ct);
Console.WriteLine("Task completed");
}
catch (OperationCanceledException) {
Console.WriteLine("Task cancelled");
}
}
In this example cancellation done based on given time , but you can call cancellation by calling cancel method.
So I found the answer myself after some more research.
The problem is a bug in IISExpress as mentioned here: https://github.com/aspnet/Mvc/issues/5239#issuecomment-323567952
I switched to Kestrel and now everything works as expected.
Related
The Task.Run method has overloads that accept both sync and async delegates:
public static Task Run (Action action);
public static Task Run (Func<Task> function);
Unfortunately these overloads don't behave the same when the delegate throws an OperationCanceledException. The sync delegate results in a Faulted task, and the async delegate results in a Canceled task. Here is a minimal demonstration of this behavior:
CancellationToken token = new(canceled: true);
Task taskSync = Task.Run(() => token.ThrowIfCancellationRequested());
Task taskAsync = Task.Run(async () => token.ThrowIfCancellationRequested());
try { Task.WaitAll(taskSync, taskAsync); } catch { }
Console.WriteLine($"taskSync.Status: {taskSync.Status}"); // Faulted
Console.WriteLine($"taskAsync.Status: {taskAsync.Status}"); // Canceled (undesirable)
Output:
taskSync.Status: Faulted
taskAsync.Status: Canceled
Online demo.
This inconsistency has been observed also in this question:
Faulted vs Canceled task status after CancellationToken.ThrowIfCancellationRequested
That question asks about the "why". My question here is how to fix it. Specifically my question is: How to implement a variant of the Task.Run with async delegate, that behaves like the built-in Task.Run with sync delegate? In case of an OperationCanceledException it should complete asynchronously as Faulted, except when the supplied cancellationToken argument matches the token stored in the exception, in which case it should complete as Canceled.
public static Task TaskRun2 (Func<Task> action,
CancellationToken cancellationToken = default);
Here is some code with the desirable behavior of the requested method:
CancellationToken token = new(canceled: true);
Task taskA = TaskRun2(async () => token.ThrowIfCancellationRequested());
Task taskB = TaskRun2(async () => token.ThrowIfCancellationRequested(), token);
try { Task.WaitAll(taskA, taskB); } catch { }
Console.WriteLine($"taskA.Status: {taskA.Status}");
Console.WriteLine($"taskB.Status: {taskB.Status}");
Desirable output:
taskA.Status: Faulted
taskB.Status: Canceled
One way to solve this problem is to use the existing Task.Run method, and attach a ContinueWith continuation that alters its cancellation behavior. The continuation returns either the original task or a faulted task, so it returns a Task<Task>. Eventually this nested task is unwrapped to a simple Task, with the help of the Unwrap method:
/// <summary>
/// Queues the specified work to run on the thread pool, and returns a proxy
/// for the task returned by the action. The cancellation behavior is the same
/// with the Task.Run(Action, CancellationToken) method.
/// </summary>
public static Task TaskRun2 (Func<Task> action,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(action);
return Task.Run(action, cancellationToken).ContinueWith(t =>
{
if (t.IsCanceled)
{
CancellationToken taskToken = new TaskCanceledException(t).CancellationToken;
if (taskToken != cancellationToken)
return Task.FromException(new OperationCanceledException(taskToken));
}
return t;
}, CancellationToken.None, TaskContinuationOptions.DenyChildAttach |
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default).Unwrap();
}
Online demo.
The TaskCanceledException constructor is used in order to get access to the CancellationToken stored inside the canceled Task. Unfortunately the Task.CancellationToken property is not exposed (it is internal), but fortunately this workaround exists.
I have code that creates a CancellationTokenSource and that passes it to a method.
I have code in another are of the app that issues a cts.Cancel();
Is there a way that I can cause that method to stop immediately without me having to wait for the two lines inside the while loop to finish?
Note that I would be okay if it caused an exception that I could handle.
public async Task OnAppearing()
{
cts = new CancellationTokenSource();
await GetCards(cts.Token);
}
public async Task GetCards(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
App.viewablePhrases = App.DB.GetViewablePhrases(Settings.Mode, Settings.Pts);
await CheckAvailability();
}
}
What I can suggest:
Modify GetViewablePhrases and CheckAvailability so you could pass the CancellationToken to them;
Use ct.ThrowIfCancellationRequested() inside these functions;
Try / Catch the OperationCanceledException inside GetCards;
As for your your functions I don't know how exactly they work inside. But let's assume you have a long running iteration inside one of them:
CheckAvailability(CancellationToken ct)
{
for(;;)
{
// if cts.Cancel() was executed - this method throws the OperationCanceledException
// if it wasn't the method does nothing
ct.ThrowIfCancellationRequested();
...calculations...
}
}
Or let's say you are going to access your database inside one of the function and you know that this process is going to take a while:
CheckAvailability(CancellationToken ct)
{
ct.ThrowIfCancellationRequested();
AccessingDatabase();
}
This will not only prevent your functions from proceeding with execution, this also will set the executioner Task status as TaskStatus.Canceled
And don't forget to catch the Exception:
public async Task GetCards(CancellationToken ct)
{
try
{
App.viewablePhrases = App.DB.GetViewablePhrases(Settings.Mode, Settings.Pts, ct);
await CheckAvailability(ct);
}
catch(OperationCanceledException ex)
{
// handle the cancelation...
}
catch
{
// handle the unexpected exception
}
}
If you are OK with cancelling not the task, but the awaiting of the task, you could use a cancelable wrapper. In case of cancellation the underlying task will continue running, but the wrapper will complete immediately as canceled.
public static Task AsCancelable(this Task task,
CancellationToken cancellationToken)
{
var cancelable = new Task(() => { }, cancellationToken);
return Task.WhenAny(task, cancelable).Unwrap();
}
public static Task<T> AsCancelable<T>(this Task<T> task,
CancellationToken cancellationToken)
{
var cancelable = new Task<T>(() => default, cancellationToken);
return Task.WhenAny(task, cancelable).Unwrap();
}
Usage example:
await GetCards(cts.Token).AsCancelable(cts.Token);
This extension method can also be implemented using a TaskCompletionSource<T> (instead of the Task<T> constructor).
I have an method in which I am using Task.Delay for 1 minute. So when I want to try cancel that Task then it's giving me error like system.threading.tasks.taskcanceledexception a task was canceled instead of cancel that Task.
So how can I cancel that Task with handle this error.
public static System.Threading.CancellationTokenSource tokenSource = new System.Threading.CancellationTokenSource();
tokenSource.cancel();
public static async void waitForSignal(System.Threading.CancellationToken token)
{
await Task.Delay(60000, token); //here I am getting error while I am defining tokenSource cancel.
}
So how can I cancel that Task with handle this error.
You're already canceling the task; you just need to handle the error:
try
{
await Task.Delay(60000, token);
}
catch (OperationCanceledException)
{
}
...
Actually admin is getting signals from wcf service in which if admin will not get expected signals from wcf then it will wait for 60 seconds and if admin will get expected numbers of signals in first 10 seconds then it will not required to wait for next 50 seconds.
Sounds like what you want is a signal, not a cancellation. You want to (asynchronously) wait for either a signal or a time period (delay), after which you want to take some action. You don't really want cancellation here.
One kind of asynchronous signal is TaskCompletionSource<T>. Your action code can await the Task of that TCS, and the signaling code can call SetResult to send the signal. Something like this:
public static TaskCompletionSource<object> signal = new TaskCompletionSource<object>();
...
signal.SetResult(null);
...
public static async Task waitForSignal(Task signalTask)
{
await Task.WhenAny(Task.Delay(60000), signalTask);
}
OK, here goes: I have a part of an application where I am querying rows from a database. I perform the query when the user enters text into a search box (or alters another filter setting).
The data that is returned from the database is going into an ObservableCollection which is bound to a DataGrid. Because I'm conscious of keeping the UI responsive, I'm using Async-Await to (attempt) to fill this ObservableCollection in the background.
So, in my mind, every time the user types something (or changes the filter settings) I want to cancel the ongoing task wait for it to confirm it's cancelled and then "restart" (or rather create a new task) with the new settings.
But I'm getting all sorts of weird results (especially when I slow down the task to simulate slow database access) such as the collection not getting cleared and being populated twice and when disposing the CancellationTokenSource (which I read is a good idea) sometimes when I get to the point of calling Cancel() it's been disposed in the meantime and I get an exception.
I suspect that the issue stems from a fundamental gap in my understanding of the pattern I'm meant to use here so any style/pattern pointers are as welcome as an actual technical solution.
The code basically goes like this:
ObservableCollection<Thing> _thingCollection;
Task _thingUpdaterTask;
CancellationTokenSource _thingUpdaterCancellationSource;
// initialisation etc. here
async void PopulateThings(ThingFilterSettings settings)
{
// try to cancel any ongoing task
if(_thingUpdaterTask?.IsCompleted ?? false){
_thingUpdaterCancellationSource.Cancel();
await _thingUpdaterTask;
}
// I'm hoping that any ongoing task is now done with,
// but in reality that isn't happening. I'm guessing
// that's because Tasks are getting dereferenced and
// orphaned in concurrent calls to this method?
_thingCollection.Clear();
_thingUpdaterCancellationSource = new CancellationTokenSource();
var cancellationToken = _thingUpdaterCancellationSource.Token;
var progressHandler = new Progress<Thing>(x => _thingCollection.add(x));
var progress = (IProgress<Thing>)progressHandler;
try{
_thingUpdaterTask = Task.Factory.StartNew(
() => GetThings(settings, progress, cancellationToken));
await _thingUpdaterTask;
}catch(AggregateException e){
//handle stuff etc.
}finally{
// should I be disposing the Token Source here?
}
}
void GetThings(ThingFilterSettings settings,
IProgress<Thing> progress,
CancellationToken ctok){
foreach(var thingy in SomeGetThingsMethod(settings)){
if(ctok.IsCancellationRequested){
break;
}
progress.Report(thingy);
}
}
You could add a wrapper class, that will wait for the previous task execution to stop (either by finishing or by canceling) before starting the new task.
public class ChainableTask
{
private readonly Task _task;
private readonly CancellationTokenSource _cts = new CancellationTokenSource();
public ChainableTask(Func<CancellationToken, Task> asyncAction,
ChainableTask previous = null)
{
_task = Execute(asyncAction, previous);
}
private async Task CancelAsync()
{
try
{
_cts.Cancel();
await _task;
}
catch (OperationCanceledException)
{ }
}
private async Task Execute(Func<CancellationToken, Task> asyncAction, ChainableTask previous)
{
if (previous != null)
await previous.CancelAsync();
if (_cts.IsCancellationRequested)
return;
await asyncAction(_cts.Token);
}
}
If used the class above in previous projects. The class takes a lambda, the asyncAction to create the next task. The task is only created after the previous has finished.
It will pass a CancellationToken to each task, to allow the task to stop before finishing. Before the starting the next task, the token of the previous is canceled and the previous task is awaited. This happens in CancelAsync.
Only after the previous Cancel was awaited, we call the lambda to create the next task.
A usage example:
var firstAction = new ChainableTask(async tcs => await Task.Delay(1000));
var secondAction = new ChainableTask(async tcs => await Task.Delay(1000), firstAction ); // pass the previous action
In this example, the created task does not support cancellation, so the second call to ChainableTask will wait until the first Task.Delay(1000) finishes, before calling the second.
I'm trying to write a wrapper for arbitrary code that will cancel (or at least stop waiting for) the code after a given timeout period.
I have the following test and implementation
[Test]
public void Policy_TimeoutExpires_DoStuff_TaskShouldNotContinue()
{
var cts = new CancellationTokenSource();
var fakeService = new Mock<IFakeService>();
IExecutionPolicy policy = new TimeoutPolicy(new ExecutionTimeout(20), new DefaultExecutionPolicy());
Assert.Throws<TimeoutException>(async () => await policy.ExecuteAsync(() => DoStuff(3000, fakeService.Object), cts.Token));
fakeService.Verify(f=>f.DoStuff(),Times.Never);
}
and the "DoStuff" method
private static async Task DoStuff(int sleepTime, IFakeService fakeService)
{
await Task.Delay(sleepTime).ConfigureAwait(false);
var result = await Task.FromResult("bob");
var test = result + "test";
fakeService.DoStuff();
}
And the implementation of IExecutionPolicy.ExecuteAsync
public async Task ExecuteAsync(Action action, CancellationToken token)
{
var cts = new CancellationTokenSource();//TODO: resolve ignoring the token we were given!
var task = _decoratedPolicy.ExecuteAsync(action, cts.Token);
cts.CancelAfter(_timeout);
try
{
await task.ConfigureAwait(false);
}
catch(OperationCanceledException err)
{
throw new TimeoutException("The task did not complete within the TimeoutExecutionPolicy window of" + _timeout + "ms", err);
}
}
What should happen is that that the test method attempts to take >3000ms and the timeout should occur at 20ms, but this is not happening. Why does my code not timeout as expected?
EDIT:
As requested - the decoratedPolicy is as follows
public async Task ExecuteAsync(Action action, CancellationToken token)
{
token.ThrowIfCancellationRequested();
await Task.Factory.StartNew(action.Invoke, token);
}
If I understand correctly, you're trying to support timeout for a method which doesn't supports timeout / cancellation.
Typically this is done with a starting a timer with required timeout value. If the timer fires first, then you can throw exception. With TPL, you can use Task.Delay(_timeout) instead of a timer.
public async Task ExecuteAsync(Action action, CancellationToken token)
{
var task = _decoratedPolicy.ExecuteAsync(action, token);
var completed = await Task.WhenAny(task, Task.Delay(_timeout));
if (completed != task)
{
throw new TimeoutException("The task did not complete within the TimeoutExecutionPolicy window of" + _timeout + "ms");
}
}
Note: This doesn't stops the execution of _decoratedPolicy.ExecuteAsync method rather it ignores it.
If your method do support cancellation(but not in a timely manner) then it is better to cancel the Task after the timeout. You can do it by creating a Linked Token.
public async Task ExecuteAsync(Action action, CancellationToken token)
{
using(var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token))
{
var task = _decoratedPolicy.ExecuteAsync(action, linkedTokenSource.Token);
var completed = await Task.WhenAny(task, Task.Delay(_timeout));
if (completed != task)
{
linkedTokenSource.Cancel();//Try to cancel the method
throw new TimeoutException("The task did not complete within the TimeoutExecutionPolicy window of" + _timeout + "ms");
}
}
}
Using CancellationToken means that you're doing cooperative cancellation. Setting CancellationTokenSource.CancelAfter will transition the underlying token to a canceled state after the specified amount of time, but if that token isn't monitored by the calling async method, then nothing will happen.
In order for this to actually generate a OperationCanceledException, you need to call cts.Token.ThrowIfCancellationRequested inside _decoratedPolicy.ExecuteAsync.
For example:
// Assuming this is _decoratedPolicy.ExecuteAsync
public async Task ExecuteAsync(Action action, CancellationToken token)
{
// This is what creates and throws the OperationCanceledException
token.ThrowIfCancellationRequested();
// Simulate some work
await Task.Delay(20);
}
Edit:
In order to actually cancel the token, you need to monitor it at all points where work is executed and execution may timeout. If you cannot make that guarantee, then defer to #SriramSakthivel answer where the actual Task is being discarded, rather than being actually canceled.
You are calling Assert.Throws(Action action) and your anonymous async method is casted to async void. The method will be called asynchronously with Fire&Forget semantics without throwing exception.
However the process would likely crash shortly after due to the uncatched exception in an async void method.
You should call ExecuteAsync synchronously:
[Test]
public void Policy_TimeoutExpires_DoStuff_TaskShouldNotContinue()
{
var cts = new CancellationTokenSource();
var fakeService = new Mock<IFakeService>();
IExecutionPolicy policy = new TimeoutPolicy(new ExecutionTimeout(20), new DefaultExecutionPolicy());
Assert.Throws<AggregateException>(() => policy.ExecuteAsync(() => DoStuff(3000, fakeService.Object), cts.Token).Wait());
fakeService.Verify(f=>f.DoStuff(),Times.Never);
}
or use an async test method:
[Test]
public async Task Policy_TimeoutExpires_DoStuff_TaskShouldNotContinue()
{
var cts = new CancellationTokenSource();
var fakeService = new Mock<IFakeService>();
IExecutionPolicy policy = new TimeoutPolicy(new ExecutionTimeout(20), new DefaultExecutionPolicy());
try
{
await policy.ExecuteAsync(() => DoStuff(3000, fakeService.Object), cts.Token);
Assert.Fail("Method did not timeout.");
}
catch (TimeoutException)
{ }
fakeService.Verify(f=>f.DoStuff(),Times.Never);
}
I've decided to answer my own question here, as while each of the listed answers solved something that I needed to do, they did not identify the root cause of this issue. Many, many thanks to: Scott Chamberlain, Yuval Itzchakov,Sriram Sakthivel, Jeff Cyr. All advice gratefully received.
Root cause/Solution:
await Task.Factory.StartNew(action.Invoke, token);
which you see above in my "decorated policy" returns a Task and await waits only for the outer task. Replacing it with
await Task.Run(async () => await action.Invoke());
gets the correct result.
My code was suffering from a combination of Gotcha #4 and Gotcha #5 from an excellent article on C# async gotchas
The entire article (as well as answers posted to this question) has really improved my overall understanding.