Нужно ли закреплять анонимного делегата?

Я вызываю CopyFileEx из приложения C# с передачей анонимного делегата в параметр LPPROGRESS_ROUTINE, чтобы получать уведомления о ходе копирования файла.

Мой вопрос в том, нужно ли закреплять анонимного делегата и почему (или почему нет).

Кроме того, изменится ли ответ, если:

  1. CopyFileEx не блокировал.
  2. Если бы я передал делегата, который не был анонимным.

Спасибо!


person SpeksETC    schedule 28.03.2011    source источник


Ответы (3)


Делегат не нужно закреплять. Управляемый объект закрепляется, если он не может быть перемещен сборщиком мусора. Если маршалинговая информация верна, то маршалинговый уровень обеспечит передачу указателя на что-то неподвижное.

Однако комментарий выше, где вы предполагаете, что локальная переменная может поддерживать активность делегата, указывает на неправильное понимание времени жизни переменной. Я отсылаю вас к спецификации, в которой говорится:

Фактическое время жизни локальной переменной зависит от реализации. Например, компилятор может статически определить, что локальная переменная в блоке используется только для небольшой части этого блока. Используя этот анализ, компилятор может сгенерировать код, в результате которого время жизни переменной будет меньше, чем в содержащем ее блоке. Хранилище, на которое ссылается локальная ссылочная переменная, освобождается независимо от времени жизни этой локальной ссылочной переменной.

Другими словами, если вы скажете:

void M()
{
    Foo foo = GetAFoo();
    UnmanagedLibrary.DoSomethingToFoo(foo);
}

тогда дрожание может сказать: «Вы знаете, я вижу, что ни один управляемый код никогда не использует foo снова в тот момент, когда вызывается неуправляемый вызов; поэтому я могу агрессивно восстановить хранилище этого объекта из другого потока в то время". Это означает, что неуправляемый вызов может работать с объектом, когда он внезапно освобождается в другом потоке.

Это особенно неприятно, если у Foo есть деструктор. Код финализации, возможно, будет выполняться в другом потоке, пока объект используется неуправляемой библиотекой, и одному Богу известно, к какой катастрофе это приведет.

В этом случае вам необходимо использовать KeepAlive, чтобы поддерживать управляемый объект в рабочем состоянии. Не полагайтесь на локальную переменную; локальные переменные специально задокументированы как не, которые гарантируют сохранение жизни.

См. http://msdn.microsoft.com/en-us/library/system.gc.keepalive.aspx для получения дополнительной информации.

person Eric Lippert    schedule 28.03.2011
comment
Спасибо, Эрик. Я немного сбит с толку, поскольку Крис Брамм утверждает, что PInvoke либо скопирует ваши данные в фиксированную память за пределы кучи GC, либо закрепит память в куче GC и предоставит эти байты непосредственно неуправляемому коду. В любом случае вам не нужно явно закреплять — до тех пор, пока доступ к этим байтам ограничивается продолжительностью вызова PInvoke. в blogs.msdn.com /b/cbrumme/archive/2003/05/06/ — разве это не относится и к делегатам? Я предположил, что если слой pinvoke закрепит данные, GC не соберет... - person SpeksETC; 29.03.2011
comment
@SpeksETC: в статье, на которую вы ссылаетесь, конкретно указано, что ваше предположение неверно. В нем говорится, Однако приложение отвечает за то, чтобы каким-то образом продлить время жизни делегата до тех пор, пока не прекратятся вызовы из неуправляемого кода. - person Eric Lippert; 29.03.2011
comment
@Eric Lippert: Разве это утверждение не может относиться только к асинхронным неуправляемым вызовам? Кажется, он опровергает то, что он пишет в комментариях: если неуправляемому вызываемому объекту нужен доступ к буферу только на время вызова, то уровень маршалинга PInvoke обычно закрепляет его на это время, если только уровень маршалинга не обрабатывает делегаты по-разному... - person SpeksETC; 29.03.2011
comment
@Eric, в документации KeepAlive сказано, что метод KeepAlive поддерживает ссылку до конца вызова. Я бы предположил (ошибочно?), что вызов метода P/Invoke не будет особенным в этом отношении и будет поддерживать ссылку до конца вызова. Если это действительно так (что отличается от того, что вы опубликовали), то KeepAlive в основном потребуется только в сценариях обратного вызова, таких как тот, что в примере для примера KeepAlive. Я знаю, что GC очень агрессивен с местными жителями, но не думал, что все зашло так далеко, как вы написали. Не могли бы вы подробнее остановиться на этом конкретном моменте? - person Jason Kresowaty; 29.03.2011
comment
@binarycoder - GC заходит так далеко, как вы написали, если, например, локальная переменная передается в метод Foo (объект X), который состоит из 100 строк кода, но первая строка — единственная, которая использует X, затем после этой строки можно собрать X - даже если Foo еще не вернулся (при условии, что он не используется вызывающим абонентом снова - что, по сути, делает KeepAlive, снова использует экземпляр). Кажется, что pInvoke может быть другим, поскольку в рамках маршалинга он закрепляет параметры, но это то, что я пытаюсь проверить... - person SpeksETC; 29.03.2011
comment
@binarycoder — KeepAlive не поддерживает ссылку до тех пор, пока KeepAlive не вернется. Как и любой метод (который не оптимизирован), он поддерживает ссылку только до тех пор, пока не будет вызван, о чем говорится в документации. Ссылается на указанный объект, что делает его непригодным для сборки мусора с начала текущей процедуры до момента вызова этого метода. Это ничего не говорит о том, чтобы сохранить его живым до конца вызова. - person Jeffrey L Whitledge; 29.03.2011
comment
@ Джеффри, ты прав. Похоже, я слишком быстро прочитал документацию... Но я чувствую, что P/Invoke автоматически закрепляет/удерживает ссылки на параметры до конца вызова, а GC.KeepAlive необходим для обратных вызовов. Это в основном объясняет, почему KeepAlive упоминается только в сочетании с делегатами и/или асинхронными вещами, а не с массивами, такими как byte[]. Есть точная информация об обратном? - person Jason Kresowaty; 29.03.2011

