我正在testing远程共享(在Windows服务器上)文件的存在。 用于testing的底层函数是WinAPI的GetFileAttributes ,会发生什么情况是函数可能会在各种情况下花费大量的时间(几十秒),例如目标服务器处于脱机状态,存在权限或DNS问题等。
然而,在我的情况下,它总是一个局域网访问,所以如果该文件不能被访问不到一秒钟,那么通常不会被等待几十秒更容易访问…
是否有替代GetFileAttributes不会失速? (除了在一个线程中调用它,并在超时后杀死线程,这似乎带来了自己的问题)
问题不是GetFileAttributes真的。 它通常只使用一个调用底层的文件系统驱动程序。 这是IO的拖延。
不过,解决方案可能很简单。 在一秒之后调用CancelSynchronousIo() (这显然需要第二个线程,因为你的第一个线程被卡在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 );