powered by simpleCommunicator - 2.0.61     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / Delphi [игнор отключен] [закрыт для гостей] / Корректное чтение файлов, в которые периодически заняты сторонним приложением.
18 сообщений из 18, страница 1 из 1
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
    #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
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
    #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
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
    #39894671
Kazantsev Alexey
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Квейд
так не поможет?

Так, либо не откроет, при активном писателе, либо заблокирует возможность писать на время чтения. fmShareDenyNone нужно, это разрешит чтение во время записи и не будет блокировать писателя, если он не открывает файл с эксклюзивными правами.
...
Рейтинг: 0 / 0
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
    #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
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
    #39894704
ёёёёё
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Андрей Игоревич
На всех форумах предлагают варианты с обработкой исключений, но неужели иначе нельзя. И насколько правильно обрабатывать исключения в потоках? Никаких подводных камней?

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

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

Пишущая программа мне почти недоступна, вносить туда правки сложное дело. Хотя конкретно такую правку может и договорюсь внести, вроде ничего радикального.
...
Рейтинг: 0 / 0
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
    #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
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
    #39894880
alekcvp
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Андрей Игоревич

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

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

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

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

Андрей Игоревич
В общем сделаю через TFileStream.Create с флагом без ограничения доступа и обработаю исключение.
Я описал в третьем случае, что без ограничений исключений не будет. Но можно прочитать половину файла до записи, а половину после. Именно поэтому предложил читать до тех пор, пока два последовательных прочтения не вернут одинаковый результат
...
Рейтинг: 0 / 0
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
    #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
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
    #39895214
Дубль
...
Рейтинг: 0 / 0
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
    #39895218
white_nigger
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Андрей Игоревич
суть претензии
В том, что не стоит программировать наугад, в том числе наугад выставляя флаги, не представляя что это и зачем.
...
Рейтинг: 0 / 0
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
    #39895242
Dimitry Sibiryakov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
_Vasilisk_Вот вы и получите событие по каждому изменившемуся файлу, с указанием имени этого файла.

Там есть маленькая проблема со временем доставки. Я подписался на переименование файла
(поскольку моя программа пишет в .tmp файл и переименовывает его при закрытии), но
неожиданно получил shared violation error. То есть чисто подписки на изменение
недостаточно для счастья, остальные телодвижения по разведению блокировок тоже придётся
выполнить.
Posted via ActualForum NNTP Server 1.5
...
Рейтинг: 0 / 0
Корректное чтение файлов, в которые периодически заняты сторонним приложением.
    #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
18 сообщений из 18, страница 1 из 1
Форумы / Delphi [игнор отключен] [закрыт для гостей] / Корректное чтение файлов, в которые периодически заняты сторонним приложением.
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


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