Гость
Целевая тема:
Создать новую тему:
Автор:
Форумы / Delphi [игнор отключен] [закрыт для гостей] / Корректное чтение файлов, в которые периодически заняты сторонним приложением. / 18 сообщений из 18, страница 1 из 1
26.11.2019, 18:46
    #39894660
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
Есть программы, которые периодически скидывают данные в текстовые файлы, есть одна программа, которая периодически эти данные считывает.
Записываются файлы классическим WriteLn, считываются TSringList.LoadFromFile();
Но! Иногда при считывании вылетала ошибка о том, что файл открыт на запись (что логично), на данный момент обыграл функцией проверяющей доступность файла, вроде не вылетает ошибка, но может просто везет.
функция проверки доступности FileIsUse
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
function FileIsUse(fName: string): boolean;   //проверка файла на открытия не запись
  var
    HFileRes: HFILE;
  begin
    Result := false;
    if not FileExists(fName) then exit;
    HFileRes := CreateFile(pchar(fName), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
    Result := (HFileRes = INVALID_HANDLE_VALUE);
    if not Result then CloseHandle(HFileRes);
  end;


В коде делают так
Код: pascal
1.
2.
     while FileIsUse (FilePath) do Sleep(10);
     ListFile.LoadFromFile(FilePath);


Но как-то сомнения, что так правильно.

Ну и обратный вопрос LoadFromFile() не блокирует файл для записи? (что гораздо страшнее, так записывающую программу ломать очень не хочется)

_Vasilisk_

Андрей Игоревич
Код: pascal
1.
 while FileIsUse (FilePath) do Sleep(10);

это полные бред

Делаю так из соображений, что запись происходит в файл крайне редко (раз в десятки минут, гарантированно не чаще), если этот момент пропустить - чтение однозначно будет произведено, чтение однозначно занимает милисекунды.

На всех форумах предлагают варианты с обработкой исключений, но неужели иначе нельзя. И насколько правильно обрабатывать исключения в потоках? Никаких подводных камней?

В принципе в случае, если файл недоступен мне просто надо подождать.
...
Рейтинг: 0 / 0
26.11.2019, 18:50
    #39894662
Квейд
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
так не поможет?

Код: pascal
1.
2.
3.
4.
5.
6.
FileStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite);
try
  ListFile.LoadFromStream(FileStream);
finally
  FileStream.Free
end
...
Рейтинг: 0 / 0
26.11.2019, 18:59
    #39894671
Kazantsev Alexey
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
Квейд
так не поможет?

Так, либо не откроет, при активном писателе, либо заблокирует возможность писать на время чтения. fmShareDenyNone нужно, это разрешит чтение во время записи и не будет блокировать писателя, если он не открывает файл с эксклюзивными правами.
...
Рейтинг: 0 / 0
26.11.2019, 19:58
    #39894701
_Vasilisk_
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
Андрей Игоревич
LoadFromFile() не блокирует файл для записи?
Смотрим в код
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
procedure TStrings.LoadFromFile(const FileName: string);
var
  Stream: TStream;
begin
  Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    LoadFromStream(Stream);
  finally
    Stream.Free;
  end;
end;

ответ - да, блокирует
Андрей Игоревич
Делаю так из соображений, что запись происходит в файл крайне редко (раз в десятки минут, гарантированно не чаще), если этот момент пропустить - чтение однозначно будет произведено, чтение однозначно занимает милисекунды.
Сценарий:

1. Вы вызвали FileIsUse. Файл был свободен.
2. Другая программа заняла файла
3. Вы вызвали ListFile.LoadFromFile(FilePath); и получили исключение.

Андрей Игоревич
И насколько правильно обрабатывать исключения в потоках? Никаких подводных камней?
Если правильно обрабатывать, то подводных камней нет.

Проблема у вас совсем в другом. Не как открыть файл, а как не поломать пишущую программу. Потому, что если вы откроете эксклюзивно файл, а в это время в этот файл понадобиться что-то записать, то писатель сильно огорчится.

Как я бы решал задачу:

Правильно и универсально - доступ к файлу синхронизирован общим мьютексом. Тогда каждый будет ждать, когда второй файл закроет.

Правильно, но не универсально. Исходим из утверждения
Андрей Игоревич
что запись происходит в файл крайне редко

Вызвал бы ReadDirectoryChangesW() с маской, скажем FILE_NOTIFY_CHANGE_LAST_WRITE (или FILE_NOTIFY_CHANGE_LAST_ACCESS - нужно смотреть, что будет адекватнее), дождался бы окончания записи и спокойно прочитал бы файл

Не правильно, не универсально, но быстро
Дважды прочитал бы файл без монопольного доступа. Если два раза была считана одна и та же информация - файл прочитан корректно

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
type
  {$IFDEF UNICODE}
  TBuffer = TBytes;
  {$ELSE}
  TBuffer = string;
  {$ENDIF}

function OpenFile(const AName: string): THandle;
begin
  Result := CreateFile(
    PChar(AName),
    GENERIC_READ,
    FILE_SHARE_READ or FILE_SHARE_WRITE,
    nil,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL,
    0
  );
end;

function ReadFile(const AName: string): TBuffer;
var
  LFile: THandle;
  LSize: Cardinal;
  LReadSize: Cardinal;
begin
  LFile := OpenFile(AName);
  while LFile = INVALID_HANDLE_VALUE do begin
    Sleep(100);
    LFile := OpenFile(AName);
  end;
  try
    LSize := GetFileSize(AFile, nil);
    if LSize = INVALID_FILE_SIZE then
      RaiseLastOSError;
    SetLength(Result, LSize);
    Win32Check(ReadFile(AFile, Pointer(Result), LSize, @LReadSize, nil));
    if LSize <> LReadSize then
      SetLength(Result, LReadSize);
  finally
    CloseHandle(LFile);
  end;
end;

function SameBuff(const ABuf1, ABuf2: TBuffer): Boolean;
begin
  Result := 
    (Length(ABuf1) = Length(ABuf2)) and
    CompareMem(Pointer(ABuf1), Pointer(ABuf2), Length(ABuf1));
end;

procedure LoadToList(const AName: string; AList: TStrings);
var
  LBuf1: TBuffer;
  LBuf2: TBuffer;
  LCurBuf: ^TBuffer;
begin
  LBuf1 := ReadFile(AName);
  LCurBuf := @LBuf2;
  repeat
    LCurBuf^ := ReadFile(AName);
    if LCurBuf = @LBuf2 then
      LCurBuf := @LBuf1
    else
      LCurBuf := @LBuf2;
  until SameBuff(LBuf1, LBuf2);
  {$IFDEF UNICODE}
  AList.Text := TEncoding.Default.GetString(LBuf1);
  {$ELSE}
  AList.Text := LBuf1;
  {$ENDIF}
end;

...
Рейтинг: 0 / 0
26.11.2019, 20:00
    #39894704
ёёёёё
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
Андрей Игоревич
На всех форумах предлагают варианты с обработкой исключений, но неужели иначе нельзя. И насколько правильно обрабатывать исключения в потоках? Никаких подводных камней?

Никаких камней, все как везде - главное не писать в общие данные одновременно.
...
Ты не борись с ситуацией, а не создавай её. Пусть "пишущие" приложение пишет в один файл, а "читающее" - читает из другого.
Первое пусть пишет в файл с расширением .pre, потом закрывает файл и переименовывает - меняет расширение на .ready, а второе читает из готового *.ready файла.
...
Рейтинг: 0 / 0
26.11.2019, 20:18
    #39894712
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
ёёёёё
Андрей Игоревич
На всех форумах предлагают варианты с обработкой исключений, но неужели иначе нельзя. И насколько правильно обрабатывать исключения в потоках? Никаких подводных камней?

Никаких камней, все как везде - главное не писать в общие данные одновременно.
...
Ты не борись с ситуацией, а не создавай её. Пусть "пишущие" приложение пишет в один файл, а "читающее" - читает из другого.
Первое пусть пишет в файл с расширением .pre, потом закрывает файл и переименовывает - меняет расширение на .ready, а второе читает из готового *.ready файла.

