Страница 1 из 1

Быстрое завершение трида (потока)

Добавлено: Чт май 27, 2010 3:15 am
Max Diesel
Ситуация такая: выполняется копирование файла посредством использования TFileStream, краткий код:

Код: Выделить всё

int block_size=0xFFF; // размер блока в примере будет представлен величиной кластера по умолчанию
TFileStream *source=new TFileStream(source_filename, fmShareDenyNone);
TFileStream *dest=new TFileStream(dest_filename, fmCreate);

.. // в этом месте пропущены формальности, не имеющие отношения к делу

try {
     dest->CopyFrom(source, block_size);
    } catch (...) { }

delete source;
delete dest;
Предположим производится попытка прочитать "битый" фрагмент файла с CD/DVD, на данный момент в программе есть обработка такой ситуации (в catch), но выпадение процесса в catch производится после длительных (вероятно продолжительность зависит от привода) попыток привода прочитать дефективный кластер (то есть крупный файл вероятно после долгих мучений будет-таки прочитан "сквозь ошибки", но во-первых это займет несколько часов, а во-вторых такое мероприятие изрядно сократит жизнь приводу). Может быть есть у кого-то идеи насчет того как сделать процесс пропускания битых фрагментов более быстрым? (если есть не только идеи, но и уже готовое решение, то это было бы совсем замечательно)

Сойдемся на мысли что если приводу не удается в течение 5-ти секунд прочитать требуемый кластер, то программа должна счесть этот кластер битым и пропустить его... для реализации такой идеи я банально отправил выполнение копирования кластера в отдельный поток (трид), о факте завершенности которого можно соответственно узнать из главного потока. Таким образом главный поток через каждые 10-ть миллисекунд проверяет факт завершенности второстепенного потока и суммарно ждет его завершения в течение 5-ти секунд, после этих 5-ти секунд (то есть не дождавшись самостоятельного завершения второстепенного потока и сочтя этот кластер битым) он отправляет второстепенному потоку команду "Terminate" и переходит к копированию следующего кластера. Проблема в этом приведенном мной варианте состоит в том, что даже после Terminate тот поток еще продолжает попытки выжать из привода какие-то результаты, соответственно новая копия второстепенного потока (которая должна копировать следующий кластер) однозначно за отведенные ей 5-ть секунд даже доступа к приводу не получит со всеми вытекающими последствиями. Может быть есть у кого-то идеи насчет того как быстро и эффективно завершить тот первый второстепенный поток? [/color]

Добавлено: Чт май 27, 2010 3:19 pm
Qwertiy
А там нет ничего типа Kill()?
Насколько я представляю, нельзя корректно закрыть поток во время выполнения этой функции. Его нужно именно прервать.

Добавлено: Чт май 27, 2010 5:48 pm
Max Diesel
Непосредственно функции "Kill" нет... меня в большей степени интересует как бы быстро прервать выполняемую в TFileStream попытку копирования блока, то есть при обращении на этот TFileStream из другого потока обращающийся поток также попадает в очередь и соответственно виснет.

Добавлено: Чт май 27, 2010 6:54 pm
Qwertiy
Max Diesel писал(а):меня в большей степени интересует как бы быстро прервать выполняемую в TFileStream попытку копирования блока
Подозреваю, что никак...

А нельзя как-то убить поток (вместе с TFileStream) и создать новый (т. е. в каждом новом потоке новый TFileStream)?

Re: Быстрое завершение трида (потока)

Добавлено: Чт май 27, 2010 7:12 pm
killfactory
Не совсем в пределах топика, но Windows API при асинхронной работе с файлами предлагает операцию CancelIO:
http://www.vsokovikov.narod.ru/New_MSDN ... ncelio.htm

Это описание механизма асинхронного чтения Windows API:
http://www.vsokovikov.narod.ru/New_MSDN ... fileex.htm

Это описание механизма асинхронной записи Windows API:
http://www.vsokovikov.narod.ru/New_MSDN ... fileex.htm

