Why is my async/await with CancellationTokenSource leaking memory? - c#

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.

Related

C# async method called concurrently should wait for the first operation to complete

I have a method that looks like
public async Task<OpenResult> openAsync()
I want to do something like if there is a current call to openAsync in the process of getting executed, I would like to place any calls to OpenAsync be added to a queue.
When the first call completes, I want to complete all the ones in the queue with the result of the first call.
What’s the way to achieve this in C#
Usually, this kind of detail is left to the caller, i.e. by making the caller await appropriately and only call methods when they should call methods. However, if you must do this, one simple way is via a semaphore; consider:
class HazProtected
{
private readonly SemaphoreSlim _lock = new SemaphoreSlim(1, 1);
public async Task<OpenResult> OpenAsync(CancellationToken cancellationToken = default)
{
await _lock.WaitAsync(cancellationToken);
try
{
return await DoOpenAsync(cancellationToken);
}
finally
{
_lock.Release();
}
}
private async Task<OpenResult> DoOpenAsync(CancellationToken cancellationToken)
{
// ... your real code here
}
}
The code in OpenAsync ensures that only one concurrent async caller can be attempting to open it at a time. When there is a conflict, callers are held asynchronously until the semaphore can be acquired. There is a complication, though; SempahoreSlim has some known problems on .NET Framework (resolved in .NET Core) when there are both asynchronous and synchronous semaphore acquisitions at the same time - which can lead to a spiral of death.
In more complex scenarios, it is possible to write your own queue of pending callers; this is a very very exotic scenario and should usually not be attempted unless you understand exactly why you're doing it!

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
}

Asynchronous webserver and garbage collection

After reading Stephen Cleary's article on async and asp.net, it is quite clear that using Async in web application is a big win on scalability (primarily due to non-blocking threads that are free to serve up more requests).
Although I am a little confused as to how does the Task object returned by the async operation remain in scope in an Aysnc WebServer implementation (lets say iis or self-hosted WebApi)
and not get collected.
for example,
If we have the following method in a low level Webserver implementation,
// some method that handles HttpListeners BeginContextAsync callback,
public void SomeHttpListenerCallback(HttpListenerContext context)
{
// Immediately set up the next begin context
// then handle the request
// forgive me if this is incorrect but this is my understanding of how an async request would be handled
// please feel free to point out the flaws
var task = messageHanlder1.SendAsync(context.Request, null);
task.ContinueWith(t => SendResponse(t));
}
public class MessageHandler1 : DelegatingHandler
{
protected async override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
Debug.WriteLine("Process request");
// Call the inner handler.
var response = await base.SendAsync(request, cancellationToken);
Debug.WriteLine("Process response");
return response;
}
}
SendResponse being some method that sends the response to clients socket.
As you might have noticed, task object drops out of scope in SomeHttpListenerCallback, since no other thread (theoretically) has any reference to the task object, wouldnt it be marked for collection in next GC cycle?
I have read Jeffrey Ritcher's explanation (Clr via C#) of how compiler converts an aysnc method into a method with a state machine, however I fail to understand what happens to the task object if no thread waits on it.
And if a thread waits on it, wouldnt we have 1 blocked thread per request?
Any help to point me in the right direction would be greatly appreciated.
First of all, if you have a Task and nothing references it then it will be collected by the GC.
However, that's almost never the case. There are 2 types of tasks, Promise tasks and Delegate tasks.
Promise tasks (asynchronous) are mostly created by TaskCompletionSource or the compiler for an async method. In both cases someone holds a reference to the task so it will be able to complete it.
Delegate tasks (synchronous) however are referenced by the thread executing the delegate in them.
In your case task is referenced by the state machine behind SendAsync until it completes while task is referencing the continuation Task. When it completes the continuation is scheduled and so is referenced by the TaskScheduler and the thread executing SendResponse. When the operation completes the task will no longer be referenced and could be eventually GCed.
You can see an example in the implementation of Task.Delay where the Task (actually DelayPromise which inherits from Task) is being referenced by the System.Threading.Timer used to complete the Task.
Because some arguments don't actually need a timer you can see a distinction in memory usage between this:
static void Main()
{
while (true)
{
Task.Delay(int.MaxValue);
}
}
And this:
static void Main()
{
while (true)
{
Task.Delay(-1); // No Timer as the delay is infinite.
}
}
For every async operation that you do there is some "native" operation behind it - a socket read or a file IO and such. These are native CLR operations or PInvoke calls. When starting such an operation the framework registers a callback to be called on completion. This callback is being used to complete tasks. That callback is being kept alive by "the system". For example when you say myFileStream.BeginRead(myCallback) then the callback will be called even if it is otherwise unreferenced.
Speaking in a simplified way the native operation itself keeps everything alive that must be worked on when completion happens.
This is not a hard rule but an IO framework would be pretty useless if it called your callback only sometimes.
There is nothing that prevents any Task from being collected. For example var dummy = new TaskCompletionSource<object>().Task; is eligible for collection immediately.

