代码之家  ›  专栏  ›  技术社区  ›  Eric Grange

如何避免getfileattributes中的网络暂停?

  •  11
  • Eric Grange  · 技术社区  · 16 年前

    我正在测试远程共享(在Windows服务器上)中是否存在文件。用于测试的基础函数是winapi的 获取文件属性 在各种情况下,如目标服务器脱机、存在权限或DNS问题等,函数可能会花费大量的时间(几十秒)。

    然而,在我的特殊情况下,它总是一个局域网访问,所以如果文件不能在不到一秒钟的时间内被访问,那么它通常不会被等待几十秒更多的时间来访问…

    有没有其他方法可以替代getfileattributes而不会停止?(除了在线程中调用它并在超时后终止线程,这似乎带来了自己的问题包)

    3 回复  |  直到 9 年前
        1
  •  7
  •   Josh Kelley    12 年前

    问题不是GetFileAttributes。它通常只使用一个对底层文件系统驱动程序的调用。是那个IO在拖延。

    不过,解决方案可能很简单。呼叫 CancelSynchronousIo() 一秒钟后(这显然需要第二个线程,因为第一个线程卡在getfileattributes中)。

        2
  •  4
  •   Sam Harwell    16 年前

    关于代表的一个很酷的事情是你可以 BeginInvoke EndInvoke 他们。只要确保被调用的方法不会抛出异常,因为[我相信]它会导致崩溃(未处理的异常)。

    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.
    
        3
  •  0
  •   Ian Boyd    9 年前

    我认为您最好的解决方案是使用线程池线程来执行该工作。

    • 分配工作单元以查询文件的属性
    • GetFileAttributes 运行到完成
    • 将结果发回表单
    • 当线程函数完成时,线程自动返回池(不需要终止它)

    通过使用线程池,您可以节省创建新线程的成本。
    你也省去了试图摆脱他们的痛苦。

    然后您就有了一个方便的助手方法,它可以在线程池线程上使用 QueueUserWorkItem :

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

    创建对象以保存线程数据信息:

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

    然后创建线程回调方法:

    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;
    

    然后您只需处理消息:

    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;
    

    神奇的 RunInThreadPoolThread 只是让您在线程中执行实例方法的一点错误:

    它只是一个包装器,允许您对实例变量调用方法:

    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;
    

    给读者的练习 创建一个 取消 旗内 GetFileAttributesData 告诉线程 必须释放数据对象并 向家长发送消息。


    要说你在创造:

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