Конечно, для использования этих механизмов придется отказаться от использования tFileStream, но, возможно, это тот самый путь, который позволит прервать чтение сбойного кластера на CD, не дожидаясь окончания этого процесса обычным путем. Проверить сейчас, к сожалению, нет возможности.

Добавлено: Пт май 28, 2010 3:33 am
Max Diesel
Qwertiy писал(а):А нельзя как-то убить поток (вместе с TFileStream) и создать новый (т. е. в каждом новом потоке новый TFileStream)?
Насколько мне известно, я так и сделал... но завершение потока (Terminate) судя по всему встает в очередь и виснет в ожидании (или же поток остается активным до момента пока не подойдет очередь услышать что ему отправлена команда Terminate).
killfactory писал(а):Не совсем в пределах топика, но Windows API при асинхронной работе с файлами предлагает операцию CancelIO

Конечно, для использования этих механизмов придется отказаться от использования tFileStream, но, возможно, это тот самый путь, который позволит прервать чтение сбойного кластера на CD, не дожидаясь окончания этого процесса обычным путем. Проверить сейчас, к сожалению, нет возможности.
С одной стороны я потому и упомянул об использовании TFileStream, что у меня функция копирования построена на его основе (а это вероятно самая большая функция в программе и мысль о взбирании на эверест у меня вызывает меньше страха чем мысль о значительном перестраивании этой функции), но с другой стороны вероятно есть способы использовать предложенный вариант лишь в случаях наличия нечитаемых фрагментов. Впрочем в моей реализации проверочный фрагмент кода с ReadFileEx/CancelIo почему-то желаемого результата не дает: если делать без потоков то на сбойном фрагменте вся программа подвисает и соответственно не удается даже получить с семафора сигнал о том что 5-ть секунд истекли, если же сделать с потоком и при попытках чтения сбойного фрагмента попытаться "вручную" (с кнопки на форме) выполнить CancelIo по отношению к хэндлу мучаемого в потоке файла, то ничего позитивного не происходит и программа, даже будучи уже закрытой, "удерживает" файл и "отпускает" его лишь при нажатии кнопки выброса диска на приводе. Вот фрагмент кода, о котором идет речь:

Код: Выделить всё

static void CALLBACK FileIOComplete(DWORD dwError, DWORD dwBytes, LPOVERLAPPED ovl)
{
  // не используется
}
//---------------------------------------------------------------------------

