我正在测试远程共享(在Windows服务器上)中文件的存在性。用于测试的底层函数是WinAPI的GetFileAttributes,这个函数在各种情况下可能需要很长时间(几十秒),比如目标服务器离线,存在权限或DNS问题等。
然而,在我的情况下,总是局域网访问,所以如果文件不能在1秒内访问,则通常等待数十秒后也无法访问...
是否有一种替代GetFileAttributes但不会停顿的方法?(除了在线程中调用它并在超时后终止线程,这似乎会带来自己的一些问题)
我正在测试远程共享(在Windows服务器上)中文件的存在性。用于测试的底层函数是WinAPI的GetFileAttributes,这个函数在各种情况下可能需要很长时间(几十秒),比如目标服务器离线,存在权限或DNS问题等。
然而,在我的情况下,总是局域网访问,所以如果文件不能在1秒内访问,则通常等待数十秒后也无法访问...
是否有一种替代GetFileAttributes但不会停顿的方法?(除了在线程中调用它并在超时后终止线程,这似乎会带来自己的一些问题)
委托的一个很酷的功能是,您始终可以使用 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.
我认为你最好的解决方案是使用线程池线程来执行工作。
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
对象内创建一个Cancelled标志,告诉线程它必须释放数据对象并且不要向父级发送消息。
这一切都是为了表达你正在创建的内容:
DWORD WINAPI GetFileAttributes(
_In_ LPCTSTR lpFileName,
_Inout_ LPOVERLAPPED lpOverlapped,
_In_ LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
GetFileAttributesEx
版本时相同 - 你必须取消现有的调用。这个问题已经被解决了。你的担心是当你有几十个或几百个停滞的线程时该怎么办。我认为这不是一个问题,因为用户工作项队列会将项目排队,直到旧项目从队列中清除。尽管如此,你可以做任何事情来帮助它,以便更快地将线程返回到池中。 - Ian BoydQueueUserWorkItem
会将您的项目排队,而不是创建数百个线程。 QueueUserWorkItem
的目的之一是让您排队工作项 - 线程池决定何时执行它们。 - Ian Boyd