windows 如何避免 GetFileAttributes 中的网络停顿?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1142080/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 12:49:38  来源:igfitidea点击:

How to avoid network stalls in GetFileAttributes?

windowsnetworking

提问by Eric Grange

I'm testing the existence of a file in a remote share (on a Windows server). The underlying function used for testing is WinAPI's GetFileAttributes, and what happens is that function can take an inordinate amount of time (dozens of seconds) in various situations, like when the target server being offline, when there are rights or DNS issues, etc.

我正在测试远程共享中文件的存在(在 Windows 服务器上)。用于测试的底层函数是 WinAPI 的GetFileAttributes,发生的情况是该函数在各种情况下可能会花费过多的时间(数十秒),例如目标服务器离线、存在权限或 DNS 问题等。

However, in my particular case, it's always a LAN access, so if the file can't be accessed in less than 1 second, then it typically won't be accessible by waiting dozens of seconds more...

但是,在我的特定情况下,它始终是 LAN 访问,因此如果无法在 1 秒内访问该文件,则通常无法通过再等待数十秒来访问它...

Is there an alternative to GetFileAttributes that wouldn't stall? (apart from calling it in a thread and killing the thread after a timeout, which seems to bring its own bag of issues)

有没有不会停止的 GetFileAttributes 替代方案?(除了在线程中调用它并在超时后杀死线程,这似乎带来了自己的问题)

采纳答案by MSalters

The problem isn't GetFileAttributes really. It typically uses just one call to the underlying file system driver. It's that IO which is stalling.

问题实际上不在于 GetFileAttributes。它通常只调用一次底层文件系统驱动程序。正是那个 IO 正在停滞。

Still, the solution is probably easy. Call CancelSynchronousIo()after one second (this obviously requires a second thread as your first is stuck inside GetFileAttributes).

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

回答by Sam Harwell

One cool thing about delegates is you can always BeginInvokeand EndInvokethem. Just make sure the called method doesn't throw an exception out since [I believe] it will cause a crash (unhandled exception).

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

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.

回答by Ian Boyd

I think your best solution is to use a thread-pool thread to perform the work.

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

  • assign a unit of work to query the attributes of a file
  • let GetFileAttributesrun to completion
  • post the results back to your form
  • when your thread function completes, the thread automatically returns back to the pool (no need to kill it)
  • 分配一个工作单元来查询文件的属性
  • GetFileAttributes运行完成
  • 将结果发布回您的表单
  • 当您的线程函数完成时,该线程会自动返回到池中(无需杀死它)

By using the thread pool you save the costs of creating new threads.
And you save the misery of trying to get rid of them.

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

Then you have your handy helper method that runs an object's method procedure on a thread-pool thread using QueueUserWorkItem:

然后你有你方便的辅助方法,它使用以下方法在线程池线程上运行对象的方法过程QueueUserWorkItem

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

You create the object to hold the thread data information:

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

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

and you create your thread callback method:

然后创建线程回调方法:

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;

then you just handle the message:

然后你只需处理消息:

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;

The magical RunInThreadPoolThreadis just a bit of fluff that lets you execute an instance method in a thread:

神奇的RunInThreadPoolThread是,它只是让你在线程中执行实例方法的一些绒毛:

Which is just a wrapper that lets you call method on an instance variable:

这只是一个包装器,可让您在实例变量上调用方法:

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;

Exercise for the reader: Create a Cancelledflag inside the GetFileAttributesDataobject that tells the thread that itmust free the data object and notpost a message to the parent.

读者练习:在对象内创建一个Canceled标志GetFileAttributesData,告诉线程必须释放数据对象而不是向父对象发送消息。



It's all a long way of saying that you're creating:

总而言之,您正在创造:

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