Висячие ожидания и возможные утечки памяти в асинхронном программировании

Поток, содержащий await в .NET 4.5 и Async CTP 4.0, может зависнуть по разным причинам, например. так как удаленный клиент не ответил. Конечно, WaitForAny, когда мы ждем еще и какую-то задачу по таймауту, — очевидное решение для восстановления высокоуровневого потока. Тем не менее, это не решает всех возможных проблем.

У меня есть следующие вопросы:

  1. Что происходит с контекстом await, который никогда не возвращается? Я понимаю, что это создаст утечку памяти. Я прав?

  2. Как я могу проверить в отладчике или с помощью соответствующего API, сколько висящих "ожидающих" существует в приложении?

  3. Можно ли перечислить их глобально?

  4. Если 3. верно, можно ли принудительно отменить задачи для этих *await* (т.е. очистить)?

Примечание. В вопросе 4 я не спрашиваю об элементах отмены, которые будут использоваться во время явного создания задачи. Я имею в виду случай, когда задача создавалась косвенно:

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

Мотивация этого вопроса:

  1. Попытка избежать утечек памяти
  2. Попытка усложнить код слишком большим количеством случаев, связанных с токенами отмены.
  3. Во многих ситуациях тайм-аут не известен заранее для каждой Задачи низкого уровня, но поток высокого уровня может использовать просто подход восстановления: «Мы застряли? Ничего, просто очистите и начнем сначала».

person Yuri S.    schedule 18.10.2012    source источник


Ответы (2)


1 Что происходит с контекстом await, который никогда не возвращается?

Я считаю, что это вызовет утечку памяти (если вы await выполняете операцию ввода-вывода). Лучше всегда заполнять Tasks (и это означает, что ваши методы async всегда возвращаются рано или поздно).

Обновление из комментария svick: есть ситуации, когда это не вызовет утечку памяти.

2 Как я могу проверить в отладчике или с помощью соответствующего API, сколько висящих «ожидающих» существует в приложении?

Я не уверен, есть ли простой способ сделать это. Я считаю, что должно быть возможно написать подключаемый модуль отладчика, который использует SoS для поиска существующих объектов кучи, соответствующих шаблону асинхронных конечных автоматов, сгенерированных компилятором.

Но это много работы при малой пользе.

3 Можно ли перечислить их глобально?

Не с обычными API.

Если 3 правильно, можно ли принудительно отменить задачи для этих ожиданий (т.е. очистить)?

Даже если бы вы могли перечислить их во время выполнения (например, через API профилирования), вы не можете «принудительно» отменить задачу. Отмена является совместной.


Правильный способ справиться с этим — стандартная отмена. В документе асинхронного шаблона на основе задач указаны рекомендации по отменяемым async методы.

На самом низком уровне: многие async API в BCL принимают необязательный CancellationToken.

На среднем уровне: обычно метод async принимает необязательный CancellationToken и просто передает его другим методам async.

На самом высоком уровне: легко создать CancellationToken, который сработает после заданное время.

person Stephen Cleary    schedule 18.10.2012
comment
Дополнение к упомянутой вами статье о Task, которые никогда не заканчиваются есть пример, когда это не вызовет утечку памяти, и я думаю, что это обычный случай. - person svick; 19.10.2012
comment
Оператор упомянул удаленный клиент, поэтому я решил, что в этом случае ввод-вывод будет рутировать продолжения. Тем не менее, я обновлю свой ответ, чтобы он был более полным. - person Stephen Cleary; 20.10.2012
comment
Стивен, спасибо. Хотя это немного разочаровывающий ответ. Наиболее близкий ответ на то, что я искал, можно найти в этой статье Джона Скита: по крайней мере, для целей отладки это решение msmvps.com/blogs/jon_skeet/archive/2010/11/04/ (с некоторыми модификации, основанные на подходе атрибута Caller, чтобы избежать ненужных изменений кода, позволяет легко отлаживать время жизни задач, по крайней мере, в выбранных модулях). - person Yuri S.; 04.06.2013
comment
@YuriS мертвая ссылка. Новое: codeblog.jonskeet .uk/2010/11/03/ - person jnm2; 25.03.2016

На вопросы 2 и 3 у меня нет реального ответа.

  1. Я сомневаюсь в этом, пока ресурсы, используемые в задаче, правильно распределяются, когда CLR удаляет задачу при завершении приложения.

Иметь задачи, которые никогда не возвращаются, вряд ли когда-либо хорошо. Чтобы избежать этого и ответить на пункт 4: задачи можно отменить.

Вам нужно создать маркер отмены, который вы передаете задаче. Задача самостоятельно следит за статусом этого маркера отмены и выдает исключение при отмене. (Несколько задач можно отменить одновременно, используя один и тот же токен.)

Эта статья в MSDN показывает, как это сделать.

person lboshuizen    schedule 18.10.2012