void __fastcall TForm1::Button1Click(TObject *Sender)
{
HANDLE hSem = CreateSemaphoreW(NULL, 1, 1, NULL);
LPVOID lpBuffer = HeapAlloc(GetProcessHeap(), 0, 4096);
OVERLAPPED ovl;
ovl.Offset = 264809910; // место в файле, с которого начинаются нечитаемые блоки
ovl.OffsetHigh = 0;
ovl.hEvent = hSem;
HANDLE hDataf = CreateFileW(L"n:\\badfile.dat", GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH, 0);

while (true)
{
		ReleaseSemaphore(ovl.hEvent, 1, NULL);

		ReadFileEx(hDataf, lpBuffer, 4096, &ovl, FileIOComplete);

		int s_res=WaitForSingleObjectEx(hSem, 5000, false);
		while (s_res==WAIT_IO_COMPLETION)
			{
			 s_res=WaitForSingleObjectEx(hSem, 5000, false);
			}

		if (s_res==WAIT_TIMEOUT)
			{
			 CancelIo(hDataf);
			}

		ovl.Offset += 4096;
}

CloseHandle(ovl.hEvent);

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

Re: Быстрое завершение трида (потока)

Добавлено: Пт май 28, 2010 12:47 pm
killfactory
Я теперь имел возможность попробовать, и пришел к выводу, что даже особые методики в данном случае бессильны.

Видимо, на каком-то совсем низком уровне у CD-привода есть особое право выполнять свою задачу столько, сколько ему понадобится, и никакие варианты отмены этого задания и прочие способы сообщить ему "горшочек, не вари" не проходят.

Пробовал даже совсем жесткий вариант:

Код: Выделить всё

while (true)
{
      ReleaseSemaphore(ovl.hEvent, 1, NULL);
      ReadFileEx(hDataf, lpBuffer, 4096, &ovl, FileIOComplete);
      int s_res=WaitForSingleObjectEx(hSem, 5000, false);
      if (s_res==WAIT_IO_COMPLETION)
         {
          CancelIo(hDataf);
         }
      ovl.Offset += 4096;
}
Но бенчмарк показал, что от этого реальная производительность не вырастает, все равно на попытки чтения одного блока может уходить до 30 секунд и более.

Добавлено: Пт май 28, 2010 2:08 pm
Qwertiy
Max Diesel писал(а):Насколько мне известно, я так и сделал... но завершение потока (Terminate) судя по всему встает в очередь и виснет в ожидании (или же поток остается активным до момента пока не подойдет очередь услышать что ему отправлена команда Terminate).
Я писал по аналогии с процессом, для которого есть корректное закрытие Close() и немедленное уничтожение Kill(). Судя по описанию, Terminate() это аналог Close()...
Max Diesel писал(а):Если Вам удастся исправить этот код до рабочего состояния (или если предоставите собственный вариант работающего в этом направлении кода), то я буду весьма признателен.
Есть одна идея... А именно, заменить

Код: Выделить всё

ovl.hEvent = hSem;
на независимый таймер и поместить изменение флага в процедуру

Код: Выделить всё

static void CALLBACK FileIOComplete(DWORD dwError, DWORD dwBytes, LPOVERLAPPED ovl)
Возможно, это что-то изменит...

Добавлено: Сб май 29, 2010 3:56 am
Max Diesel
killfactory писал(а):Я теперь имел возможность попробовать, и пришел к выводу, что даже особые методики в данном случае бессильны.

Видимо, на каком-то совсем низком уровне у CD-привода есть особое право выполнять свою задачу столько, сколько ему понадобится, и никакие варианты отмены этого задания и прочие способы сообщить ему "горшочек, не вари" не проходят.
Я даже попробовал программно выдвинуть лоток из привода, чтобы в такой ситуации привод наконец "отцепился" от файла, но к сожалению команда выдвигания судя по всему скромно встала в очередь...
Qwertiy писал(а):Есть одна идея... А именно, заменить

Код: Выделить всё

ovl.hEvent = hSem;
на независимый таймер и поместить изменение флага в процедуру

Код: Выделить всё

static void CALLBACK FileIOComplete(DWORD dwError, DWORD dwBytes, LPOVERLAPPED ovl)
Возможно, это что-то изменит...
Есть подозрение что это вряд ли улучшит ситуацию, я чуть выше говорил что даже использовать функцию CancelIo с нажатия кнопки, то положительного результата это не дает...

Функция CancelIo

Добавлено: Пт июн 18, 2010 8:37 pm
Qwertiy
Замечания писал(а):
Если есть какие-либо операции ввода-вывода (I/O) исполняемые в текущий момент для указанного дескриптора файла, и они были порождены вызывающим потоком, функция CancelIo отменяет их.

Обратите внимание! на то, что операции ввода-вывода (I/O) должны быть порождены как асинхронные операции. Если они не такие, то операции ввода-вывода (I/O) не возвратят значение, чтобы дать возможность потоку вызывать функцию CancelIo. Вызов функции CancelIo с дескриптором файла, который не был открыт с флажком FILE_FLAG_OVERLAPPED, не делает ничего.

Все операции ввода-вывода (I/O), которые отменены, закончат работу с ошибкой ERROR_OPERATION_ABORTED. Все уведомления о завершении операций ввода-вывода (I/O) произойдут как обычно.
Я здесь заметил несколько моментов:
1. Надо обязательно использовать флаг FILE_FLAG_OVERLAPPED при открытии файла, чего в приведённых кусках кода нет.
2. Возможно, исполняемые операции должны быть порождены тем же потоком, поэтому я не уверен, что функция должна корректно работать из обработки события нажатия кнопки.