Delphi Win API CreateTimerQueueTimer subprocesos y seguridad de subprocesos FormatDateTime se bloquea

StackOverflow https://stackoverflow.com/questions/352543

Pregunta

Esta es una pregunta un poco larga, pero aquí vamos. Hay una versión de FormatDateTime que se dice que es segura para subprocesos en la que usa

GetLocaleFormatSettings(3081, FormatSettings); 

para obtener un valor y luego puede usarlo así;

FormatDateTime('yyyy', 0, FormatSettings); 

Ahora imagine dos temporizadores, uno usando TTimer (intervalo digamos 1000ms) y luego otro temporizador creado así (intervalo de 10ms);

CreateTimerQueueTimer
(
  FQueueTimer, 
  0, 
  TimerCallback, 
  nil, 
  10, 
  10, 
  WT_EXECUTEINTIMERTHREAD
);

Ahora el bit narly, si en la devolución de llamada y también en el evento del temporizador tiene el siguiente código;

for i := 1 to 10000 do
begin
  FormatDateTime('yyyy', 0, FormatSettings);
end;

Tenga en cuenta que no hay asignación. Esto produce violaciones de acceso casi de inmediato, a veces 20 minutos después, lo que sea, en lugares aleatorios. Ahora, si escribe ese código en C ++ Builder, nunca falla. La conversión de encabezado que estamos utilizando es la de JEDI JwaXXXX. Incluso si ponemos bloqueos en la versión de Delphi alrededor del código, solo retrasa lo inevitable. Hemos visto los archivos de encabezado C originales y todo se ve bien, ¿hay alguna forma diferente de que C ++ use el tiempo de ejecución de Delphi? La versión segura para subprocesos de FormatDatTime parece ser reentrante. Cualquier idea o pensamiento de cualquiera que haya visto esto antes.

UPDATE:

Para reducir esto un poco, FormatSettings se pasa como una constante, entonces, ¿importa si usan la misma copia (ya que resulta que pasar una versión local dentro de la función llama al mismo problema)? Además, la versión de FormatDateTime que toma FormatSettings no llama a GetThreadLocale, porque ya tiene la información de Locale en la estructura FormatSettings (lo verifiqué dos veces al revisar el código).

Mencioné que no hay asignación para dejar en claro que no se está accediendo al almacenamiento compartido, por lo que no se requiere bloqueo.

WT_EXECUTEINTIMERTHREAD se usa para simplificar el problema. Tenía la impresión de que solo debería usarlo para tareas muy cortas porque puede significar que perderá el siguiente intervalo si está ejecutando algo largo.

Si usa un TThread simple y antiguo, el problema no se produce. Supongo que lo que estoy llegando aquí es que usar un TThread o un TTimer funciona pero usar un thread creado fuera del VCL no funciona, por eso pregunté si había una diferencia en la forma en que C ++ Builder usa el VCL / Delphi RTL.

Como un aparte, este código como se mencionó anteriormente también falla (pero lleva más tiempo), después de un tiempo, CS: = TCriticalSection.Create;

  CS.Acquire;
  for i := 1 to LoopCount do
  begin
    FormatDateTime('yyyy', 0, FormatSettings);
  end;
  CS.Release;

Y ahora, por un momento que realmente no entiendo, escribí esto como se sugiere;

function ReturnAString: string;
begin
  Result := 'Test';
  UniqueString(Result);
end;

y luego dentro de cada tipo de temporizador el código es;

  for i := 1 to 10000 do
  begin
    ReturnAString;
  end;

Esto causa los mismos tipos de fallas, como dije antes, la falla nunca está en el mismo lugar dentro de la ventana de la CPU, etc. Algunas veces es una violación de acceso y otras puede ser una operación de puntero no válida. Estoy usando Delphi 2009 por cierto.

ACTUALIZACIÓN 2:

Roddy (abajo) señala el evento Ontimer (y desafortunadamente también Winsock, es decir, TClientSocket) usa la bomba de mensajes de Windows (aparte, sería bueno tener algunos buenos componentes Winsock2 usando IOCP y Overlapping IO), de ahí el impulso para alejarse de eso. Sin embargo, ¿alguien sabe cómo ver qué tipo de almacenamiento local de subprocesos está configurado en CreateQueueTimerQueue?

Gracias por tomarse el tiempo para pensar y responder a este problema.

¿Fue útil?

Solución

No estoy seguro de si es una buena forma publicar un " Responder " a mi propia pregunta, pero parecía lógico, avíseme si eso no es genial.