Вам не нужно закреплять его, но вам нужно сохранить ссылку на него, пока выполняется копирование.

Преобразователь, который вызывается неуправляемым кодом, закреплен, но вы должны убедиться, что делегат не собирается мусором — отсюда и ссылка.

person Stephen Cleary    schedule 28.03.2011
comment
Если анонимный метод является локальной переменной, считается ли он живым, пока не вернулся CopyFileEx? - person SpeksETC; 29.03.2011
comment
@sehe, @SpeksETC: Вы уверены? Где именно в спецификации это сказано? Моя копия спецификации говорит об обратном, а именно: компилятор может сгенерировать код, в результате которого время жизни переменной будет меньше, чем у содержащего ее блока. - person Eric Lippert; 29.03.2011
comment
Спасибо - это именно то, что меня беспокоило (увидел это однажды, когда экземпляр был собран и завершен, когда один из его методов находился в середине выполнения). Я только что прочитал blogs.msdn .com/b/cbrumme/archive/2003/05/06/ и, если я правильно понял, кажется, что слой pinvoke выполняет закрепление за вас на время жизни вызова, поэтому в этом случае кажется, что он покрыт ... - person SpeksETC; 29.03.2011
comment
Я совершенно уверен, что компилятор может справиться и с этим разрешением;) - person Jon Hanna; 29.03.2011
comment
@eric: Да, но мы говорим о ссылке, которая здесь передается как параметр [in] функции. Разве это не должно обычно означать, что параметр остается действительным, по крайней мере, до тех пор, пока функция не вернется? Для ясности: я отвечал на комментарий до тех пор, пока CopyFileEx не вернулся? [я предполагаю отсутствие небезопасных ссылок ptr/weakreferences] - person sehe; 29.03.2011
comment
@sehe - Нет. Механизм, определяющий фактическое время жизни переменной, может предполагать, что сам параметр в вызываемом методе будет содержать ссылку на управляемый объект, если это необходимо. Это может быть полезной оптимизацией, если переменная параметра быстро устанавливается в значение null (или какое-либо другое значение) в вызываемом методе, и в этом случае управляемый объект не имеет ссылок и может быть безопасно собран, даже если вызванный метод еще не возвратил . - person Jeffrey L Whitledge; 29.03.2011
comment
(продолжение) — Очевидно, что эти предположения не обязательно верны при вызове неуправляемого кода, поэтому KeepAlive необходим в этих обстоятельствах. - person Jeffrey L Whitledge; 29.03.2011
comment
@sehe: Джеффри прав. Передача ссылки в качестве аргумента не обязательно продлевает срок жизни чего-либо. Если вы передаете мёртвый объект методу, который его не использует, он всё равно мёртв и, следовательно, может быть восстановлен в любое время. С точки зрения управляемой среды выполнения неуправляемые функции не используют управляемые объекты. Если вы хотите сообщить среде выполнения, что неуправляемая функция использует управляемый объект, вы несете ответственность за его поддержание в рабочем состоянии. Вот что означает неуправляемый — если вы хотите вызвать код, который не управляет временем существования объекта, вы можете сделать это вместо этого. - person Eric Lippert; 29.03.2011

Из следующего msdn кажется, что и закрепление, и GC.KeepAlive в этом случае он не нужен, так как CopyFileEx является синхронным. В частности, говорится:

«Обычно вам не нужно беспокоиться о времени жизни делегатов. Всякий раз, когда вы передаете делегата неуправляемому коду, CLR гарантирует, что делегат жив во время вызова. Однако, если собственный код хранит копию указатель за пределами диапазона вызова и намеревается выполнить обратный вызов через этот указатель позже, вам может потребоваться использовать GCHandle, чтобы явно предотвратить сбор делегата сборщиком мусора».

Поскольку CopyFileEx не хранит указатель на функцию за пределами диапазона вызова, нам не нужно вызывать KeepAlive.

person SpeksETC    schedule 29.03.2011
comment
Это неверно, даже если шанс столкнуться с проблемой в этом случае очень мал. Это, как указано в других ответах, GC - в другом потоке - может вернуть делегата, предоставленного в качестве параметра в любой момент после начала вызова. Для краткосрочного синхронного вызова это обычно, вам не о чем беспокоиться, но единственный способ гарантировать, что он будет работать, — убедиться, что сборщик мусора не будет< /i> восстановить его, используя KeepAlive или иным образом поддерживая сильную ссылку. - person user2864740; 17.02.2015
comment
Иными словами, вы бы чувствовали себя так же комфортно, не используя KeepAlive, если синхронный вызов длится несколько минут? Я уверен, что нет, и меня укусило это, как правило, не всегда; даже несколько секунд — это эоны времени для ЦП и достаточно свободного времени для того, чтобы сборщик мусора стал слишком голодным. Обязательно контролируйте время жизни точно так, как требуется. - person user2864740; 17.02.2015