Пишущая программа мне почти недоступна, вносить туда правки сложное дело. Хотя конкретно такую правку может и договорюсь внести, вроде ничего радикального.
...
Рейтинг: 0 / 0
27.11.2019, 09:27
    #39894842
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
_Vasilisk_
Андрей Игоревич
LoadFromFile() не блокирует файл для записи?
Смотрим в код
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
procedure TStrings.LoadFromFile(const FileName: string);
var
  Stream: TStream;
begin
  Stream := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    LoadFromStream(Stream);
  finally
    Stream.Free;
  end;
end;

ответ - да, блокирует

Тут ок, сделаю через TFileStream.Create(name, fmOpenRead or fmShareDenyNone ), просто удивлен, зачем его тут для записи открывают, если для записи есть SaveToFile;

_Vasilisk_
Сценарий:

1. Вы вызвали FileIsUse. Файл был свободен.
2. Другая программа заняла файла
3. Вы вызвали ListFile.LoadFromFile(FilePath); и получили исключение.


Ну по такому сценарию файл может быт заблокирован прям в процессе чтения, тогда уж, действительно, ничего кроме исключения не спасет.

_Vasilisk_

Правильно и универсально - доступ к файлу синхронизирован общим мьютексом. Тогда каждый будет ждать, когда второй файл закроет.

Для этого надо править записывающую программу - а это уже отдельная проблема.
_Vasilisk_

Правильно, но не универсально. Исходим из утверждения
Андрей Игоревич
что запись происходит в файл крайне редко

Вызвал бы ReadDirectoryChangesW() с маской, скажем FILE_NOTIFY_CHANGE_LAST_WRITE (или FILE_NOTIFY_CHANGE_LAST_ACCESS - нужно смотреть, что будет адекватнее), дождался бы окончания записи и спокойно прочитал бы файл

Не правильно, не универсально, но быстро
Дважды прочитал бы файл без монопольного доступа. Если два раза была считана одна и та же информация - файл прочитан корректно

Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
type
  {$IFDEF UNICODE}
  TBuffer = TBytes;
  {$ELSE}
  TBuffer = string;
  {$ENDIF}

function OpenFile(const AName: string): THandle;
begin
  Result := CreateFile(
    PChar(AName),
    GENERIC_READ,
    FILE_SHARE_READ or FILE_SHARE_WRITE,
    nil,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL,
    0
  );
end;

function ReadFile(const AName: string): TBuffer;
var
  LFile: THandle;
  LSize: Cardinal;
  LReadSize: Cardinal;
begin
  LFile := OpenFile(AName);
  while LFile = INVALID_HANDLE_VALUE do begin
    Sleep(100);
    LFile := OpenFile(AName);
  end;
  try
    LSize := GetFileSize(AFile, nil);
    if LSize = INVALID_FILE_SIZE then
      RaiseLastOSError;
    SetLength(Result, LSize);
    Win32Check(ReadFile(AFile, Pointer(Result), LSize, @LReadSize, nil));
    if LSize <> LReadSize then
      SetLength(Result, LReadSize);
  finally
    CloseHandle(LFile);
  end;
end;

function SameBuff(const ABuf1, ABuf2: TBuffer): Boolean;
begin
  Result := 
    (Length(ABuf1) = Length(ABuf2)) and
    CompareMem(Pointer(ABuf1), Pointer(ABuf2), Length(ABuf1));
end;

procedure LoadToList(const AName: string; AList: TStrings);
var
  LBuf1: TBuffer;
  LBuf2: TBuffer;
  LCurBuf: ^TBuffer;
begin
  LBuf1 := ReadFile(AName);
  LCurBuf := @LBuf2;
  repeat
    LCurBuf^ := ReadFile(AName);
    if LCurBuf = @LBuf2 then
      LCurBuf := @LBuf1
    else
      LCurBuf := @LBuf2;
  until SameBuff(LBuf1, LBuf2);
  {$IFDEF UNICODE}
  AList.Text := TEncoding.Default.GetString(LBuf1);
  {$ELSE}
  AList.Text := LBuf1;
  {$ENDIF}