Task.Factory.FromAsync with CancellationTokenSource

I have the following line of code used to read asynchronously from a NetworkStream:
int bytesRead = await Task<int>.Factory.FromAsync(this.stream.BeginRead, this.stream.EndRead, buffer, 0, buffer.Length, null);
I'd like to make it support cancellation. I see that I can cancel tasks using a CancellationTokenSource, however I don't see any way I can pass it to TaskFactory.FromAsync().
Is it possible to make a FromAsync()-constructed task support cancellation?
Edit: I want to cancel a task that is already running.
Gigi, unfortunately the semantic nature of FromAsync indicates that you are only adapting an asynchronous process to TPL's API (TPL = Microsoft's Task Parallel Library)
In essence, TPL's ReadAsync controls the async behaviour itself, whilst FromAsync only wraps the behaviour (but doesn't control it).
Now since Cancellation is a TPL specific construct, and since FromAsync has no control on the inner workings of the async method being called, then there is no guaranteed way to cleanly cancel the task and ensure that all resources are closed correctly (which is why it was omitted. If you're curious, just decompile the method ;))
In these situations, it makes more sense to wrap the actual async call yourself in a normal task and detect the OperationCancelled exception, which will give you the opportunity to close your stream by making the appropriate calls.
In short, the answer is no, but there is nothing stopping you from creating a generic overloaded method that will pick the correct strategy to cleanly close a stream depending on its type.
As others have already mentioned, there is no clean way of achieving what you're asking for. The notion of cancellation was absent from the Asynchronous Programming Model; thus, it couldn't be retrofitted through the FromAsync converters.
However, you can introduce cancellation for the Task that wraps the asynchronous operation. This will not cancel the underlying operation itself – your NetworkStream would still continue reading all the requested bytes from the socket – but it will permit your application to react as if the operation was cancelled, immediately throwing an OperationCanceledException from your await (and executing any registered task continuations). The result of the underlying operation, once completed, will be ignored.
This is a helper extension method:
public static class TaskExtensions
{
public async static Task<TResult> HandleCancellation<TResult>(
this Task<TResult> asyncTask,
CancellationToken cancellationToken)
{
// Create another task that completes as soon as cancellation is requested.
// http://stackoverflow.com/a/18672893/1149773
var tcs = new TaskCompletionSource<TResult>();
cancellationToken.Register(() =>
tcs.TrySetCanceled(), useSynchronizationContext: false);
var cancellationTask = tcs.Task;
// Create a task that completes when either the async operation completes,
// or cancellation is requested.
var readyTask = await Task.WhenAny(asyncTask, cancellationTask);
// In case of cancellation, register a continuation to observe any unhandled
// exceptions from the asynchronous operation (once it completes).
// In .NET 4.0, unobserved task exceptions would terminate the process.
if (readyTask == cancellationTask)
asyncTask.ContinueWith(_ => asyncTask.Exception,
TaskContinuationOptions.OnlyOnFaulted |
TaskContinuationOptions.ExecuteSynchronously);
return await readyTask;
}
}
And this is an example that uses the extension method to treat an operation as cancelled after 300ms:
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromMilliseconds(300));
try
{
int bytesRead =
await Task<int>.Factory.FromAsync(this.stream.BeginRead, this.stream.EndRead, buffer, 0, buffer.Length, null)
.HandleCancellation(cts.Token);
}
catch (OperationCanceledException)
{
// Operation took longer than 300ms, and was treated as cancelled.
}
No, there is no generic way to cancel such a task. Cancellation is API specific.
For example, WebClient has a Cancel method.
A Socket or a FileStream needs to be Close'd to cancel an outstanding call.
Web-service clients have even different ways of aborting calls.
...
This is because the implementer of the IO operation must support cancellation.
It might seem tempting to use NetworkStream.ReadAsync and pass a cancellation token but is Stream.ReadAsync. An the latter just throws away the token. Basically not supported.
Stream.ReadAsync is just the base class method. It does not do anything by itself. Concrete IO operations are issued only by derived classed. Those must support cancellation natively. Stream can't do anything to force them. It happens that NetworkStream doesn't support cancellation.
I understand that you want to cancel the operation and leave the socket open. But it is not possible. (Subjective note: This is really a sad state of affairs. Especially considering that Windows supports cancellable IO at the Win32 level.)
If you still want your app to quickly continue, although the IO operation is not cancellable, just ignore the result of that task and continue. Be aware that eventually the IO might complete and for example drain data from the socket buffers or cause other side-effects.
"Cancelling by ignoring" effectively make the stream position undefined. The stream becomes unusable. This doesn't really avoid the need to open a new stream. You still have to get rid of the old stream (in most cases) and reopen. Also, you are introducing concurrency.

Stop and resume async calls

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.

Categories

Resources