2009-07-17 7 views
9

Estoy probando la existencia de un archivo en un recurso compartido remoto (en un servidor de Windows). La función subyacente utilizada para la prueba es GetFileAttributes de WinAPI, y lo que ocurre es que la función puede tardar mucho tiempo (docenas de segundos) en diversas situaciones, como cuando el servidor de destino está fuera de línea, cuando hay derechos o problemas de DNS. etc.¿Cómo evitar puestos en la red en GetFileAttributes?

Sin embargo, en mi caso particular, siempre es un acceso LAN, por lo que si no se puede acceder al archivo en menos de 1 segundo, normalmente no se podrá acceder a él esperando docenas de segundos más.

¿Existe una alternativa a GetFileAttributes que no se detenga? (aparte de llamarlo en un hilo y matar el hilo después de un tiempo de espera, lo que parece traer su propia bolsa de problemas)

+0

puedo pensar en cualquier cosa excepto el modelo asíncrono. –

+0

En otras circunstancias, he resuelto este problema mediante la implementación de un servidor web minimalista para servir los archivos compartidos, ya que una solicitud HTTP puede cancelarse o sincronizarse fácilmente. Pero en este caso, esta no es la solución por una variedad de razones (dolores de cabeza de implementación, problemas de seguridad, etc.). –

Respuesta

6

El problema no es realmente GetFileAttributes. Por lo general, utiliza una sola llamada al controlador del sistema de archivos subyacente. Es ese IO que se está estancando.

Aún así, la solución es probablemente fácil. Llame al CancelSynchronousIo() después de un segundo (esto obviamente requiere un segundo hilo ya que el primero está atrapado en GetFileAttributes).

+0

Tenga en cuenta que CancelSynchronousIo no está disponible en Windows XP. –

+0

@MSalters: recibo un error de acceso denegado (5) al usar el método GetFileAttributes. Tengo el servidor de Windows 2003 con poca configuración de hardware. Probé la misma llamada con permiso deshabilitado en otros sistemas que funcionó perfectamente. Puede ralentizar IO porque se produce un error de "acceso denegado". –

+0

@RahulKP: bastante improbable. – MSalters

4

Una cosa genial acerca de los delegados es que siempre puede BeginInvoke y EndInvoke ellos. Solo asegúrese de que el método llamado no arroje una excepción ya que [creo] causará un bloqueo (excepción no controlada).

AttributeType attributes = default(AttributeType); 

Action<string> helper = 
    (path) => 
    { 
     try 
     { 
      // GetFileAttributes 
      attributes = result; 
     } 
     catch 
     { 
     } 
    }; 
IAsyncResult asyncResult = helper.BeginInvoke(); 
// whatever 
helper.EndInvoke(); 
// at this point, the attributes local variable has a valid value. 
+1

Entonces, básicamente, ¿no hay esperanza fuera de envolver la llamada API en un hilo? Esperaba una solución fuera de los hilos, porque matar un hilo en un tiempo de espera no es "limpio" (por experiencia, pueden pasar cosas malas), e ignorar los hilos atascados podría potencialmente llevar a un montón de hilos estancados ... –

+0

Lo siento, también parece que asumí erróneamente que estabas trabajando en .NET (respondí algunas de ellas antes de esto). Si la API no tiene una versión asíncrona y/o de tiempo de espera disponible, entonces la solución de subprocesamiento puede ser la única solución confiable. –

0

Creo que la mejor solución es utilizar un subproceso de grupo de subprocesos para realizar el trabajo.

  • asignar una unidad de trabajo para consultar los atributos de un archivo
  • vamos GetFileAttributes se termina de ejecutar
  • enviar los resultados de vuelta a su forma
  • cuando su función rosca completa, el hilo vuelve automáticamente al grupo (no es necesario que lo mate)

Al usar el grupo de subprocesos, ahorra los costos de crear nuevos subprocesos.
Y guardas la miseria de tratar de deshacerte de ellos.

entonces usted tiene su método de ayuda práctica que funciona procedimiento de método de un objeto en un hilo de rosca de la piscina usando QueueUserWorkItem:

RunInThreadPoolThread(
     GetFileAttributesThreadMethod, 
     TGetFileAttributesData.Create('D:\temp\foo.xml', Self.Handle), 
     WT_EXECUTEDEFAULT); 

de crear el objeto de mantener la información de datos de rosca:

TGetFileAttributesData = class(TObject) 
public 
    Filename: string; 
    WndParent: HWND; 
    Attributes: DWORD; 
    constructor Create(Filename: string; WndParent: HWND); 
end; 

y crear su método de devolución de llamada de rosca:

procedure TForm1.GetFileAttributesThreadMethod(Data: Pointer); 
var 
    fi: TGetFileAttributesData; 
begin 
    fi := TObject(Data) as TGetFileAttributesData; 
    if fi = nil then 
     Exit; 

    fi.attributes := GetFileAttributes(PWideChar(fi.Filename)); 

    PostMessage(fi.WndParent, WM_GetFileAttributesComplete, NativeUInt(Data), 0); 
end; 

t gallina que acaba de tratar el mensaje:

procedure WMGetFileAttributesComplete(var Msg: TMessage); message WM_GetFileAttributesComplete; 

procedure TfrmMain.WMGetFileAttributesComplete(var Msg: TMessage); 
var 
    fi: TGetFileAttributesData; 
