我正在测试远程共享(在Windows服务器上)中是否存在文件。用于测试的基础函数是WinAPI的 GetFileAttributes ,会发生的情况是,在各种情况下,该函数可能需要过多的时间(几十秒),例如当目标服务器处于脱机状态时,权利或DNS问题等。
但是,在我的特定情况下,它总是一个局域网访问,所以如果文件在不到1秒的时间内无法访问,那么通常等待几十秒就无法访问...
GetFileAttributes有替代方案吗? (除了在线程中调用它并在超时后杀死线程,这似乎带来了自己的问题)
答案 0 :(得分:7)
问题不在于GetFileAttributes。它通常只使用一次对底层文件系统驱动程序的调用。这就是IO停滞不前。
不过,解决方案可能很简单。一秒后调用CancelSynchronousIo()(这显然需要第二个线程,因为第一个线程被卡在GetFileAttributes中)。
答案 1 :(得分:4)
关于代表的一个很酷的事情是,您可以始终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.
答案 2 :(得分:0)
我认为您最好的解决方案是使用线程池线程来执行工作。
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
对象中创建一个已取消的标志,该标志告诉线程 it 必须释放数据对象和不将消息发布到父级。
说你创造了很长的路要走:
DWORD WINAPI GetFileAttributes(
_In_ LPCTSTR lpFileName,
_Inout_ LPOVERLAPPED lpOverlapped,
_In_ LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);