Stop and resume async calls - c#

When navigating to page, i'm calling in the viewmodel
public void OnNavigatedTo()
{
ThreadPool.QueueUserWorkItem(async o =>
{
collectionsAnswer = await productCategoriesDataService.RequestServerAsync();
***
if (collectionsAnswer.status == Ok)
{
var parsedList = await productCategoriesDataService.Parse(collectionsAnswer.json);
_itemsList = new List<ProductItem>(parsedList);
DispatcherHelper.CheckBeginInvokeOnUI(() =>
RaisePropertyChanged("ItemsList", _itemsList, _itemsList, true));
}
}
How to stop/resume it properly? I tried to Abort current HttpWebResponse (which is inside of RequestServerAsync() ) from OnNavigatedFrom(), but it crashed, when i'm returning to the same page again.
So, in short, the problem is:
Navigating to page starts await commands
Leaving the page (by pressing Back) should cancel current request
Visiting page should create new request, but awaits are still waiting (if to return fast enough)
Are there better ways to solve this problem? Maybe i should create new instances of viewmodels every time?

Cancellations in the TPL and in async-await code are performed through the use of CancellationTokenSource and CancellationToken. Many asynchronous methods have an overload which accepts a CancellationToken as a parameter, this is then used by the method to observe for cancellation.
Here is a post on the MSDN about how to initiate and handle the cancellation of async tasks.
When using cancellation tokens, I would advise the use of this overload of Task.Run which takes a CancellationToken parameter instead of ThreadPool.QueueUserWorkItem. The token parameter is used internally by the Task to return a cancelled task upon cancellation, failing to use this overload could result in an OperationCanceledException being thrown from Task.Run.

Related

Execution of ContinueWith when the antecedent Task is in canceled state and usage of async delegate inside of ContinueWith

The code discussed here is written in C# and executed with .netcore 3.1
I have the following piece of code, which starts a workload in the background without awaiting for it to complete (fire and forget):
public void StartBackgroundWork(IAsyncDisposable resource, CancellationToken token)
{
// some background work is started in a fire and forget manner
_ = Task.Run(async () =>
{
try
{
// here I perform my background work. Regardless of the outcome resource must be released as soon as possible
// I want that cancellation requests coming from the provided cancellation token are correctly listened by this code
// So, I pass the cancellation token everywhere
await Task.Delay(1500, token);
}
finally
{
// here I need to release the resource. Releasing this resource is important and must be done as soon as possible
await resource.DisposeAsync();
}
}, token);
}
There are three important points:
the background work is started in a fire and forget manner. I'm not interested in awaiting its completion
the provided cancellation token is important and the background work must listed to incoming cancellation requests
the provided resource (IAsyncDisposable) must be released as soon as possible, regardless of the outcome of the background work. In order to release the resource a call to DisposeAsync is required.
The problem with this code is that the cancellation token is passed to Task.Run invokation. If the token is canceled before the execution of the async delegate starts, the async delegate is never executed and so the finally block is never executed. By doing so the requirement of releasing the IAsyncDisposable resource is not met (basically, DisposeAsync is never called).
The simplest way to solve this issue is not providing the cancellation token when Task.Run is invoked. That way, the async delegate is always executed and so the finally block is executed too. The code inside the async delegate listens to cancellation requests, so the requirement of cancel the execution is met too:
public void StartBackgroundWork(IAsyncDisposable resource, CancellationToken token)
{
// some background work is started in a fire and forget manner
_ = Task.Run(async () =>
{
try
{
// here I perform my background work. Regardless of the outcome resource must be released as soon as possible
// I want that cancellation requests coming from the provided cancellation token are correctly listened by this code
// So, I pass the cancellation token everywhere
await Task.Delay(1500, token);
}
finally
{
// here I need to release the resource. Releasing this resource is important and must be done as soon as possible
await resource.DisposeAsync();
}
}, CancellationToken.None);
}
I'm asking myself whether the release of the IAsyncDisposable resource should, instead, be delegated to a continuation task. The code refactored by using this approach is the following:
public void StartBackgroundWork(IAsyncDisposable resource, CancellationToken token)
{
// some background work is started in a fire and forget manner
_ = Task.Run(async () =>
{
// here I perform my background work. Regardless of the outcome resource must be released as soon as possible
// I want that cancellation requests coming from the provided cancellation token are correctly listened by this code
// So, I pass the cancellation token everywhere
await Task.Delay(1500, token);
},
token).ContinueWith(async _ =>
{
// release the IAsyncDisposable resource here, afte the completion of the antecedent task and regardless
// of the antecedent task actual state
await resource.DisposeAsync();
});
}
I'm not really familiar with ContinueWith gotchas, so my questions are the following:
do I have the guarantee that the continuation is always executed, even if the cancellation token is canceled before the execution of the antecedent task starts ?
is there any issue in providing an async delegate to the invokation of ContinueWith ? Is the execution of the async delegate fully completed as expected ?
What is the best approach ? Passing CancellationToken.None to the invokation of Task.Run, or relying on the continuation by using ContinueWith ?
IMPORTANT NOTE: I know that using Task.Run is not the best approach in a server application (more on that can be found here), so there are probably much better ways of designing my overall architecture. I posted this question to better understanding the actual behavior of ContinueWith, because I'm not really familiar with its usage (in modern .NET code it is largely replaced by the usage of async await).
You could consider using the await using statement, that handles the asynchronous disposal of the resource automatically:
public async void StartBackgroundWork(IAsyncDisposable resource, CancellationToken token)
{
await using var _ = resource;
try
{
await Task.Run(async () =>
{
await Task.Delay(1500, token);
}, token);
} catch (OperationCanceledException) { }
}
I also converted your fire-and-forget task to an async void (aka fire-and-crash) method. In case the unthinkable happens and your code has a bug, instead of the app continue running with an unobserved exception having occurred, resulting possibly to corrupted application state, the whole app will crash, forcing you to fix the bug ASAP.
But honestly creating a disposable resource in one method and disposing it in another is a smelly design. Ideally the method that created the resource should be responsible for disposing it finally.
I think Theodor has a great answer; I'm just going to answer some of your other questions:
do I have the guarantee that the continuation is always executed, even if the cancellation token is canceled before the execution of the antecedent task starts ?
ContinueWith will execute its delegate even of the antecedent task is already completed. In this specific case, there is no "guarantee" simply because of the nature of fire-and-forget.
is there any issue in providing an async delegate to the invokation of ContinueWith ?
ContinueWith is not async-aware, so the return type of ContinueWith is surprising for most developers. Since your code discards the return type, that's not a concern here.
Is the execution of the async delegate fully completed as expected ?
In this case, most likely, but it really depends on what "expected" means. Like all other fire-and-forget code, you can't guarantee completion. ContinueWith has an additional wrinkle: it executes its delegate using a TaskScheduler, and the default TaskScheduler is not TaskScheduler.Default but is actually TaskScheduler.Current. So I always recommend passing an explicit TaskScheduler for clarity if you really need to use ContinueWith.
What is the best approach ? Passing CancellationToken.None to the invokation of Task.Run, or relying on the continuation by using ContinueWith ?
Just drop the second argument to Task.Run.
I'll go further than that: Task.Run probably shouldn't even take a CancellationToken. I have yet to see a scenario where it's useful. I suspect the CancellationToken part of the API was copied from TaskFactory.StartNew (where it is rarely useful), but since Task.Run always uses TaskScheduler.Default, providing a CancellationToken is not useful in practice.
P.S. I recently wrote a short series on the proper solution for fire-and-forget on ASP.NET.

Cancel Async operation

private async void TriggerWeekChanged(Week currentWeek)
{
await LoadDataForSelectedWeek(currentWeek); //Split into multiple methods
}
In case a user hammers on the Change_Week Button how can I cancel the current Task, and start a new one with the new paramerters ?
I tried like this:
private async Task Refresh(Week selectedWeek, CancellationToken token)
{
Collection.Clear();
await LoadDataFromDatabase();
token.ThrowIfCancellationRequested();
await ApplyDataToBindings();
token.ThrowIfCancellationRequested();
//Do some other stuff
}
Problem is:
In my Collection I got data from multiple weeks when I hit the button to fast in a row.
Everything that is awaited will need to have visibility on that CancellationToken. If it is a custom Task that you wrote, it should accept it as an argument, and periodically within the function, check to see if it has been cancelled. If so, it should take any actions needed (if any) to stop or rollback the operation in progress, then call ThrowIfCancellationRequested().
In your example code, you propbably want to pass token in to LoadDataFromDatabase and ApplyDataToBindings, as well as any children of those tasks.
There may be some more advanced situations where you don't want to pass the same CancellationToken in to child tasks, but you still want them to be cancellable. In those cases, you should create a new, internal to the Task, Cancellationtoken that you use for child tasks.
An important thing to remember is that ThrowIfCancellationRequested marks safe places within the Task that the Task can be stopped. There is no guaranteed safe way for the runtime to automatically detect safe places. If the Task were to automatically cancel its self as soon as cancellation was requested, it could potentially be left in an unknown state, so it is up to developers to mark those safe locations. It isn't uncommon to have several calls to check cancellation scattered throughout your Task.
I've just notice that your TriggerWeekChanged function is async void. This is usually considered an anti-pattern when it is used on something that is not an event handler. It can cause a lot of problems with tracking the completed status of the async operations within the method, and handling any exceptions that might be thrown from within it. You should be very weary of anything that is marked as async void that isn't an event handler, as it is the wrong thing to do 99%, or more, of the time. I would strongly recommend changing that to async Task, and consider passing in the CancellationToken from your other code.
You did not mention about how LoadDataForSelectedWeek and Refresh interact.
For short you need to create one CancellationTokenSource instance that handle every click. Then pass it to that method and do Cancel method every time new one appear.
private async void TriggerWeekChanged(Week currentWeek, CancellationTokenSource tokenSource)
{
tokenSource.Cancel();
try
{
var loadDataTask = Task.Run(() => LoadDataForSelectedWeek(currentWeek, tokenSource.Token), tokenSource.Token); //Split into multiple methods
}
catch(OperationCanceledException ex)
{
//Cancelled
}
}
LoadDataForSelectedWeek -> Refresh (?)
private async Task Refresh(Week selectedWeek, CancellationToken token)
{
Collection.Clear();
await LoadDataFromDatabase();
token.ThrowIfCancellationRequested();
await ApplyDataToBindings();
token.ThrowIfCancellationRequested();
//Do some other stuff
}

C# Task.Factory.CancellationToken

My question is about task cancellation. I have to poll the token.IsCancellationRequested to detect a cancellation. I call cts.Cancel(); in a WindowsForm Buttonmethod.
Questions:
If I hit the Button is the CancelRequest stored? Or do I have to be lucky, that to same time when I press my Button the code is at the position if (token.IsCancellationRequested)?
Is it possible to cancel my Task with for-loop by event?
Code Example:
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
Task t1 = Task.Factory.StartNew(() =>
{
// Do syncronius work
for(int i=0; i<1000;++i)
{
DoSyncWork(i);
if (token.IsCancellationRequested)
{
Console.WriteLine("Cancelled");
break;
}
Thread.Sleep(1000);
}
});
The cancellation request is a one time thing. Once a token source is canceled it can never be un-canceled so all derived tokens will have IsCancellationRequested always return true.
Yes it is possible, but for a for loop I don't think it is a better way. The way you use a event is you pass the callback to the CancellationToken.Register method and the callback is your event. I leave it to you how you would make a Action delegate cancel the for loop.
A few things with your code that you did not bring up:
You should never call Task.Factory.StartNew without passing in TaskScheduler, if you don't you could cause your code in the StartNew to run on the UI thread when you expect it to be on a background thread. Either use Task.Run or make sure you pass in a scheduler (TaskScheduler.Default is the one Task.Run( uses to always run on the background thread, TaskScheduler.Current is the one that is used when you don't pass anything in and is the one that can cause stuff to run on the UI thread).
If you pass the token in to the factory (or to Task.Run() then use token.ThrowIfCancellationRequested() this will cause the task to enter the Canceled state instead of the Completed state (if you forget to pass it to the factory it will enter the Faulted state), this can be useful for when you need to know when the task finished or not when you are awaiting.
The cancellation is "stored". If you call Cancel() on your CancellationTokenSource instance, the IsCancellationRequested property of the CancellationToken will be true for the rest of its existence.
As I understand it, you want to break your for loop by an event? I don't know how this should look like. The control flow of a for loop is straight forward, no event could break that. But you can use the token in the for loop's header:
for(int i=0; i<1000 && !token.IsCancellationRequested; ++i)
{
...
}
// output log if cancelled
if (token.IsCancellationRequested) Console.WriteLine(...);
if it's that what you want.
The usual implementation for cancelling out with a cancellation token is to throw the OperationCanceledException, by using the tokens .ThrowIfCancellationRequested. This allows you to catch a cancelled operation and get out of operating for however deep you are in the stack and know that it was cancelled.
For your first question, as soon as the token has been cancelled, cancellation will be requested and when you come back around in the loop the if block would be true that you have. Instead of that though I would just use token.ThrowIfCancellationRequested, and catch the specific OperationCanceledException and do any logging you want.
Second question, you can register a cancellation from anything that can access to your cancellationtokensource by calling cancel. So any event that is able to access the cancellationtokensource you could call it's cancellation event. I will often put a tokensource as an instance variable on a form that should support cancellation so that a "cancel" button or some other event that causes cancellation can call on the cancel method for the cts.
Example of one way I'll set up a form with a token:
public class MyForm
{
private CancellationTokenSource _cts;
private void Cancel()
{
if (_cts != null) {
_cts.Cancel();
}
}
}

Cannot cancel a task via CancellationTokenSource.Cancel()

I am having trouble stopping a Task. Firstly, I just started using Tasks. Previously, I had been using delegate.BeginInvoke() to run things in background, but this time I need to stop background execution, if required. So I switched to Tasks. This is my code:
CancellationTokenSource token = new CancellationTokenSource();
Task posting = Task.Factory.StartNew(() => DoPosting(docs, session), token.Token);
HttpRuntime.Cache[session.ToString() + "_token"] = token;
HttpRuntime.Cache[session.ToString() + "_task"] = posting;
This is ASP.NET MVC, so I store long-lasting things in HttpRuntime.Cache. User can cancel operation via this action:
public JsonResult StopPosting(string session)
{
CancellationTokenSource token = (CancellationTokenSource)HttpRuntime.Cache.Get(session.ToString() + "_token");
Task posting = (Task)HttpRuntime.Cache[session.ToString() + "_task"];
token.Cancel();
return Json(new { Message = "Stopped!" });
}
Now, when this action gets hit for the first time, nothing happens. Then i request cancellation for the second time. Now, token.IsCancellationRequested says "true", so token.Cancel() must have done something. But posting.Status is still "Running". It will stay that way until it completes and then it is "RunToCompletion".
So, I requested cancellation of a Task, but it didt got cancelled.
Maybe I am doing something wrong or I am missing something obvious, but I just cant see/understand why it wont cancel.
Maybe somebody can shed some light?
Regards.
Cancelling the token won't immediately result in the task's delegate stopping execution right where it is. Supporting that (as is done via Abort through threads) can cause all sorts of badness, ranging from objects not being cleaned up properly, invariants being violated due to execution stopping part way through an operation that ought to be logically observed to be atomic, etc.
What you need to do is have the actual function being executed look at the CancellationToken and periodically verify that it hasn't been cancelled. In addition to passing the token to StartNew you need to pass it to the DoPosting method. That method then needs to periodically call token.ThrowIfCancellationRequested(); at places in the method where it would be appropriate to stop if cancellation was indeed requested.
See How do I cancel non-cancelable async operations? for further reading.
I think you're probably expecting too much of CancellationToken. As regards Tasks, I think the CancellationToken is only used when determining whether to start the task when a slot is available for the task. Once the task is started, the task framework cannot simply abort the delegate call when the CancellationToken is cancelled. In order to fully cancel something like this, the code that is called by the Task framework (DoPosting) must be written to be aware of the CancellationToken and must check the state of that token directly at appropriate points in the code.

Why is my async/await with CancellationTokenSource leaking memory?

I have a .NET (C#) application that makes extensive use of async/await. I feel like I've got my head around async/await, but I'm trying to use a library (RestSharp) that has an older (or perhaps I should just say different) programming model that uses callbacks for asynchronous operations.
RestSharp's RestClient class has an ExecuteAsync method that takes a callback parameter, and I wanted to be able to put a wrapper around that which would allow me to await the whole operation. The ExecuteAsync method looks something like this:
public RestRequestAsyncHandle ExecuteAsync(IRestRequest request, Action<IRestResponse> callback);
I thought I had it all working nicely. I used TaskCompletionSource to wrap the ExecuteAsync call in something that I could await, as follows:
public static async Task<T> ExecuteRequestAsync<T>(RestRequest request, CancellationToken cancellationToken) where T : new()
{
var response = await ExecuteTaskAsync(request, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(response.Content);
}
private static async Task<IRestResponse> ExecuteTaskAsync(RestRequest request, CancellationToken cancellationToken)
{
var taskCompletionSource = new TaskCompletionSource<IRestResponse>();
var asyncHandle = _restClient.ExecuteAsync(request, r =>
{
taskCompletionSource.SetResult(r);
});
cancellationToken.Register(() => asyncHandle.Abort());
return await taskCompletionSource.Task;
}
This has been working fine for most of my application.
However, I have one part of the application that does hundreds of calls to my ExecuteRequestAsync as part of a single operation, and that operation shows a progress dialog with a cancel button. You'll see that in the code above that I'm passing a CancellationToken to ExecuteRequestAsync; for this long-running operation, the token is associated with a CancellationTokenSource "belonging" to the dialog, whose Cancel method is called if the use clicks the cancel button. So far so good (the cancel button does work).
My problem is that my application's memory usage shoots up during the long-running application, to the extent that it runs out of memory before the operation completes.
I've run a memory profiler on it, and discovered that I have lots of RestResponse objects still in memory, even after garbage collection. (They in turn have huge amounts of data, because I'm sending multi-megabyte files across the wire).
According to the profiler, those RestResponse objects are being kept alive because they're referred to by the TaskCompletionSource (via the Task), which in turn is being kept alive because it's referenced from the CancellationTokenSource, via its list of registered callbacks.
From all this, I gather that registering the cancellation callback for each request means that the whole graph of objects that is associated with all those requests will live on until the entire operation is completed. No wonder it runs out of memory :-)
So I guess my question is not so much "why does it leak", but "how do I stop it". I can't un-register the callback, so what can I do?
I can't un-register the callback
Actually, you can. The return value of Register() is:
The CancellationTokenRegistration instance that can be used to deregister the callback.
To actually deregister the callback, call Dispose() on the returned value.
In your case, you could do it like this:
private static async Task<IRestResponse> ExecuteTaskAsync(
RestRequest request, CancellationToken cancellationToken)
{
var taskCompletionSource = new TaskCompletionSource<IRestResponse>();
var asyncHandle = _restClient.ExecuteAsync(
request, r => taskCompletionSource.SetResult(r));
using (cancellationToken.Register(() => asyncHandle.Abort()))
{
return await taskCompletionSource.Task;
}
}
You need to keep the CancellationTokenRegistration returned by the Register() call. Disposing that CancellationTokenRegistration un-registers the callback.

Categories

Resources