end;


В каталоге до полутора тысяч файлов разных видов которые записываются в разное время (хотя между записью/обновлением одного конкретного файла времени проходит много), такой способ интересен, но очень уж сложен :).
В общем сделаю через TFileStream.Create с флагом без ограничения доступа и обработаю исключение.
Спасибо большое за помощь.
...
Рейтинг: 0 / 0
27.11.2019, 10:43
    #39894880
alekcvp
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
Андрей Игоревич

Ну по такому сценарию файл может быт заблокирован прям в процессе чтения, тогда уж, действительно, ничего кроме исключения не спасет.
Не может. При попытке его заблокировать, блокировщик получит ошибку.
...
Рейтинг: 0 / 0
27.11.2019, 10:58
    #39894904
Василий 2
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
Андрей Игоревич

Тут ок, сделаю через TFileStream.Create(name, fmOpenRead or fmShareDenyNone ), просто удивлен, зачем его тут для записи открывают, если для записи есть SaveToFile;

Его не открывают для записи, а блокируют ОТ записи. Чтобы в процессе чтения кто-то другой не перезаписал содержимое. А вот если открывать с fmShareDenyNone, то такая возможность существует
...
Рейтинг: 0 / 0
27.11.2019, 12:03
    #39894967
ёёёёё
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
Андрей Игоревич
fmShareDenyNone )

Ну и получишь вместо блокировки неконсистентные данные.
...
Рейтинг: 0 / 0
27.11.2019, 13:30
    #39895038
Василий 2
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
Кстати, если данные только добавляются, то способ имеет право на существование. Более того, у нас крутится построенная на этом система. Накладки бывают, конечно, но в целом схема рабочая
...
Рейтинг: 0 / 0
27.11.2019, 14:14
    #39895079
white_nigger
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
Андрей Игоревич
fmShareDenyNone ), просто удивлен, зачем его тут для записи открывают
Андрей Игоревич
тут ок, сделаю через TFileStream.Create(name, fmOpenRead or fmShareDenyNone
Может стоит сначала хотя бы почитать описание флагов, прежде чем сморозить хрень? Уверен есть даже в подробностях и на русском языке
...
Рейтинг: 0 / 0
27.11.2019, 16:25
    #39895145
_Vasilisk_
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
Андрей Игоревич
_Vasilisk_
Вызвал бы ReadDirectoryChangesW() с маской, скажем FILE_NOTIFY_CHANGE_LAST_WRITE (или FILE_NOTIFY_CHANGE_LAST_ACCESS - нужно смотреть, что будет адекватнее), дождался бы окончания записи и спокойно прочитал бы файл
В каталоге до полутора тысяч файлов разных видов которые записываются в разное время
Вот вы и получите событие по каждому изменившемуся файлу, с указанием имени этого файла.
Андрей Игоревич
но очень уж сложен :).
Хозяин барин

Андрей Игоревич
В общем сделаю через TFileStream.Create с флагом без ограничения доступа и обработаю исключение.
Я описал в третьем случае, что без ограничений исключений не будет. Но можно прочитать половину файла до записи, а половину после. Именно поэтому предложил читать до тех пор, пока два последовательных прочтения не вернут одинаковый результат
...
Рейтинг: 0 / 0
27.11.2019, 18:18
    #39895209
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
Василий 2
Андрей Игоревич

Тут ок, сделаю через TFileStream.Create(name, fmOpenRead or fmShareDenyNone ), просто удивлен, зачем его тут для записи открывают, если для записи есть SaveToFile;

Его не открывают для записи, а блокируют ОТ записи. Чтобы в процессе чтения кто-то другой не перезаписал содержимое. А вот если открывать с fmShareDenyNone, то такая возможность существует