begin 
    fi := TObject(Pointer(Msg.WParam)) as TGetFileAttributesData; 
    try 
     ShowMessage(Format('Attributes of "%s": %.8x', [fi.Filename, fi.attributes])); 
    finally 
     fi.Free; 
    end; 
end; 

La mágica RunInThreadPoolThread es sólo un poco de pelusa que le permite ejecutar un método de instancia en un hilo:

Cuál es sólo un envoltorio que le permite llamar método en un variable de instancia:

TThreadMethod = procedure (Data: Pointer) of object; 

TThreadPoolCallbackContext = class(TObject) 
public 
    ThreadMethod: TThreadMethod; 
    Context: Pointer; 
end; 

function ThreadPoolCallbackFunction(Parameter: Pointer): Integer; stdcall; 
var 
    tpContext: TThreadPoolCallbackContext; 
begin 
    try 
     tpContext := TObject(Parameter) as TThreadPoolCallbackContext; 
    except 
     Result := -1; 
     Exit; 
    end; 
    try 
     tpContext.ThreadMethod(tpContext.Context); 
    finally 
     try 
      tpContext.Free; 
     except 
     end; 
    end; 
    Result := 0; 
end; 

function RunInThreadPoolThread(const ThreadMethod: TThreadMethod; const Data: Pointer; Flags: ULONG): BOOL; 
var 
    tpContext: TThreadPoolCallbackContext; 
begin 
    { 
     Unless you know differently, the flag you want to use is 0 (WT_EXECUTEDEFAULT). 

     If your callback might run for a while you can pass the WT_ExecuteLongFunction flag. 
       Sure, I'm supposed to pass WT_EXECUTELONGFUNCTION if my function takes a long time, but how long is long? 
       http://blogs.msdn.com/b/oldnewthing/archive/2011/12/09/10245808.aspx 

     WT_EXECUTEDEFAULT (0): 
       By default, the callback function is queued to a non-I/O worker thread. 
       The callback function is queued to a thread that uses I/O completion ports, which means they cannot perform 
       an alertable wait. Therefore, if I/O completes and generates an APC, the APC might wait indefinitely because 
       there is no guarantee that the thread will enter an alertable wait state after the callback completes. 
     WT_EXECUTELONGFUNCTION (0x00000010): 
       The callback function can perform a long wait. This flag helps the system to decide if it should create a new thread. 
     WT_EXECUTEINPERSISTENTTHREAD (0x00000080) 
       The callback function is queued to a thread that never terminates. 
       It does not guarantee that the same thread is used each time. This flag should be used only for short tasks 
       or it could affect other timer operations. 
       This flag must be set if the thread calls functions that use APCs. 
       For more information, see Asynchronous Procedure Calls. 
       Note that currently no worker thread is truly persistent, although worker threads do not terminate if there 
       are any pending I/O requests. 
    } 

    tpContext := TThreadPoolCallbackContext.Create; 
    tpContext.ThreadMethod := ThreadMethod; 
    tpContext.Context := Data; 

    Result := QueueUserWorkItem(ThreadPoolCallbackFunction, tpContext, Flags); 
end; 

ejercicio para el lector: Crear un Cancelado bandera dentro del objeto GetFileAttributesData que dice º e hilo que es debe liberar el objeto de datos y no publicar un mensaje para el padre.


Es todo un largo camino de decir que está creando:

DWORD WINAPI GetFileAttributes(
    _In_  LPCTSTR       lpFileName, 
    _Inout_ LPOVERLAPPED     lpOverlapped, 
    _In_  LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine 
); 
+0

El uso de subprocesos agrupados no resuelve el problema de que los subprocesos se pueden estancar durante mucho tiempo ya que GetFileAttributes está esperando algunos tiempos de espera de red. También es necesario ignorar los hilos irrelevantes. Por ejemplo, si consulta el mismo archivo dos veces con 10 segundos, la primera puede quedarse atascada durante 30 segundos, mientras que la segunda puede tener éxito inmediatamente (y debe ignorar los resultados de la primera llamada). Además, si supervisa varios archivos con una frecuencia de unos pocos segundos, es "fácil" terminar con docenas o incluso cientos de hilos estancados ...no es práctico en absoluto:/ –

+0

Ignorar los hilos irrelevantes se resuelve de la misma manera que si Windows ya proporcionara de forma nativa una versión superpuesta (es decir, asincrónica) 'GetFileAttributesEx': debe cancelar la llamada existente. Eso se resuelve con el ejercicio. Su preocupación es qué hacer cuando tiene docenas o cientos de hilos estancados. Yo diría que esto no es una preocupación, ya que la cola de elementos de trabajo del usuario pondrá los artículos en cola hasta que se eliminen los artículos más antiguos de la cola. Aunque, [todo lo que puede hacer para ayudarlo] (http://msdn.microsoft.com/en-us/library/windows/desktop/aa363794.aspx) devolverá el hilo al grupo más rápido. –

+0

También es útil para no que 'QueueUserWorkItem' * ponga * sus artículos en cola; y no crear cientos de hilos. Uno de los propósitos de 'QueueUserWorkItem' es permitirle * queue * los elementos de trabajo: el grupo de subprocesos decide cuándo se ejecutarán. –

Cuestiones relacionadas