Dangling await and possible memory leaks in async programming
Asked Answered
I

2

12

The flow containing await in .NET 4.5 and Async CTP 4.0 can be stuck due to various reasons, e.g. since the remote client has not responded. Of course, WaitForAny, when we wait also for some timeout task is an obvious solution for recovery of the high-level flow. Still, this does not solve all possible problems.

I have the following questions:

  1. What happens to the context of await which does not ever return? I understand that this will create memory leak. Am I right?

  2. How can I check either in debugger or using the respective API how many dangling "awaiter"s exist in the application?

  3. Is it possible to enumerate them globally?

  4. If 3. is correct, is it possible to force cancellation the tasks for these *await*s (i.e. to clean up)?

Note: In question 4 I don't ask about cancellation items to be used during explicit task creation. I mean the case when the task was created indirectly:

async Task<bool> SomeTask()
{
   await Something();
   ...
   return true;
}

Motivation for this question:

  1. Trying to avoid memory leaks
  2. Trying to complication of the code with too many cases involving cancellation tokens
  3. In many situations the timeout is not known in advance for each low-level Task, but the high-level flow can use just recovery approach: "We are stuck? Never mind, just clean up and let's start over".
Inmesh answered 18/10, 2012 at 11:17 Comment(0)
F
10

1 What happens to the context of await which does not ever return?

I believe it will cause a memory leak (if you're awaiting an I/O operation). It's best to always complete your Tasks (and this means always having your async methods return sooner or later).

Update from svick's comment: There are situations where this would not cause a memory leak.

2 How can I check either in debugger or using the respective API how many dangling "awaiter"s exist in the application?

I'm not sure if there's an easy way to do this. I believe it should be possible to write a debugger plugin that uses SoS to find existing heap objects that match the pattern of the asynchronous state machines generated by the compiler.

But that's a lot of work for little benefit.

3 Is it possible to enumerate them globally?

Not with the normal APIs.

If 3 is correct, is it possible to force cancellation the tasks for these awaits (i.e. to clean up)?

Even if you could enumerate them at runtime (e.g., via the profiling API), you can't "force" cancellation onto a task. Cancellation is cooperative.


The correct way to deal with this is with standard cancellation. The Task-based Asynchronous Pattern document specifies guidelines for cancelable async methods.

At the lowest level: Many async APIs in the BCL take an optional CancellationToken.

At a middle level: It's common to have an async method take an optional CancellationToken and just pass it on to other async methods.

At the highest level: It's easy to create a CancellationToken that will fire after a given time.

Feigin answered 18/10, 2012 at 13:55 Comment(4)
A followup to the article you mentioned about Tasks that never finish has an example where this won't cause a memory leak and I think that's the usual case.Legge
The op mentioned a "remote client", so I figured the I/O would root the continuations in this case. That said, I'll update my answer to be more complete.Feigin
Stephen, thanks. It is a bit disappointing answer though. The most close answer to what I was looking for can be found in this article by John Skeet: at least for debugging purposes this solution msmvps.com/blogs/jon_skeet/archive/2010/11/04/… (with certain modifications, based on "Caller attribute" approach in order to avoid unnecessary code changes allows to easily debug the tasks lifetime at least in selected modules).Inmesh
@YuriS dead link. New: codeblog.jonskeet.uk/2010/11/03/…Mayberry
A
2

About questions 2 and 3 I have no real answer.

  1. I doubt it as long as the resources used in the task are properly disposed when the CLR disposes the task when the application terminates.

Having tasks around that never return is hardly ever a good thing. To avoid this and to answer point 4: tasks can be cancelled.

You need to create a cancellationtoken that you pass to the task. The task is responsible on his own to watch for the status of that cancellation token and throw an exception when cancelled. (Multiple tasks can be cancelled at once using the same token too.)

This article on MSDN shows you to do so.

Ametropia answered 18/10, 2012 at 12:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.