Блокировать от записи нельзя ни в коем разе, записывающая программа должна работать и трогать её нельзя (мне).
ёёёёё
Андрей Игоревич
fmShareDenyNone )

Ну и получишь вместо блокировки неконсистентные данные.

Жизнь боль, подумаю как обойти, проверки предложенные _Vasilisk_ это хорошо, но у меня только на чтении 2к строчек кода (несколько десятков разновидностей больших файлов), добавлять туда проверки дело долгое, отложу на потом :).
white_nigger
Андрей Игоревич
fmShareDenyNone ), просто удивлен, зачем его тут для записи открывают
Андрей Игоревич
тут ок, сделаю через TFileStream.Create(name, fmOpenRead or fmShareDenyNone
Может стоит сначала хотя бы почитать описание флагов, прежде чем сморозить хрень? Уверен есть даже в подробностях и на русском языке

fmOpenRead - Открытие файла только для чтения.
fmShareDenyNone — другие приложения могут производить с файлом любые операции.
???

...
white_nigger
Может стоит сначала хотя бы почитать описание флагов, прежде чем сморозить хрень? Уверен есть даже в подробностях и на русском языке
А, понял суть претензии, ну некорректно написал, о другом думал, видимо вы к фразе о fmShareDenyWrite претензии предъявляете, ну да, не для записи открывают, а блокируют для записи остальным, ниже мне пояснили почему так.

П.С. Хм, а кнопка "изменить" сообщение всегда снизу была, и я её раньше просто не видел? Да? Хм...
...
Рейтинг: 0 / 0
27.11.2019, 18:21
    #39895214
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
Дубль
...
Рейтинг: 0 / 0
27.11.2019, 18:34
    #39895218
white_nigger
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
Андрей Игоревич
суть претензии
В том, что не стоит программировать наугад, в том числе наугад выставляя флаги, не представляя что это и зачем.
...
Рейтинг: 0 / 0
27.11.2019, 19:31
    #39895242
Dimitry Sibiryakov
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
_Vasilisk_Вот вы и получите событие по каждому изменившемуся файлу, с указанием имени этого файла.

Там есть маленькая проблема со временем доставки. Я подписался на переименование файла
(поскольку моя программа пишет в .tmp файл и переименовывает его при закрытии), но
неожиданно получил shared violation error. То есть чисто подписки на изменение
недостаточно для счастья, остальные телодвижения по разведению блокировок тоже придётся
выполнить.
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
27.11.2019, 19:51
    #39895245
_Vasilisk_
Участник
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
Dimitry Sibiryakov
Я подписался на переименование файла
(поскольку моя программа пишет в .tmp файл и переименовывает его при закрытии), но
неожиданно получил shared violation error.
Кстати да. Полез в свой код, обнаружил такое
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
function TestFileAccess(const AFileName: string): Cardinal;
var
  LHandle: THandle;
begin
  LHandle := CreateFile(PChar(AFileName), GENERIC_READ, FILE_SHARE_READ, nil,
    OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
  if LHandle <> INVALID_HANDLE_VALUE then begin
    CloseHandle(LHandle);
    Result := 0;
  end else
    Result := GetLastError;
end;

.......

    LAccess := TestFileAccess(AInfo.Path + AInfo.NewFileName);
    if (AInfo.Action in [FILE_ACTION_ADDED, FILE_ACTION_MODIFIED, FILE_ACTION_RENAMED_NEW_NAME]) and
       (LAccess <> ERROR_FILE_NOT_FOUND)
    then begin
      while LAccess = ERROR_SHARING_VIOLATION do begin
        Sleep(0);
        LAccess := TestFileAccess(AInfo.Path + AInfo.NewFileName);
      end;



А подписан на события
Код: pascal
1.
FILE_NOTIFY_CHANGE_FILE_NAME or FILE_NOTIFY_CHANGE_LAST_WRITE
...
Рейтинг: 0 / 0
Форумы / Delphi [игнор отключен] [закрыт для гостей] / Корректное чтение файлов, в которые периодически заняты сторонним приложением. / 18 сообщений из 18, страница 1 из 1
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]