Существует одно условие, которое удовлетворяет вашему условию «до» в цикле повторения, которое вы игнорируете, WAIT_FAILED
:
until rWait<>WAIT_TIMEOUT;
Memo1.Lines.Add('Wait done');
Поскольку время ожидания несколько ограничено, один (или более) изпотоки заканчивают работу и освобождают себя, делая один (или более) дескриптор недопустимым для следующего WaitForMultipleObjects
, что заставляет его возвращать 'WAIT_FAILED', в результате чего отображается сообщение 'Done done'.
Для каждой итерации в повторениипетля, вы должны удалить ручки готовых нитей из вашего hArr
.Кроме того, не забывайте проверять «WAIT_FAILED» в любом случае.
редактирование: Ниже приведен пример кода, показывающий, как это можно сделать.Отличие этого подхода от сохранения потоков состоит в том, что он не оставляет неиспользуемые объекты ядра и RTL.Это не имеет значения для рассматриваемого примера, но для большого количества потоков, занимающихся длительным бизнесом, это может быть предпочтительным.
В коде WaitForMultipleObjects
вызывается с передачей false для параметра bWaitAll.чтобы иметь возможность удалить дескриптор потока без использования дополнительного вызова API, чтобы выяснить, является ли он недействительным или нет.Но это допускает иное, так как код также должен иметь возможность обрабатывать потоки, заканчивающиеся вне ожидающего вызова.
procedure TForm1.Button1Click(Sender: TObject);
const
nThreads=5;
Var
tArr : Array[1..nThreads] of TFoo;
hArr : Array[1..nThreads] of THandle;
i : Integer;
rWait : Cardinal;
hCount: Integer; // total number of supposedly running threads
Flags: DWORD; // dummy variable used in a call to find out if a thread handle is valid
procedure RemoveHandle(Index: Integer); // Decrement valid handle count and leave invalid handle out of range
begin
if Index <> hCount then
hArr[Index] := hArr[hCount];
Dec(hCount);
end;
begin
Memo1.Clear;
for i:=1 to nThreads do
begin
tArr[i]:=TFoo.Create(Pi*i);
hArr[i]:=tArr[i].Handle;
end;
hCount := nThreads;
repeat
rWait:= WaitForMultipleObjects(hCount, @hArr, False, 100);
case rWait of
// one of the threads satisfied the wait, remove its handle
WAIT_OBJECT_0..WAIT_OBJECT_0 + nThreads - 1: RemoveHandle(rWait + 1);
// at least one handle has become invalid outside the wait call,
// or more than one thread finished during the previous wait,
// find and remove them
WAIT_FAILED:
begin
if GetLastError = ERROR_INVALID_HANDLE then
begin
for i := hCount downto 1 do
if not GetHandleInformation(hArr[i], Flags) then // is handle valid?
RemoveHandle(i);
end
else
// the wait failed because of something other than an invalid handle
RaiseLastOSError;
end;
// all remaining threads continue running, process messages and loop.
// don't process messages if the wait returned WAIT_FAILED since we didn't wait at all
// likewise WAIT_OBJECT_... may return soon
WAIT_TIMEOUT: Application.ProcessMessages;
end;
until hCount = 0; // no more valid thread handles, we're done
Memo1.Lines.Add('Wait done');
end;
Обратите внимание, что это ответ на вопрос так, как он задан.Я бы предпочел использовать событие TThreads OnTerminate
, чтобы уменьшить счетчик и вывести сообщение «Wait done», когда оно достигнет «0».Это или, как другие рекомендовали, перемещение ожидания в отдельный поток, было бы проще и, вероятно, чище, и избавило бы от необходимости Application.ProcessMessages
.