Как избежать сетевых сбоев в GetFileAttributes? - PullRequest
12 голосов
/ 17 июля 2009

Я проверяю наличие файла в удаленном общем ресурсе (на сервере Windows). Базовой функцией, используемой для тестирования, является WinAPI GetFileAttributes , и происходит то, что функция может занимать чрезмерное количество времени (десятки секунд) в различных ситуациях, например, когда целевой сервер находится в автономном режиме, когда есть права или проблемы с DNS и т. д.

Однако, в моем конкретном случае, это всегда доступ по локальной сети, поэтому, если файл не может быть доступен менее чем за 1 секунду, то он, как правило, не будет доступен, ожидая десятки секунд больше ...

Есть ли альтернатива GetFileAttributes, которая не остановится? (кроме вызова его в потоке и его уничтожения после истечения времени ожидания, что, похоже, приносит свои проблемы)

Ответы [ 3 ]

7 голосов
/ 17 июля 2009

Проблема на самом деле не в GetFileAttributes. Обычно он использует только один вызов базового драйвера файловой системы. Это тот IO, который останавливается.

Тем не менее, решение, вероятно, простое. Вызовите CancelSynchronousIo () через одну секунду (очевидно, для этого требуется второй поток, поскольку ваш первый застрял внутри GetFileAttributes).

4 голосов
/ 17 июля 2009

Одна из замечательных особенностей делегатов - вы всегда можете 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.
0 голосов
/ 05 декабря 2016

Я думаю, что ваше лучшее решение - использовать поток пула потоков для выполнения работы.

  • назначить единицу работы для запроса атрибутов файла
  • пусть 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
);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...