Creo que he encontrado el problema, la idea de almacenamiento local del hilo me llevó a seguir un montón de pistas y encontré esta línea mágica;

IsMultiThread: = True;

De la ayuda;

" IsMultiThread se establece en verdadero para indicar que el administrador de memoria debe admitir múltiples subprocesos. IsMultiThread se establece en true por BeginThread y las fábricas de clases. & Quot;

Esto, por supuesto, no se establece mediante el uso de un único subproceso VCL principal con un TTimer, sin embargo, se configura cuando utiliza TThread. Si lo configuro manualmente, el problema desaparece.

En C ++ Builder, no uso un TThread pero aparece usando el siguiente código;

if (IsMultiThread) {
  ShowMessage("IsMultiThread is True!");
}

eso está configurado para usted en algún lugar automáticamente.

Estoy muy contento por la contribución de la gente para poder encontrar esto y espero en vano que pueda ayudar a alguien más.

Otros consejos

Como DateTimeToString que utiliza FormatDateTime llama a GetThreadLocale, es posible que desee intentar tener una variable local FormatSettings para cada subproceso, tal vez incluso configurar FormatSettings en una variable local antes del bucle.

También puede ser el parámetro WT_EXECUTEINTIMERTHREAD que causa esto. Tenga en cuenta que indica que solo debe usarse para tareas muy cortas.

Si el problema persiste, el problema puede estar en otro lugar, que fue mi primer presentimiento cuando vi esto, pero no tengo suficiente información sobre lo que hace el código para determinar realmente eso.

Los detalles sobre dónde ocurre la infracción de acceso pueden ayudar.

¿Estás seguro de que esto realmente tiene algo que ver con FormatDateTime? Usted mencionó que no hay una declaración de asignación allí; ¿Es ese un aspecto importante de tu pregunta? ¿Qué sucede si llama a alguna otra función de retorno de cadena? (Asegúrese de que no sea una cadena constante. Escriba su propia función que llame a UniqueString(Result) antes de regresar).

¿La variable FormatSettings es específica del hilo? Ese es el punto de tener el parámetro adicional para TThread, por lo que cada hilo tiene su propia copia privada que se garantiza que no será modificada por ningún otro hilo mientras la función esté activa.

¿Es importante la cola del temporizador para esta pregunta? ¿O obtienes los mismos resultados cuando usas un simple Execute viejo y ejecutas tu ciclo en el método <=>?

Usted advirtió que era una pregunta larga, pero parece que hay algunas cosas que podría hacer para que sea más pequeña, para reducir el alcance del problema.

Me pregunto si las llamadas RTL / VCL que está haciendo esperan acceso a algunas variables de almacenamiento local de subprocesos (TLS) que no están configuradas correctamente cuando invoca su código a través de la cola del temporizador.

Esta no es la respuesta a su problema, pero ¿sabe que los eventos TTimer OnTimer solo se ejecutan como parte del bucle de mensajes normal en el hilo principal de VCL?

Has encontrado tu respuesta: IsMultiThread. Esto debe usarse en cualquier momento para volver a usar la API y crear hilos. Desde MSDN: CreateTimerQueueTimer está creando un grupo de subprocesos para manejar esta funcionalidad para que tenga un subproceso externo que funcione con el subproceso VCL principal sin protección. (Nota: su CS.acquire / release no hace nada a menos que otras partes del código respeten este bloqueo).

Re. su última pregunta sobre Winsock y la superposición de E / S: debe mirar atentamente Indy .

Indy utiliza el bloqueo de E / S, y es una excelente opción cuando desea una E / S de red de alto rendimiento, independientemente de lo que esté haciendo el hilo principal. Ahora que ha resuelto el problema de subprocesos múltiples, simplemente debe crear otro subproceso (o más) para usar indy para manejar su E / S.

El problema con Indy es que si necesita muchas conexiones, no es eficiente en absoluto. Requiere un hilo por conexión (bloqueo de E / S) que no se escala en absoluto, de ahí el beneficio de IOCP y Overlapping IO, es prácticamente la única forma escalable en Windows.

Para la actualización2:

Hay componentes de socket IOCP gratuitos: http://www.torry.net /authorsmore.php?id=7131 (código fuente incluido)

  

" Por Naberegnyh Sergey N .. High   servidor de socket de rendimiento basado en   Puerto de finalización de Windows y con el uso   Extensiones de Windows Socket. IPv6   soportado. "

Lo encontré mientras buscaba mejores componentes / biblioteca para volver a diseñar mi pequeño servidor de mensajería instantánea. Todavía no lo he probado, pero se ve bien codificado como primera impresión.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top