2011-03-28 19 views
7

Llamo a CopyFileEx desde una aplicación C# con un delegado anónimo que se pasa al parámetro LPPROGRESS_ROUTINE para recibir notificaciones sobre el progreso de la copia del archivo.¿Debo anclar un delegado anónimo?

Mi pregunta es, ¿tiene que anclarse el delegado anónimo y por qué (o por qué no)?

Además, ¿Cambia la respuesta si:

  1. CopyFileEx no estaba bloqueando.
  2. Si pasé en un delegado que no era anónimo.

¡Gracias!

Respuesta

3

No necesita fijarlo, pero debe mantener una referencia viva mientras la copia esté en progreso.

El pinchazo que recibe el código no administrado está anclado, pero debe asegurarse de que el delegado no sea un recolector de elementos no utilizados, de ahí la referencia.

+0

Si el método anónimo es una variable local ¿se considera "activo" mientras CopyFileEx no haya regresado? – SpeksETC

+0

estoy seguro de que es – sehe

+0

@sehe, @SpeksETC: Muy cierto, ¿verdad? ¿Dónde exactamente en la especificación dice eso? Mi copia de la especificación dice lo contrario, es decir, "* el compilador puede generar código que da como resultado que el almacenamiento de la variable tenga una vida útil más corta que su bloque *." –

6

El delegado no necesita ser fijado. Un objeto administrado es anclado si no puede ser movido por el recolector de basura. Si la información de clasificación es correcta, la capa de clasificación garantizará que se pase un puntero a algo inmóvil.

Sin embargo, el comentario anterior en el que sugiere que una variable local puede mantener al delegado con vida indica un malentendido de la duración de la variable. Lo remito a la especificación, que dice:

La vida útil real de una variable local depende de la implementación. Por ejemplo, un compilador puede determinar estáticamente que una variable local en un bloque solo se usa para una pequeña porción de ese bloque. Usando este análisis, el compilador podría generar código que da como resultado que el almacenamiento de la variable tenga una vida más corta que su bloque contenedor. El almacenamiento contemplada por una variable de referencia local se recupera independientemente del curso de la vida de esa variable de referencia local

En otras palabras, si usted dice:

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

continuación, se permite decir la fluctuación " ya sabes, veo que ningún código administrado usa foo nuevamente en el momento después de invocar la llamada no administrada, por lo que puedo reclamar agresivamente el almacenamiento de ese objeto desde otro subproceso en ese momento ". Lo que significa que la llamada no administrada puede estar funcionando en el objeto cuando de repente se desasigna en otro hilo.

Esto es particularmente desagradable si Foo tiene un destructor. El código de finalización posiblemente se ejecutará en otro subproceso mientras el objeto esté en uso por la biblioteca no administrada, y solo Dios sabe qué tipo de desastre causará.

En esta circunstancia debe usar KeepAlive para mantener vivo el objeto gestionado. No confíe en una variable local; las variables locales están específicamente documentadas como no garantizadas para mantener las cosas con vida.

Ver http://msdn.microsoft.com/en-us/library/system.gc.keepalive.aspx para más detalles.

+0

Gracias Eric. Estoy un poco confundido ya que Chris Brumme afirma que "PInvoke copiará tus datos a la memoria fija fuera del montón del GC, o fijará la memoria en el montón del GC y expondrá esos bytes directamente al código no administrado. En cualquier caso, no lo harás". Es necesario anclar explícitamente, siempre y cuando el alcance de estos bytes se alcance dentro de la duración de la llamada PInvoke. "en http://blogs.msdn.com/b/cbrumme/archive/2003/05/06/51385 .aspx? PageIndex = 1 # comentarios - ¿no es esto también relevante para los delegados? Supuse que si la capa de pinvoke fija los datos, GC no recolectaría ... – SpeksETC

+2

@SpeksETC: el artículo al que se vinculó especifica que su suposición es incorrecta. Dice "** Sin embargo, la aplicación es responsable de extender de alguna manera la vida útil del delegado hasta que no ocurran más llamadas del código no administrado **." –

+0

@Eric Lippert: ¿No podría esa afirmación solo referirse a llamadas no administradas asincrónicas? Parece negar lo que escribe en los comentarios que" Si el destinatario no administrado solo necesita acceder al búfer durante la vida de la llamada, entonces el PInvoke la capa de clasificación generalmente lo fijará por esa duración "a menos que la capa de clasificación trate a los delegados de manera diferente ... – SpeksETC

2

De la siguiente msdn parece que tanto pinning como GC.KeepAlive no son necesarios en este caso dado que CopyFileEx es síncrono. Específicamente, dice:

"Por lo general, no tendrá que preocuparse por la duración de los delegados. Cuando transfiera un delegado al código no administrado, el CLR se asegurará de que el delegado esté activo durante la llamada. el código nativo guarda una copia del puntero más allá del lapso de la llamada y tiene la intención de volver a llamar a través de ese puntero más adelante; es posible que necesite usar GCHandle para evitar explícitamente que el recolector de elementos innecesarios recopile el delegado ".

Como CopyFileEx no mantiene un puntero a la función más allá del lapso de la llamada, no deberíamos necesitar llamar a KeepAlive.

+0

Esto es incorrecto incluso si la posibilidad de tener un problema en este caso es "muy delgada". Esto es, como se señala en las otras respuestas, el GC - en otro hilo - * puede * reclamar el delegado suministrado como parámetro * en cualquier punto * después de que la llamada haya comenzado. Para una llamada síncrona de corta duración, esto es un "por lo general, no tendrá que preocuparse", pero la única forma de * garantizar * que funcione es asegurarse de que el GC * no * lo reclame mediante el uso de KeepAlive. o manteniendo una referencia fuerte. – user2864740

+0

Dicho de otra manera: ¿se sentiría tan cómodo * no * con KeepAlive si la llamada síncrona abarca varios minutos? Estoy seguro de que no me han mordido porque "normalmente" no es "siempre"; incluso varios segundos son eones para una CPU y mucho tiempo libre para que un GC tenga demasiada hambre. Asegúrese de controlar la vida útil exactamente como se requiere. – user2864740

Cuestiones relacionadas