powered by simpleCommunicator - 2.0.61     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / Delphi [игнор отключен] [закрыт для гостей] / Шаблон класса для работы с потоком (WThread, Thread)
25 сообщений из 469, страница 11 из 19
Шаблон класса для работы с потоком (WThread, Thread)
    #38957492
Фотография wadman
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Проблема в TEvent, который для wince. Срабатывает до 12 раз в секунду по сигналу и ни о каком таймауте и речи нет.
Подумаю, как обойти...
...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #38957603
Фотография wadman
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Собственно адаптировал под wince.

wthread.pas
Код: 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.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
261.
262.
263.
264.
265.
266.
267.
268.
269.
270.
271.
272.
273.
274.
275.
276.
277.
278.
279.
280.
281.
282.
283.
284.
285.
286.
287.
288.
289.
290.
291.
292.
293.
294.
295.
296.
297.
298.
299.
300.
301.
302.
303.
304.
305.
306.
307.
308.
309.
310.
311.
312.
313.
314.
315.
316.
317.
318.
319.
320.
321.
322.
323.
324.
325.
326.
327.
328.
329.
330.
331.
332.
333.
334.
335.
336.
337.
338.
339.
340.
341.
342.
343.
344.
345.
346.
347.
348.
349.
350.
351.
352.
353.
354.
355.
356.
357.
358.
359.
360.
361.
362.
363.
364.
365.
366.
367.
368.
369.
370.
371.
372.
373.
374.
375.
376.
377.
378.
379.
380.
381.
382.
383.
384.
385.
386.
387.
388.
389.
390.
391.
392.
393.
394.
395.
396.
397.
398.
399.
400.
401.
402.
403.
404.
405.
406.
407.
408.
409.
410.
411.
412.
413.
414.
415.
416.
417.
418.
419.
420.
421.
422.
423.
424.
425.
426.
427.
428.
429.
430.
431.
432.
433.
434.
435.
436.
437.
438.
439.
440.
441.
442.
443.
444.
445.
446.
447.
448.
449.
450.
451.
452.
453.
454.
455.
456.
457.
458.
459.
460.
461.
462.
463.
464.
465.
466.
467.
468.
469.
470.
471.
472.
473.
474.
475.
476.
477.
478.
479.
480.
481.
482.
483.
484.
485.
486.
487.
488.
489.
490.
491.
492.
493.
494.
495.
496.
497.
498.
499.
500.
501.
502.
503.
504.
505.
506.
507.
508.
509.
510.
511.
512.
513.
514.
515.
516.
517.
518.
519.
520.
521.
522.
523.
524.
525.
526.
527.
528.
529.
530.
531.
532.
533.
534.
535.
536.
537.
538.
539.
540.
541.
542.
543.
544.
545.
546.
547.
548.
549.
550.
551.
552.
553.
554.
555.
556.
557.
558.
559.
560.
561.
562.
563.
564.
565.
566.
567.
568.
569.
570.
571.
572.
573.
574.
575.
576.
577.
578.
579.
580.
581.
582.
583.
584.
585.
586.
587.
588.
589.
590.
591.
592.
593.
594.
595.
596.
597.
598.
599.
600.
601.
602.
603.
604.
605.
606.
607.
608.
609.
610.
611.
612.
613.
614.
615.
616.
617.
618.
619.
620.
621.
622.
623.
624.
625.
626.
627.
628.
629.
630.
631.
632.
633.
634.
635.
636.
637.
638.
639.
640.
641.
642.
643.
644.
645.
646.
647.
648.
649.
650.
651.
652.
653.
654.
655.
656.
657.
658.
659.
660.
661.
662.
663.
664.
665.
666.
667.
668.
669.
670.
671.
672.
673.
674.
675.
676.
677.
678.
679.
680.
681.
682.
683.
684.
685.
686.
687.
688.
689.
690.
691.
692.
693.
694.
695.
696.
697.
698.
699.
700.
701.
702.
703.
704.
705.
706.
707.
708.
709.
710.
711.
712.
713.
714.
715.
716.
717.
718.
719.
720.
721.
722.
723.
724.
725.
726.
727.
728.
729.
730.
731.
732.
733.
734.
735.
736.
737.
738.
739.
740.
741.
742.
743.
744.
745.
746.
747.
748.
749.
750.
751.
752.
753.
754.
755.
756.
757.
758.
759.
760.
761.
762.
763.
764.
765.
766.
767.
768.
769.
770.
771.
772.
773.
774.
775.
776.
777.
778.
779.
780.
781.
782.
783.
784.
785.
786.
787.
788.
789.
790.
791.
792.
793.
794.
795.
796.
797.
798.
799.
800.
801.
802.
803.
804.
805.
806.
807.
808.
809.
810.
811.
812.
813.
814.
815.
816.
817.
818.
819.
820.
821.
822.
823.
824.
825.
826.
827.
828.
829.
830.
831.
832.
833.
834.
835.
836.
837.
838.
839.
840.
841.
842.
843.
844.
845.
846.
847.
848.
849.
850.
851.
852.
853.
854.
855.
856.
857.
858.
859.
860.
861.
862.
863.
864.
865.
unit wthread;
// модуль для работы с доп.потоками Delphi&Lazarus (win&wince&linux)
// позволяет "общаться" дополнительному и основному потокам посредством очереди сообщений
// (c) wadman 2013-2015, версия от 13.05.2015
//
// использование:
// 1. Создать наследника с объявленными обработчиками сообщений
//  const WM_TEST_PROC = WM_THREAD_BASE + 1;
//  TMyThread = class(TWThread)
//     procedure WMTestProc(var Msg: TThreadMessage); message WM_TEST_PROC;
//  данные процедуры будут выполняться в доп.потоке путем отправки связанного с ними сообщения в поток
//  см. PostToThreadMessage
// 2. Присвоить форме обработчика(ов) события OnThreadReceiveMessage (OnTimeOut - при необходимости)
//
// Для обмена строками рекомендуется использовать функции NewString и FreeString
//
// Для корректной остановки потока и дальнейшей работы программы должна использоваться процедура StopThrad
// При компиляции под *nix в файле проекта до раздела USES вставить строку строку {$DEFINE UseCThreads}
{$IFDEF FPC}
{$mode objfpc}{$H+}
    {$IFDEF WINDOWS}
        {$DEFINE FPC_WIN}
        {$DEFINE ALL_WIN}
    {$ELSE}
        {$DEFINE FPC_NIX}
    {$ENDIF}
{$ELSE}
    {$DEFINE WIN}
    {$DEFINE ALL_WIN}
{$ENDIF}

// для передачи строк между потоками используется выделенная память
{$DEFINE ALLOC_STRING}
// добавлять отладочную информацию с использованием модуля wlog
{.$DEFINE WTHREAD_DEBUG_LOG}

interface

uses
    SysUtils,
    {$IFDEF WTHREAD_DEBUG_LOG}
     wlog,
    {$ENDIF}
    {$IFDEF UNIX} // добавить эти строки в модуль проекта при разработке не под windows
        //cthreads,
        //cmem,
    {$ENDIF}
    Classes
    {$IFNDEF FPC}
    ,Messages
    {$ENDIF}
    ,contnrs
    {$IFDEF ALL_WIN}
    ,Windows
    {$ENDIF}
    ,SyncObjs
    ;

const
    {$IFDEF FPC}
    //INFINITE            = Cardinal($FFFFFFFF);
    {$ENDIF}
    WM_USER             = $400;
    WM_THREAD_BASE      = WM_USER + $110;
    WM_THREAD_MAX       = WM_USER + $300;

type
    PThreadMessage = ^TThreadMessage;
    TThreadMessage = record
        {$IFDEF FPC}
        Message: DWord;
        {$ELSE}
        Message: Word;
        {$ENDIF}
        WParam: Word;
        LParam: NativeInt;
    end;

    TWEvent = class(TEvent)
    protected
        FWHandle: THandle;
    public
        constructor Create; overload;
        destructor Destroy; override;
        procedure WSetEvent;
        function WWaitFor(const TimeOut: Cardinal): TWaitResult;
    end;


    TWThread = class;

        // событие на получение сообщения из потока
    TWThreadReceiveMessage = procedure(Sender: TWThread; var Msg: TThreadMessage) of object;
        // событие из потока на таймаут
    TWThreadTimeOut = procedure(Sender: TWThread) of object;

    { TWThread }

    TWThread = class(TThread)
    private
        {$IFDEF FPC}
        FQueue: TQueue;
        FSection: TCriticalSection;
        FMessageEvent: TWEvent;
        FGUIThread: TThread;
        {$ELSE}
        FQueueReady: boolean;
        FQueueMessages: array of TThreadMessage;
        FToolWindow: THandle;
        hCloseEvent: THandle;
        {$ENDIF}
        FOnThreadReceiveMessage: TWThreadReceiveMessage;
        FOnTimeOut: TWThreadTimeOut;
        FTimeOutIsDirect: boolean;
        {$IFDEF WTHREAD_DEBUG_LOG}
        FUseDebugLog: boolean;
        function intPostToLog(const Text: string): boolean; overload;
        function intPostToLog(const Text: string; const Level: TLogLevel): boolean; overload;
        {$ENDIF}
        function GetMsFromDateTime(const Value: TDateTime): Cardinal;
        procedure SetTimeOut(const Value: Cardinal);
    protected
        FTimeOut: Cardinal;
        {$IFNDEF FPC}
        FWindowHandle: THandle;
        procedure WWindowProc(var Msg: TMessage);
        procedure GetWindow;
        procedure FreeWindow;
        {$ENDIF}
            // отправка сообщения из этого потока для вызова обработчика OnThreadReceiveMessage
        procedure PostMessageFromThread(const Msg: Word; const WParam: Word; const LParam: NativeInt);
        procedure DoThreadReceiveMessage(const Msg: Word; const WParam: Word; const LParam: NativeInt);
        procedure DoTimeout;
        procedure Execute; override;
        function GetTimeOut: Cardinal; virtual;
    public
        constructor Create
        {$IFDEF WTHREAD_DEBUG_LOG}
                (AUseDebugLog: Boolean)
        {$ENDIF}
            ; overload;
        constructor Create(CreateSuspended
        {$IFDEF WTHREAD_DEBUG_LOG}
                , AUseDebugLog
        {$ENDIF}
                : Boolean); overload;
        destructor Destroy; override;
            // Две процедуры, которые выполняются в контексте потока. В начале и в конце.
        procedure InitThread; virtual;
        procedure DoneThread; virtual;
            // см TimeOutIsDirect, при true - перекрыть следующую процедуру
        procedure DirectTimeOut; virtual;
            // отправка любого сообщения В этот поток
        function PostToThreadMessage(const Msg: Word; const WParam: Word; const LParam: NativeInt): Boolean;
            // остановка потока по феншую
        procedure StopThread;
            // событие на получение данных ИЗ этого потока, вызывается в основном потоке
        property OnThreadReceiveMessage: TWThreadReceiveMessage read FOnThreadReceiveMessage write FOnThreadReceiveMessage;
            // событие, возникающее при превышении интервала ожидания
        property OnTimeOut: TWThreadTimeOut read FOnTimeOut write FOnTimeOut;
            // интервал ожидания в мс, по-умолчанию - бесконечно
        property TimeOut: Cardinal read GetTimeOut write SetTimeOut default INFINITE;
            // true = Будет вызван DirectTimeOut из этого потока, false = вызвано событие OnTimeOut в основном потоке.
        property TimeOutIsDirect: boolean read FTimeOutIsDirect write FTimeOutIsDirect default false;
        {$IFDEF WTHREAD_DEBUG_LOG}
        property UseDebugLog: boolean read FUseDebugLog write FUseDebugLog default false;
        {$ENDIF}
    end;

    // подготовка строки к обмену между потоками
function NewString(const Text: string): NativeInt;

    // возвращение строки к привычному виду после приема из другого потока
function FreeString(var P: NativeInt): String;

implementation

{$IFNDEF UNIX}
//uses Windows;
{$ENDIF}

const
    {$IFNDEF FPC}
    WM_THREAD_READY     = WM_USER+$102;
    {$ENDIF}
    WM_TIMEOUT          = WM_USER+$101;

    SizeOfChar          = SizeOf(Char);

    // константы из DateUtils
    OneMillisecond      = 1 / MSecsPerDay;

{$IFDEF ALLOC_STRING}
{$IFDEF ALL_WIN}
// для передачи строки между программами
function GlobalNewString(const Text: string): NativeInt;
var l: Integer;
    p: pointer;
begin
    l := Length(Text)*SizeOfChar;
    if L > SizeOfChar then begin
        Result := GlobalAlloc(GHND, l+SizeOfChar);
        if LongBool(Result) then begin
            {$HINTS OFF}
            p := GlobalLock(Result);
            {$HINTS ON}
            Move(Pointer(Text)^, Pointer(p)^, l);
            GlobalUnlock(Result);
        end
    end else
        Result := 0;
end;

// для передачи строки между программами
function GlobalFreeString(var P: NativeInt): String;
var ps: pointer;
begin
    if LongBool(P) then begin
        {$HINTS OFF}
        ps := GlobalLock(P);
        {$HINTS ON}
        SetLength(Result, Length(PChar(ps)));
        Move(Pointer(ps)^, Pointer(Result)^, Length(Result)*SizeOfChar);
        GlobalUnlock(P);
        P := GlobalFree(P);
    end;
end;
{$ENDIF}

// для передачи строки в пределах одной программы
function FreeString(var P: NativeInt): String;
begin
    {$HINTS OFF}
    if LongBool(P) then begin
        SetLength(Result, Length(PChar(P)));
        Move(Pointer(P)^, Pointer(Result)^, Length(Result)*SizeOfChar);
        {$IFDEF ALL_WIN}
        P := LocalFree(HLOCAL(P));
        {$ELSE}
        P := FreeMem(Pointer(P));
        {$ENDIF}
    end;
end;

// для передачи строки в пределах одной программы
function NewString(const Text: string): NativeInt;
var l: Integer;
begin
    l := Length(Text)*SizeOfChar;
    if L > SizeOfChar then begin
        {$IFDEF ALL_WIN}
        Result := LocalAlloc(LPTR, l+SizeOfChar);
        {$ELSE}
        Pointer(Result) := AllocMem(l+SizeOfChar);
        {$ENDIF}
        if LongBool(Result) then
            Move(Pointer(Text)^, Pointer(Result)^, l);
        {$HINTS ON}
    end else
        Result := 0;
end;
{$ELSE}
function NewString(const Text: string): NativeInt;
begin
    Result := 0;
    string(Result) := Text;
end;

function FreeString(var P: NativeInt): String;
begin
    Result := '';
    NativeInt(Result) := P;
end;
{$ENDIF}

constructor TWEvent.Create;
begin
    {$IFDEF WINCE}
    FWHandle := CreateEvent(nil, false, false, nil);
    {$ELSE}
    inherited Create(nil, false, false, '');
    {$ENDIF}
end;

destructor TWEvent.Destroy;
begin
    {$IFDEF WINCE}
    CloseHandle(FWHandle);
    {$ELSE}
    inherited Create(nil, false, false, '');
    {$ENDIF}
end;

procedure TWEvent.WSetEvent;
begin
    {$IFDEF WINCE}
    Windows.SetEvent(FWHandle);
    {$ELSE}
    inherited SetEvent;
    {$ENDIF}
end;

function TWEvent.WWaitFor(const TimeOut: Cardinal): TWaitResult;
{$IFDEF WINCE}
var
    dw: DWord;
begin
    dw := WaitForSingleObject(FWHandle, TimeOut);
    case dw of
        WAIT_ABANDONED: result := wrAbandoned;
        WAIT_OBJECT_0: result := wrSignaled;
        WAIT_TIMEOUT: result := wrTimeout;
    else
        Result := wrError;
    end;
{$ELSE}
begin
    result := WaitFor(TimeOut);
{$ENDIF}
end;

{$IFDEF FPC}
type

    { TGUIThread }

    TGUIThread = class(TThread)
    private
        FMessageEvent: TWEvent;
        FTimeOut: Boolean;
        FOwner: TWThread;
        FQueue: TQueue;
        FSection: TCriticalSection;
        FCurrentMessage: TThreadMessage;
    protected
        procedure Execute; override;
        procedure CallGUIThread;
    public
        constructor Create(AOwner: TWThread); overload;
        destructor Destroy; override;
        procedure PostMessage(const Message: DWord; const WParam: Word; const LParam: NativeInt);
        procedure StopThread;
    end;

{ TGUIThread }

procedure FreeQueue(const Queue: TQueue);
begin
    while Queue.Count > 0 do
        Queue.Pop;
end;

procedure TGUIThread.Execute;
var Message: PThreadMessage;
    wr: TWaitResult;
begin
    while not Terminated do begin
        wr := FMessageEvent.WWaitFor(INFINITE);
        if (not Terminated) and (wr = wrSignaled) then begin
            if FTimeOut then begin
                FTimeOut := False;
                if Assigned(FOwner.FOnTimeOut) then
                    FOwner.FOnTimeOut(FOwner);
            end;
            while FQueue.Count > 0 do begin
                FSection.Enter;
                Message := FQueue.Pop;
                FSection.Leave;
                FCurrentMessage := Message^;
                FreeMem(Message);
                if (FCurrentMessage.Message >= WM_THREAD_BASE)
                    and(FCurrentMessage.Message <= WM_THREAD_MAX) then
                    Synchronize(@CallGUIThread);
            end;
        end;
    end;
end;

procedure TGUIThread.CallGUIThread;
begin
    if Assigned(FOwner) then
        FOwner.DoThreadReceiveMessage(FCurrentMessage.Message, FCurrentMessage.WParam, FCurrentMessage.LParam);
end;

constructor TGUIThread.Create(AOwner: TWThread);
begin
    inherited Create(False);
    FMessageEvent := TWEvent.Create;
    FQueue := TQueue.Create;
    FSection := TCriticalSection.Create;
    FOwner := AOwner;
end;

destructor TGUIThread.Destroy;
begin
    FreeQueue(FQueue);
    FSection.Free;
    FQueue.Free;
    FMessageEvent.Free;
    inherited Destroy;
end;

procedure TGUIThread.PostMessage(const Message: DWord; const WParam: Word; const LParam: NativeInt);
var Msg: PThreadMessage;
begin
    GetMem(Msg, SizeOf(TThreadMessage));
    Msg^.Message := Message;
    Msg^.WParam := WParam;
    Msg^.LParam := LParam;
    FSection.Enter;
    FQueue.Push(Msg);
    FSection.Leave;
    FMessageEvent.WSetEvent;
end;

procedure TGUIThread.StopThread;
begin
    Terminate;
    FMessageEvent.WSetEvent;
end;
{$ENDIF}

{ TWThread }

constructor TWThread.Create(CreateSuspended
        {$IFDEF WTHREAD_DEBUG_LOG}
        , AUseDebugLog
        {$ENDIF}
        : Boolean);
begin
    inherited Create(CreateSuspended);
    {$IFDEF WTHREAD_DEBUG_LOG}
    FUseDebugLog := AUseDebugLog;
    {$ENDIF}
    {$IFDEF FPC}
    FQueue := TQueue.Create;
    FMessageEvent := TWEvent.Create;
    FSection := TCriticalSection.Create;
    FGUIThread := TGUIThread.Create(Self);
    {$ELSE}
    hCloseEvent := CreateEvent(nil, false, false, nil);
    GetWindow;
    FWindowHandle := 0;
    FQueueReady := False;
    {$ENDIF}
    FTimeOut := INFINITE;
    FTimeOutIsDirect := False;
end;

constructor TWThread.Create
    {$IFDEF WTHREAD_DEBUG_LOG}
    (AUseDebugLog: Boolean)
    {$ENDIF}
    ;
begin
    Create(false
        {$IFDEF WTHREAD_DEBUG_LOG}
        , AUseDebugLog
        {$ENDIF}
        );
end;

destructor TWThread.Destroy;
begin
    {$IFDEF FPC}
    FMessageEvent.Free;
    FreeQueue(FQueue);
    FQueue.Free;
    FGUIThread.Free;
    FSection.Free;
    {$ELSE}
    CloseHandle(hCloseEvent);
    {$ENDIF}
    inherited;
end;

procedure TWThread.DirectTimeOut;
begin
    // override
end;

// Процедура, выполняющая в контексте этого потока перед запуском очереди сообщений (в начале работы потока).
procedure TWThread.InitThread;
begin
    // empty
end;

// Процедура, выполняющая в контексте этого потока после отработки очереди сообщений (в конце работы потока).
procedure TWThread.DoneThread;
begin
    // empty
end;

procedure TWThread.DoThreadReceiveMessage(const Msg: Word; const WParam: Word; const LParam: NativeInt);
var ThreadMsg: TThreadMessage;
begin
    if Assigned(FOnThreadReceiveMessage) then begin
        ThreadMsg.Message := Msg;
        ThreadMsg.WParam := WParam;
        ThreadMsg.LParam := LParam;
        FOnThreadReceiveMessage(Self, ThreadMsg);
    end;
end;

procedure TWThread.DoTimeout;
begin
    {$IFDEF FPC}
    TGUIThread(FGUIThread).FTimeOut := true;
    TGUIThread(FGUIThread).FMessageEvent.WSetEvent;
    {$ELSE}
    if Assigned(FOnTimeOut) then
        FOnTimeOut(Self);
    {$ENDIF}
end;

// отправка любого сообщения В этот поток
function TWThread.PostToThreadMessage(const Msg: Word; const WParam: Word;
    const LParam: NativeInt): Boolean;
{$IFDEF FPC}
var PMsg: PThreadMessage;
begin
    GetMem(PMsg, SizeOf(TThreadMessage));
    PMsg^.Message := Msg;
    PMsg^.WParam := WParam;
    PMsg^.LParam := LParam;
    FSection.Enter;
    FQueue.Push(PMsg);
    FSection.Leave;
    {$IFDEF WTHREAD_DEBUG_LOG}
    intPostToLog(Format('Thread %d : send TO thread msg: %d (%1:x).', [Handle, Msg]), WLL_EXTRA);
    {$ENDIF}
    FMessageEvent.WSetEvent;
    result := true;
{$ELSE}
begin
    if FQueueReady then begin
        // если очередь сообщений создана и поток инициализирован, то сразу отправляем
        result := (not Suspended)and(PostThreadMessage(ThreadID, Msg, wParam, lParam));
    end else begin
        // иначе кэшируем
        SetLength(FQueueMessages, Length(FQueueMessages)+1);
        FQueueMessages[High(FQueueMessages)].Message := Msg;
        FQueueMessages[High(FQueueMessages)].WParam := WParam;
        FQueueMessages[High(FQueueMessages)].LParam := LParam;
        {$IFDEF WTHREAD_DEBUG_LOG}
        intPostToLog(Format('Thread %d : send TO thread msg: %d (%1:x).', [Handle, Msg]), WLL_EXTRA);
        {$ENDIF}
        result := True;
    end;
{$ENDIF}
end;

{$IFDEF WTHREAD_DEBUG_LOG}
{$B-}
function TWThread.intPostToLog(const Text: string): boolean;
begin
    result := FUseDebugLog and PostToLog(Text);
end;

function TWThread.intPostToLog(const Text: string; const Level: TLogLevel): boolean;
begin
    result := FUseDebugLog and PostToLog(Text, Level);
end;
{$ENDIF}

function TWThread.GetMsFromDateTime(const Value: TDateTime): Cardinal;
begin
    Result := Round((Now - Value) / OneMillisecond);
end;

procedure TWThread.SetTimeOut(const Value: Cardinal);
begin
    {$IFDEF FPC}
    if FTimeOut <> Value then begin
        if (Value = 0) then
            FTimeOut := INFINITE
        else
            FTimeOut := Value;
        if not Suspended then
            FMessageEvent.WSetEvent;
    end;
    {$ELSE}
    if FTimeOut <> Value then begin
        if (Value = 0) then
            FTimeOut := INFINITE
        else
            FTimeOut := Value;
        if (not Suspended)and(GetCurrentThreadId <> ThreadID) then
            PostThreadMessage(ThreadID, WM_TIMEOUT, Value, 0);
    end;
    {$ENDIF}
end;

procedure TWThread.Execute;
var
    internalTimeout, ms: Cardinal;
    busy: TDateTime;
{$IFDEF FPC}
    Message: PThreadMessage;
    wr: TWaitResult;
begin
    {$IFDEF WTHREAD_DEBUG_LOG}
    intPostToLog(Format('Thread %d : started.', [Handle]));
    {$ENDIF}
    InitThread;
    internalTimeout := TimeOut;
    busy := 0;
    {$IFDEF WTHREAD_DEBUG_LOG}
    intPostToLog(Format('Thread %d : main loop started.', [Handle]), WLL_EXTRA);
    {$ENDIF}
    while not Terminated do begin
        wr := FMessageEvent.WWaitFor(internalTimeout);
        busy := Now;
        if not Terminated then
            case WR of
                wrSignaled: begin
                    {$IFDEF WTHREAD_DEBUG_LOG}
                    intPostToLog(Format('Thread %d : got signal (queue cnt: %d).', [Handle, Integer(wr), FQueue.Count]), WLL_EXTRA);
                    {$ENDIF}
                    while FQueue.Count > 0 do begin
                        FSection.Enter;
                        Message := FQueue.Pop;
                        FSection.Leave;
                        if (Message^.message >= WM_THREAD_BASE)
                            and (Message^.Message <= WM_THREAD_MAX) then begin
                                {$IFDEF WTHREAD_DEBUG_LOG}
                                intPostToLog(Format('Thread %d : dispatch msg %d (%1:x).', [Handle, Message^.Message]), WLL_EXTRA);
                                {$ENDIF}
                                Dispatch(Message^);
                            end;
                        FreeMem(Message);
                    end;
                end;
                wrTimeout: begin
                    internalTimeout := TimeOut;
                    if FTimeOutIsDirect then begin
                        {$IFDEF WTHREAD_DEBUG_LOG}
                        intPostToLog(Format('Thread %d : DirectTimeOut.', [Handle]), WLL_EXTRA);
                        {$ENDIF}
                        DirectTimeOut;
                    end else begin
                        {$IFDEF WTHREAD_DEBUG_LOG}
                        intPostToLog(Format('Thread %d : Sent TimeOut.', [Handle]), WLL_EXTRA);
                        {$ENDIF}
                        PostMessageFromThread(WM_TIMEOUT, 0, 0);
                        // сообщение не занимает много времени, на точность не влияет
                        busy := 0;
                    end;
                end;
            end;
        // корректируем интервал таймаута на время выполнения кода выше
        if (busy <> 0) then begin
            ms := GetMsFromDateTime(busy);
            if (ms > TimeOut) then
                internalTimeout := 0
            else
                internalTimeout := TimeOut - ms;
        end else begin
            internalTimeout := TimeOut;
        end;
    end;
{$ELSE}
    message: TThreadMessage;
    HandlesToWaitFor: array [0 .. 0] of THandle;
    dwHandleSignaled: DWORD;
    msg: TMsg;

begin
    {$IFDEF WTHREAD_DEBUG_LOG}
    intPostToLog(Format('Thread %d : started.', [Handle]));
    {$ENDIF}
    // следующая строка должна быть всегда первой в процедуре, т.к. запускает очередь сообщений для потока
    {$HINTS OFF}
    PeekMessage(msg, THandle(-1), WM_USER, WM_USER, PM_NOREMOVE);
    {$HINTS ON}
    internalTimeout := TimeOut;
    busy := 0;
    InitThread;
    HandlesToWaitFor[0] := hCloseEvent;
    PostMessageFromThread(WM_THREAD_READY, 0, 0);

    {$IFDEF WTHREAD_DEBUG_LOG}
    intPostToLog(Format('Thread %d : main loop started.', [Handle]), WLL_EXTRA);
    {$ENDIF}
    while not Terminated do begin
        if ((FWindowHandle = 0) and(not PeekMessage(msg, THandle(-1), WM_USER, WM_THREAD_MAX, PM_REMOVE)))
            or((FWindowHandle <> 0) and (
                (not PeekMessage(msg, FWindowHandle, 0, 0, PM_REMOVE))
                and
                (not PeekMessage(msg, THandle(-1), WM_USER, WM_THREAD_MAX, PM_REMOVE))
            )) then begin

            if (FWindowHandle = 0) then
                dwHandleSignaled := MsgWaitForMultipleObjects(1, HandlesToWaitFor, false, internalTimeout, QS_ALLINPUT)
            else
                dwHandleSignaled := MsgWaitForMultipleObjects(1, HandlesToWaitFor, false, internalTimeout, QS_ALLEVENTS);
            busy := Now;
            case dwHandleSignaled of
                WAIT_OBJECT_0: begin // выставлено событие остановки потока
                    if not FreeOnTerminate then
                        Terminate;
                    break;
                end;
                WAIT_OBJECT_0 + 1: begin // получено сообщение
                    Continue;
                end;
                WAIT_TIMEOUT: begin
                    if FTimeOutIsDirect then begin
                        {$IFDEF WTHREAD_DEBUG_LOG}
                        intPostToLog(Format('Thread %d : DirectTimeOut.', [Handle]), WLL_EXTRA);
                        {$ENDIF}
                        DirectTimeOut;
                    end else begin
                        {$IFDEF WTHREAD_DEBUG_LOG}
                        intPostToLog(Format('Thread %d : Sent TimeOut.', [Handle]), WLL_EXTRA);
                        {$ENDIF}
                        PostMessageFromThread(WM_TIMEOUT, 0, 0);
                        // сообщение не занимает много времени, на точность не влияет
                        busy := 0;
                    end;
                    // выставляем новый интервал за минусом времени выполнения процедуры таймаута
                    if (busy <> 0) then begin
                        ms := GetMsFromDateTime(busy);
                        if (ms > TimeOut) then
                            internalTimeout := 0
                        else
                            internalTimeout := TimeOut - ms;
                    end else begin
                        internalTimeout := TimeOut;
                    end;
                    Continue;
                end;
            end;
        end;

        if Terminated then break;

        if LongBool(FWindowHandle) and (FWindowHandle = msg.hwnd) then begin
            // хэндл окна присвоен и пришло сообщение для окна
            TranslateMessage(msg);
            DispatchMessage(msg);
        end else case msg.message of
            WM_TIMEOUT: begin
                // изменен таймаут
                FTimeOut := msg.wParam;
                busy := 0;
            end;
            WM_THREAD_BASE..WM_THREAD_MAX: begin
                message.Message := Msg.message;
                message.WParam := Msg.wParam;
                message.LParam := Msg.lParam;
                {$IFDEF WTHREAD_DEBUG_LOG}
                intPostToLog(Format('Thread %d : dispatch msg %d (%1:x).', [Handle, Message^.Message]), WLL_EXTRA);
                {$ENDIF}
                // ищем и вызываем procedure of object message
                Dispatch(message);
            end;
        end;

        // корректируем интервал таймаута на время выполнения кода выше
        if (busy <> 0) then begin
            ms := GetMsFromDateTime(busy);
            if (ms > TimeOut) then
                internalTimeout := 0
            else
                internalTimeout := TimeOut - ms;
        end else begin
            internalTimeout := TimeOut;
        end;
    end;
{$ENDIF}
    DoneThread;
    {$IFDEF WTHREAD_DEBUG_LOG}
    intPostToLog(Format('Thread %d : main loop stoped.', [Handle]), WLL_EXTRA);
    {$ENDIF}
end;

// отправка сообщения из этого потока для вызова обработчика OnThreadReceiveMessage
procedure TWThread.PostMessageFromThread(const Msg: Word; const WParam: Word; const LParam: NativeInt);
begin
{$IFDEF FPC}
    if Assigned(FGUIThread) then begin
        TGUIThread(FGUIThread).PostMessage(Msg, WParam, LParam);
    {$IFDEF WTHREAD_DEBUG_LOG}
        intPostToLog(Format('Thread %d : send FROM thread msg: %d (%1:x).', [Handle, Msg]), WLL_EXTRA);
    {$ENDIF}
    end
    {$ELSE}
    if FToolWindow <> 0 then begin
        PostMessage(FToolWindow, Msg, WParam, LParam);
        {$IFDEF WTHREAD_DEBUG_LOG}
        intPostToLog(Format('Thread %d : send FROM thread msg: %d (%1:x).', [Handle, Msg]), WLL_EXTRA);
        {$ENDIF}
    end
{$ENDIF}
    {$IFDEF WTHREAD_DEBUG_LOG}
    else
        intPostToLog(Format('Thread %d : cant send FROM thread msg: %d (%1:x).', [Handle, Msg]), WLL_EXTRA);
    {$ENDIF}
end;

// отправка любого сообщения В этот поток
procedure TWThread.StopThread;
begin
    {$IFDEF FPC}
    TGUIThread(FGUIThread).StopThread;
    Terminate;
    FMessageEvent.WSetEvent;
    {$ELSE}
    FreeWindow;
    Terminate;
    SetEvent(hCloseEvent);
    //SleepEx(1, false);
    //result := WaitForSingleObject(Handle, 10000) <> WAIT_TIMEOUT;
    {$ENDIF}
    {$IFDEF WTHREAD_DEBUG_LOG}
    intPostToLog(Format('Thread %d : stop signal.', [Handle]));
    {$ENDIF}
end;

function TWThread.GetTimeOut: Cardinal;
begin
    Result := FTimeOut;
end;

{$IFNDEF FPC}

procedure TWThread.WWindowProc(var Msg: TMessage);
var i: integer;
begin
    case Msg.Msg of
        WM_TIMEOUT: begin
            DoTimeout;
            Msg.Result := 1;
        end;
        WM_THREAD_READY: begin
            FQueueReady := true;
            // поток готов, отправляем ему все закэшированные сообщения
            if Length(FQueueMessages) > 0 then for i := Low(FQueueMessages) to High(FQueueMessages) do
                PostToThreadMessage(FQueueMessages[i].Message, FQueueMessages[i].WParam, FQueueMessages[i].LParam);
            SetLength(FQueueMessages, 0);
        end;
        WM_THREAD_BASE..WM_THREAD_MAX: begin
            DoThreadReceiveMessage(Msg.Msg, Msg.WParam, Msg.LParam);
            Msg.Result := 1;
        end
    else  if LongBool(FToolWindow) then
        Msg.Result := DefWindowProc(FToolWindow, Msg.Msg, Msg.wParam, Msg.lParam);
    end;
end;

procedure TWThread.GetWindow;
begin
    FToolWindow := AllocateHWnd(WWindowProc);
end;

procedure TWThread.FreeWindow;
begin
    DeallocateHWnd(FToolWindow);
    FToolWindow := 0;
end;

{$ENDIF}

end.



wlog.pas
Код: 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.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
unit wlog;
// wlog (c) wadman, 2015, 13.05.2015
// multithread safe logging

interface

// log level
type
    TLogLevel = (WLL_MINIMUM, WLL_NORMAL, WLL_MAXIMUM, WLL_EXTRA);

var WLogFileName: string;
    WLogEnabled: boolean;
    WLogLevel: TLogLevel;

function PostToLog(const Text: string): boolean; overload; // with WLL_NORMAL
function PostToLog(const Text: string; const Level: TLogLevel): boolean; overload;

implementation

uses SysUtils, WThread;

const
    WM_LOG      = WM_THREAD_BASE + 1;

type
    PLogRecord = ^TLogRecord;
    TLogRecord = record
        DT: TDateTime;
        PString: NativeInt;
    end;

    TLogThread = class(TWThread)
    private
        procedure WMLog(var Msg: TThreadMessage); message WM_LOG;
    end;

var
    LogThread: TLogThread;
    FirstLine: boolean;

function PostToLog(const Text: string): boolean;
begin
    result := PostToLog(Text, WLL_NORMAL);
end;

function PostToLog(const Text: string; const Level: TLogLevel): boolean;
var p: PLogRecord;
    d: TDateTime;
begin
    if SmallInt(Level) <= SmallInt(WLogLevel) then begin
        result := WLogEnabled and LongBool(LogThread);
        if result then begin
            d := Now;
            p := AllocMem(SizeOf(TLogRecord));
            p^.DT := d;
            p^.PString := NewString(Text);
{$HINTS OFF}
            result := LogThread.PostToThreadMessage(WM_LOG, 0, NativeInt(p));
{$HINTS ON}
            if not result then begin
                FreeString(p^.PString);
                FreeMem(p);
            end;
        end;
    end else
        Result := true;
end;

function InitLogs: boolean;
begin
    WLogFileName := ChangeFileExt(ParamStr(0), '.log');
    LogThread := TLogThread.Create(false);
    result := LongBool(LogThread);
end;

procedure DoneLogs;
begin
    if LongBool(LogThread) then begin
        LogThread.StopThread;
        LogThread.Free;
    end;
end;

function AddToLog(const DT: TDateTime; const Text: string): boolean;
var t: TextFile;
begin
    result := false;
    try
        AssignFile(t, WLogFileName);
        if (FileExists(WLogFileName)) then begin
            Append(t);
            if FirstLine then begin
                WriteLn(t, '');
            end;
        end else begin
            Rewrite(t);
        end;
        FirstLine := false;
        WriteLn(t, Format('%s : %s', [FormatDateTime('dd.mm.yyyy hh:nn:ss.zzz', DT), Text]));
        CloseFile(t);
        result := true;
    finally
    end;
end;

{ TLogThread }

procedure TLogThread.WMLog(var Msg: TThreadMessage);
var p: PLogRecord;
begin
{$HINTS OFF}
    p := PLogRecord(Msg.LParam);
{$HINTS ON}
    AddToLog(p^.DT, FreeString(p^.PString));
    FreeMem(p);
end;

initialization
    WLogLevel := WLL_NORMAL;
    WLogEnabled := InitLogs;
    FirstLine := true;

finalization
    DoneLogs;

end.



Во вложении дублеры.
...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #38957954
dred2k
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
wadman, это так и задумано (Create в деструкторе) ?
Код: pascal
1.
2.
3.
4.
5.
6.
7.
8.
destructor TWEvent.Destroy;
begin
    {$IFDEF WINCE}
    CloseHandle(FWHandle);
    {$ELSE}
    inherited Create(nil, false, false, '');
    {$ENDIF}
end;
...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #38957965
Фотография wadman
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
dred2k,

тут еще есть косячек... Не такой очевидный. Завтра поправлю.
Спасибо.
...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #38957973
чччД
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
wadman,

"косячОк".
...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #38957980
kealon(Ruslan)
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
wadmanПроблема в TEvent, который для wince. Срабатывает до 12 раз в секунду по сигналу и ни о каком таймауте и речи нет.
Подумаю, как обойти...
в багрепорт писали?
...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #38958102
Фотография DarkMaster
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
wadmandred2k,

тут еще есть косячек... Не такой очевидный. Завтра поправлю.
Спасибо.

А чо, красиво ;)
...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #38958259
Фотография wadman
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
kealon(Ruslan)wadmanПроблема в TEvent, который для wince. Срабатывает до 12 раз в секунду по сигналу и ни о каком таймауте и речи нет.
Подумаю, как обойти...
в багрепорт писали?
Написал-бы, если бы знал решение. С отладкой на wince нет желания разбираться (долго и лень), с учетом, что threadmansger и для обычной винды и для ce устанавливается один и тот же (судя по исходникам), то ковыряние тоже будет не быстрым.
CE порт в целом глючный. Написав приложение из окна, кнопки и потока с ивентом словил несколько ошибок/недочетов:
1. now не возвращает миллисекунды (кто виноват: fpc, эмулятор или wince?).
2. если окну поставить position в default, то приложение вылетает еще до показа окна.
3. event сигналит как оголтелый.

В общем, вот исправленный вариант.

wthread.pas
Код: 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.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
261.
262.
263.
264.
265.
266.
267.
268.
269.
270.
271.
272.
273.
274.
275.
276.
277.
278.
279.
280.
281.
282.
283.
284.
285.
286.
287.
288.
289.
290.
291.
292.
293.
294.
295.
296.
297.
298.
299.
300.
301.
302.
303.
304.
305.
306.
307.
308.
309.
310.
311.
312.
313.
314.
315.
316.
317.
318.
319.
320.
321.
322.
323.
324.
325.
326.
327.
328.
329.
330.
331.
332.
333.
334.
335.
336.
337.
338.
339.
340.
341.
342.
343.
344.
345.
346.
347.
348.
349.
350.
351.
352.
353.
354.
355.
356.
357.
358.
359.
360.
361.
362.
363.
364.
365.
366.
367.
368.
369.
370.
371.
372.
373.
374.
375.
376.
377.
378.
379.
380.
381.
382.
383.
384.
385.
386.
387.
388.
389.
390.
391.
392.
393.
394.
395.
396.
397.
398.
399.
400.
401.
402.
403.
404.
405.
406.
407.
408.
409.
410.
411.
412.
413.
414.
415.
416.
417.
418.
419.
420.
421.
422.
423.
424.
425.
426.
427.
428.
429.
430.
431.
432.
433.
434.
435.
436.
437.
438.
439.
440.
441.
442.
443.
444.
445.
446.
447.
448.
449.
450.
451.
452.
453.
454.
455.
456.
457.
458.
459.
460.
461.
462.
463.
464.
465.
466.
467.
468.
469.
470.
471.
472.
473.
474.
475.
476.
477.
478.
479.
480.
481.
482.
483.
484.
485.
486.
487.
488.
489.
490.
491.
492.
493.
494.
495.
496.
497.
498.
499.
500.
501.
502.
503.
504.
505.
506.
507.
508.
509.
510.
511.
512.
513.
514.
515.
516.
517.
518.
519.
520.
521.
522.
523.
524.
525.
526.
527.
528.
529.
530.
531.
532.
533.
534.
535.
536.
537.
538.
539.
540.
541.
542.
543.
544.
545.
546.
547.
548.
549.
550.
551.
552.
553.
554.
555.
556.
557.
558.
559.
560.
561.
562.
563.
564.
565.
566.
567.
568.
569.
570.
571.
572.
573.
574.
575.
576.
577.
578.
579.
580.
581.
582.
583.
584.
585.
586.
587.
588.
589.
590.
591.
592.
593.
594.
595.
596.
597.
598.
599.
600.
601.
602.
603.
604.
605.
606.
607.
608.
609.
610.
611.
612.
613.
614.
615.
616.
617.
618.
619.
620.
621.
622.
623.
624.
625.
626.
627.
628.
629.
630.
631.
632.
633.
634.
635.
636.
637.
638.
639.
640.
641.
642.
643.
644.
645.
646.
647.
648.
649.
650.
651.
652.
653.
654.
655.
656.
657.
658.
659.
660.
661.
662.
663.
664.
665.
666.
667.
668.
669.
670.
671.
672.
673.
674.
675.
676.
677.
678.
679.
680.
681.
682.
683.
684.
685.
686.
687.
688.
689.
690.
691.
692.
693.
694.
695.
696.
697.
698.
699.
700.
701.
702.
703.
704.
705.
706.
707.
708.
709.
710.
711.
712.
713.
714.
715.
716.
717.
718.
719.
720.
721.
722.
723.
724.
725.
726.
727.
728.
729.
730.
731.
732.
733.
734.
735.
736.
737.
738.
739.
740.
741.
742.
743.
744.
745.
746.
747.
748.
749.
750.
751.
752.
753.
754.
755.
756.
757.
758.
759.
760.
761.
762.
763.
764.
765.
766.
767.
768.
769.
770.
771.
772.
773.
774.
775.
776.
777.
778.
779.
780.
781.
782.
783.
784.
785.
786.
787.
788.
789.
790.
791.
792.
793.
794.
795.
796.
797.
798.
799.
800.
801.
802.
803.
804.
805.
806.
807.
808.
809.
810.
811.
812.
813.
814.
815.
816.
817.
818.
819.
820.
821.
822.
823.
824.
825.
826.
827.
828.
829.
830.
831.
832.
833.
834.
835.
836.
837.
838.
839.
840.
841.
842.
843.
844.
845.
846.
847.
848.
849.
850.
851.
852.
853.
854.
855.
856.
857.
858.
859.
860.
861.
862.
863.
864.
865.
866.
867.
868.
869.
870.
871.
872.
873.
874.
875.
unit wthread;
// модуль для работы с доп.потоками Delphi&Lazarus (win&wince&linux)
// позволяет "общаться" дополнительному и основному потокам посредством очереди сообщений
// (c) wadman 2013-2015, версия от 14.05.2015
//
// использование:
// 1. Создать наследника с объявленными обработчиками сообщений
//  const WM_TEST_PROC = WM_THREAD_BASE + 1;
//  TMyThread = class(TWThread)
//     procedure WMTestProc(var Msg: TThreadMessage); message WM_TEST_PROC;
//  данные процедуры будут выполняться в доп.потоке путем отправки связанного с ними сообщения в поток
//  см. PostToThreadMessage
// 2. Присвоить форме обработчика(ов) события OnThreadReceiveMessage (OnTimeOut - при необходимости)
//
// Для обмена строками рекомендуется использовать функции NewString и FreeString
//
// Для корректной остановки потока и дальнейшей работы программы должна использоваться процедура StopThrad
// При компиляции под *nix в файле проекта до раздела USES вставить строку {$DEFINE UseCThreads}
{$IFDEF FPC}
{$mode objfpc}{$H+}
    {$IFDEF WINDOWS}
        {$DEFINE FPC_WIN}
        {$DEFINE ALL_WIN}
    {$ELSE}
        {$DEFINE FPC_NIX}
    {$ENDIF}
{$ELSE}
    {$DEFINE WIN}
    {$DEFINE ALL_WIN}
{$ENDIF}

// для передачи строк между потоками используется выделенная память
{$DEFINE ALLOC_STRING}
// добавлять отладочную информацию с использованием модуля wlog
{.$DEFINE WTHREAD_DEBUG_LOG}

interface

uses
    SysUtils,
    {$IFDEF WTHREAD_DEBUG_LOG}
     wlog,
    {$ENDIF}
    {$IFDEF UNIX} // добавить эти строки в модуль проекта при разработке не под windows
        //cthreads,
        //cmem,
    {$ENDIF}
    Classes
    {$IFNDEF FPC}
    ,Messages
    {$ENDIF}
    ,contnrs
    {$IFDEF ALL_WIN}
    ,Windows
    {$ENDIF}
    ,SyncObjs
    ;

const
    {$IFDEF FPC}
    //INFINITE            = Cardinal($FFFFFFFF);
    {$ENDIF}
    WM_USER             = $400;
    WM_THREAD_BASE      = WM_USER + $110;
    WM_THREAD_MAX       = WM_USER + $300;

type
    PThreadMessage = ^TThreadMessage;
    TThreadMessage = record
        {$IFDEF FPC}
        Message: DWord;
        {$ELSE}
        Message: Word;
        {$ENDIF}
        WParam: Word;
        LParam: NativeInt;
    end;

    TWEvent = class(TEvent)
    protected
        FWHandle: THandle;
    public
        constructor Create; overload;
        destructor Destroy; override;
        procedure WSetEvent;
        procedure WResetEvent;
        function WWaitFor(const TimeOut: Cardinal): TWaitResult;
    end;


    TWThread = class;

        // событие на получение сообщения из потока
    TWThreadReceiveMessage = procedure(Sender: TWThread; var Msg: TThreadMessage) of object;
        // событие из потока на таймаут
    TWThreadTimeOut = procedure(Sender: TWThread) of object;

    { TWThread }

    TWThread = class(TThread)
    private
        {$IFDEF FPC}
        FQueue: TQueue;
        FSection: TCriticalSection;
        FMessageEvent: TWEvent;
        FGUIThread: TThread;
        {$ELSE}
        FQueueReady: boolean;
        FQueueMessages: array of TThreadMessage;
        FToolWindow: THandle;
        hCloseEvent: THandle;
        {$ENDIF}
        FOnThreadReceiveMessage: TWThreadReceiveMessage;
        FOnTimeOut: TWThreadTimeOut;
        FTimeOutIsDirect: boolean;
        {$IFDEF WTHREAD_DEBUG_LOG}
        FUseDebugLog: boolean;
        function intPostToLog(const Text: string): boolean; overload;
        function intPostToLog(const Text: string; const Level: TLogLevel): boolean; overload;
        {$ENDIF}
        function GetMsFromDateTime(const Value: TDateTime): Cardinal;
        procedure SetTimeOut(const Value: Cardinal);
    protected
        FTimeOut: Cardinal;
        {$IFNDEF FPC}
        FWindowHandle: THandle;
        procedure WWindowProc(var Msg: TMessage);
        procedure GetWindow;
        procedure FreeWindow;
        {$ENDIF}
            // отправка сообщения из этого потока для вызова обработчика OnThreadReceiveMessage
        procedure PostMessageFromThread(const Msg: Word; const WParam: Word; const LParam: NativeInt);
        procedure DoThreadReceiveMessage(const Msg: Word; const WParam: Word; const LParam: NativeInt);
        procedure DoTimeout;
        procedure Execute; override;
        function GetTimeOut: Cardinal; virtual;
    public
        constructor Create
        {$IFDEF WTHREAD_DEBUG_LOG}
                (AUseDebugLog: Boolean)
        {$ENDIF}
            ; overload;
        constructor Create(CreateSuspended
        {$IFDEF WTHREAD_DEBUG_LOG}
                , AUseDebugLog
        {$ENDIF}
                : Boolean); overload;
        destructor Destroy; override;
            // Две процедуры, которые выполняются в контексте потока. В начале и в конце.
        procedure InitThread; virtual;
        procedure DoneThread; virtual;
            // см TimeOutIsDirect, при true - перекрыть следующую процедуру
        procedure DirectTimeOut; virtual;
            // отправка любого сообщения В этот поток
        function PostToThreadMessage(const Msg: Word; const WParam: Word; const LParam: NativeInt): Boolean;
            // остановка потока по феншую
        procedure StopThread;
            // событие на получение данных ИЗ этого потока, вызывается в основном потоке
        property OnThreadReceiveMessage: TWThreadReceiveMessage read FOnThreadReceiveMessage write FOnThreadReceiveMessage;
            // событие, возникающее при превышении интервала ожидания
        property OnTimeOut: TWThreadTimeOut read FOnTimeOut write FOnTimeOut;
            // интервал ожидания в мс, по-умолчанию - бесконечно
        property TimeOut: Cardinal read GetTimeOut write SetTimeOut default INFINITE;
            // true = Будет вызван DirectTimeOut из этого потока, false = вызвано событие OnTimeOut в основном потоке.
        property TimeOutIsDirect: boolean read FTimeOutIsDirect write FTimeOutIsDirect default false;
        {$IFDEF WTHREAD_DEBUG_LOG}
        property UseDebugLog: boolean read FUseDebugLog write FUseDebugLog default false;
        {$ENDIF}
    end;

    // подготовка строки к обмену между потоками
function NewString(const Text: string): NativeInt;

    // возвращение строки к привычному виду после приема из другого потока
function FreeString(var P: NativeInt): String;

implementation

{$IFNDEF UNIX}
//uses Windows;
{$ENDIF}

const
    {$IFNDEF FPC}
    WM_THREAD_READY     = WM_USER+$102;
    {$ENDIF}
    WM_TIMEOUT          = WM_USER+$101;

    SizeOfChar          = SizeOf(Char);

    // константы из DateUtils
    OneMillisecond      = 1 / MSecsPerDay;

{$IFDEF ALLOC_STRING}
{$IFDEF ALL_WIN}
// для передачи строки между программами
function GlobalNewString(const Text: string): NativeInt;
var l: Integer;
    p: pointer;
begin
    l := Length(Text)*SizeOfChar;
    if L > SizeOfChar then begin
        Result := GlobalAlloc(GHND, l+SizeOfChar);
        if LongBool(Result) then begin
            {$HINTS OFF}
            p := GlobalLock(Result);
            {$HINTS ON}
            Move(Pointer(Text)^, Pointer(p)^, l);
            GlobalUnlock(Result);
        end
    end else
        Result := 0;
end;

// для передачи строки между программами
function GlobalFreeString(var P: NativeInt): String;
var ps: pointer;
begin
    if LongBool(P) then begin
        {$HINTS OFF}
        ps := GlobalLock(P);
        {$HINTS ON}
        SetLength(Result, Length(PChar(ps)));
        Move(Pointer(ps)^, Pointer(Result)^, Length(Result)*SizeOfChar);
        GlobalUnlock(P);
        P := GlobalFree(P);
    end;
end;
{$ENDIF}

// для передачи строки в пределах одной программы
function FreeString(var P: NativeInt): String;
begin
    {$HINTS OFF}
    if LongBool(P) then begin
        SetLength(Result, Length(PChar(P)));
        Move(Pointer(P)^, Pointer(Result)^, Length(Result)*SizeOfChar);
        {$IFDEF ALL_WIN}
        P := LocalFree(HLOCAL(P));
        {$ELSE}
        P := FreeMem(Pointer(P));
        {$ENDIF}
    end;
end;

// для передачи строки в пределах одной программы
function NewString(const Text: string): NativeInt;
var l: Integer;
begin
    l := Length(Text)*SizeOfChar;
    if L > SizeOfChar then begin
        {$IFDEF ALL_WIN}
        Result := LocalAlloc(LPTR, l+SizeOfChar);
        {$ELSE}
        Pointer(Result) := AllocMem(l+SizeOfChar);
        {$ENDIF}
        if LongBool(Result) then
            Move(Pointer(Text)^, Pointer(Result)^, l);
        {$HINTS ON}
    end else
        Result := 0;
end;
{$ELSE}
function NewString(const Text: string): NativeInt;
begin
    Result := 0;
    string(Result) := Text;
end;

function FreeString(var P: NativeInt): String;
begin
    Result := '';
    NativeInt(Result) := P;
end;
{$ENDIF}

constructor TWEvent.Create;
begin
    {$IFDEF WINCE}
    FWHandle := CreateEvent(nil, false, false, nil);
    {$ELSE}
    inherited Create(nil, false, false, '');
    {$ENDIF}
end;

destructor TWEvent.Destroy;
begin
    {$IFDEF WINCE}
    CloseHandle(FWHandle);
    {$ELSE}
    inherited;
    {$ENDIF}
end;

procedure TWEvent.WResetEvent;
begin
    {$IFDEF WINCE}
    Windows.ResetEvent(FWHandle);
    {$ELSE}
    inherited ResetEvent;
    {$ENDIF}
end;

procedure TWEvent.WSetEvent;
begin
    {$IFDEF WINCE}
    Windows.SetEvent(FWHandle);
    {$ELSE}
    inherited SetEvent;
    {$ENDIF}
end;

function TWEvent.WWaitFor(const TimeOut: Cardinal): TWaitResult;
{$IFDEF WINCE}
var
    dw: DWord;
begin
    dw := WaitForSingleObject(FWHandle, TimeOut);
    case dw of
        WAIT_ABANDONED: result := wrAbandoned;
        WAIT_OBJECT_0: result := wrSignaled;
        WAIT_TIMEOUT: result := wrTimeout;
    else
        Result := wrError;
    end;
{$ELSE}
begin
    result := WaitFor(TimeOut);
{$ENDIF}
end;

{$IFDEF FPC}
type

    { TGUIThread }

    TGUIThread = class(TThread)
    private
        FMessageEvent: TWEvent;
        FTimeOut: Boolean;
        FOwner: TWThread;
        FQueue: TQueue;
        FSection: TCriticalSection;
        FCurrentMessage: TThreadMessage;
    protected
        procedure Execute; override;
        procedure CallGUIThread;
    public
        constructor Create(AOwner: TWThread); overload;
        destructor Destroy; override;
        procedure PostMessage(const Message: DWord; const WParam: Word; const LParam: NativeInt);
        procedure StopThread;
    end;

{ TGUIThread }

procedure FreeQueue(const Queue: TQueue);
begin
    while Queue.Count > 0 do
        Queue.Pop;
end;

procedure TGUIThread.Execute;
var Message: PThreadMessage;
    wr: TWaitResult;
begin
    while not Terminated do begin
        wr := FMessageEvent.WWaitFor(INFINITE);
        if (not Terminated) and (wr = wrSignaled) then begin
            if FTimeOut then begin
                FTimeOut := False;
                if Assigned(FOwner.FOnTimeOut) then
                    FOwner.FOnTimeOut(FOwner);
            end;
            while FQueue.Count > 0 do begin
                FSection.Enter;
                Message := FQueue.Pop;
                FSection.Leave;
                FCurrentMessage := Message^;
                FreeMem(Message);
                if (FCurrentMessage.Message >= WM_THREAD_BASE)
                    and(FCurrentMessage.Message <= WM_THREAD_MAX) then
                    Synchronize(@CallGUIThread);
            end;
        end;
    end;
end;

procedure TGUIThread.CallGUIThread;
begin
    if Assigned(FOwner) then
        FOwner.DoThreadReceiveMessage(FCurrentMessage.Message, FCurrentMessage.WParam, FCurrentMessage.LParam);
end;

constructor TGUIThread.Create(AOwner: TWThread);
begin
    inherited Create(False);
    FMessageEvent := TWEvent.Create;
    FQueue := TQueue.Create;
    FSection := TCriticalSection.Create;
    FOwner := AOwner;
end;

destructor TGUIThread.Destroy;
begin
    FreeQueue(FQueue);
    FSection.Free;
    FQueue.Free;
    FMessageEvent.Free;
    inherited Destroy;
end;

procedure TGUIThread.PostMessage(const Message: DWord; const WParam: Word; const LParam: NativeInt);
var Msg: PThreadMessage;
begin
    GetMem(Msg, SizeOf(TThreadMessage));
    Msg^.Message := Message;
    Msg^.WParam := WParam;
    Msg^.LParam := LParam;
    FSection.Enter;
    FQueue.Push(Msg);
    FSection.Leave;
    FMessageEvent.WSetEvent;
end;

procedure TGUIThread.StopThread;
begin
    Terminate;
    FMessageEvent.WSetEvent;
end;
{$ENDIF}

{ TWThread }

constructor TWThread.Create(CreateSuspended
        {$IFDEF WTHREAD_DEBUG_LOG}
        , AUseDebugLog
        {$ENDIF}
        : Boolean);
begin
    inherited Create(CreateSuspended);
    {$IFDEF WTHREAD_DEBUG_LOG}
    FUseDebugLog := AUseDebugLog;
    {$ENDIF}
    {$IFDEF FPC}
    FQueue := TQueue.Create;
    FMessageEvent := TWEvent.Create;
    FSection := TCriticalSection.Create;
    FGUIThread := TGUIThread.Create(Self);
    {$ELSE}
    hCloseEvent := CreateEvent(nil, false, false, nil);
    GetWindow;
    FWindowHandle := 0;
    FQueueReady := False;
    {$ENDIF}
    FTimeOut := INFINITE;
    FTimeOutIsDirect := False;
end;

constructor TWThread.Create
    {$IFDEF WTHREAD_DEBUG_LOG}
    (AUseDebugLog: Boolean)
    {$ENDIF}
    ;
begin
    Create(false
        {$IFDEF WTHREAD_DEBUG_LOG}
        , AUseDebugLog
        {$ENDIF}
        );
end;

destructor TWThread.Destroy;
begin
    {$IFDEF FPC}
    FMessageEvent.Free;
    FreeQueue(FQueue);
    FQueue.Free;
    FGUIThread.Free;
    FSection.Free;
    {$ELSE}
    CloseHandle(hCloseEvent);
    {$ENDIF}
    inherited;
end;

procedure TWThread.DirectTimeOut;
begin
    // override
end;

// Процедура, выполняющая в контексте этого потока перед запуском очереди сообщений (в начале работы потока).
procedure TWThread.InitThread;
begin
    // empty
end;

// Процедура, выполняющая в контексте этого потока после отработки очереди сообщений (в конце работы потока).
procedure TWThread.DoneThread;
begin
    // empty
end;

procedure TWThread.DoThreadReceiveMessage(const Msg: Word; const WParam: Word; const LParam: NativeInt);
var ThreadMsg: TThreadMessage;
begin
    if Assigned(FOnThreadReceiveMessage) then begin
        ThreadMsg.Message := Msg;
        ThreadMsg.WParam := WParam;
        ThreadMsg.LParam := LParam;
        FOnThreadReceiveMessage(Self, ThreadMsg);
    end;
end;

procedure TWThread.DoTimeout;
begin
    {$IFDEF FPC}
    TGUIThread(FGUIThread).FTimeOut := true;
    TGUIThread(FGUIThread).FMessageEvent.WSetEvent;
    {$ELSE}
    if Assigned(FOnTimeOut) then
        FOnTimeOut(Self);
    {$ENDIF}
end;

// отправка любого сообщения В этот поток
function TWThread.PostToThreadMessage(const Msg: Word; const WParam: Word;
    const LParam: NativeInt): Boolean;
{$IFDEF FPC}
var PMsg: PThreadMessage;
begin
    GetMem(PMsg, SizeOf(TThreadMessage));
    PMsg^.Message := Msg;
    PMsg^.WParam := WParam;
    PMsg^.LParam := LParam;
    FSection.Enter;
    FQueue.Push(PMsg);
    FSection.Leave;
    {$IFDEF WTHREAD_DEBUG_LOG}
    intPostToLog(Format('Thread %d : send TO thread msg: %d (%1:x).', [Handle, Msg]), WLL_EXTRA);
    {$ENDIF}
    FMessageEvent.WSetEvent;
    result := true;
{$ELSE}
begin
    if FQueueReady then begin
        // если очередь сообщений создана и поток инициализирован, то сразу отправляем
        result := (not Suspended)and(PostThreadMessage(ThreadID, Msg, wParam, lParam));
    end else begin
        // иначе кэшируем
        SetLength(FQueueMessages, Length(FQueueMessages)+1);
        FQueueMessages[High(FQueueMessages)].Message := Msg;
        FQueueMessages[High(FQueueMessages)].WParam := WParam;
        FQueueMessages[High(FQueueMessages)].LParam := LParam;
        {$IFDEF WTHREAD_DEBUG_LOG}
        intPostToLog(Format('Thread %d : send TO thread msg: %d (%1:x).', [Handle, Msg]), WLL_EXTRA);
        {$ENDIF}
        result := True;
    end;
{$ENDIF}
end;

{$IFDEF WTHREAD_DEBUG_LOG}
{$B-}
function TWThread.intPostToLog(const Text: string): boolean;
begin
    result := FUseDebugLog and PostToLog(Text);
end;

function TWThread.intPostToLog(const Text: string; const Level: TLogLevel): boolean;
begin
    result := FUseDebugLog and PostToLog(Text, Level);
end;
{$ENDIF}

function TWThread.GetMsFromDateTime(const Value: TDateTime): Cardinal;
begin
    Result := Round((Now - Value) / OneMillisecond);
end;

procedure TWThread.SetTimeOut(const Value: Cardinal);
begin
    {$IFDEF FPC}
    if FTimeOut <> Value then begin
        if (Value = 0) then
            FTimeOut := INFINITE
        else
            FTimeOut := Value;
        if not Suspended then
            FMessageEvent.WSetEvent;
    end;
    {$ELSE}
    if FTimeOut <> Value then begin
        if (Value = 0) then
            FTimeOut := INFINITE
        else
            FTimeOut := Value;
        if (not Suspended)and(GetCurrentThreadId <> ThreadID) then
            PostThreadMessage(ThreadID, WM_TIMEOUT, Value, 0);
    end;
    {$ENDIF}
end;

procedure TWThread.Execute;
var
    internalTimeout, ms: Cardinal;
    busy: TDateTime;
{$IFDEF FPC}
    Message: PThreadMessage;
    wr: TWaitResult;
begin
    {$IFDEF WTHREAD_DEBUG_LOG}
    intPostToLog(Format('Thread %d : started.', [Handle]));
    {$ENDIF}
    InitThread;
    internalTimeout := TimeOut;
    busy := 0;
    {$IFDEF WTHREAD_DEBUG_LOG}
    intPostToLog(Format('Thread %d : main loop started.', [Handle]), WLL_EXTRA);
    {$ENDIF}
    while not Terminated do begin
        wr := FMessageEvent.WWaitFor(internalTimeout);
        busy := Now;
        if not Terminated then
            case WR of
                wrSignaled: begin
                    {$IFDEF WTHREAD_DEBUG_LOG}
                    intPostToLog(Format('Thread %d : got signal (queue cnt: %d).', [Handle, Integer(wr), FQueue.Count]), WLL_EXTRA);
                    {$ENDIF}
                    while FQueue.Count > 0 do begin
                        FSection.Enter;
                        Message := FQueue.Pop;
                        FSection.Leave;
                        if (Message^.message >= WM_THREAD_BASE)
                            and (Message^.Message <= WM_THREAD_MAX) then begin
                                {$IFDEF WTHREAD_DEBUG_LOG}
                                intPostToLog(Format('Thread %d : dispatch msg %d (%1:x).', [Handle, Message^.Message]), WLL_EXTRA);
                                {$ENDIF}
                                Dispatch(Message^);
                            end;
                        FreeMem(Message);
                    end;
                end;
                wrTimeout: begin
                    internalTimeout := TimeOut;
                    if FTimeOutIsDirect then begin
                        {$IFDEF WTHREAD_DEBUG_LOG}
                        intPostToLog(Format('Thread %d : DirectTimeOut.', [Handle]), WLL_EXTRA);
                        {$ENDIF}
                        DirectTimeOut;
                    end else begin
                        {$IFDEF WTHREAD_DEBUG_LOG}
                        intPostToLog(Format('Thread %d : Sent TimeOut.', [Handle]), WLL_EXTRA);
                        {$ENDIF}
                        PostMessageFromThread(WM_TIMEOUT, 0, 0);
                        // сообщение не занимает много времени, на точность не влияет
                        busy := 0;
                    end;
                end;
            end;
        // корректируем интервал таймаута на время выполнения кода выше
        if (busy <> 0) then begin
            ms := GetMsFromDateTime(busy);
            if (ms > TimeOut) then
                internalTimeout := 0
            else
                internalTimeout := TimeOut - ms;
        end else begin
            internalTimeout := TimeOut;
        end;
    end;
{$ELSE}
    message: TThreadMessage;
    HandlesToWaitFor: array [0 .. 0] of THandle;
    dwHandleSignaled: DWORD;
    msg: TMsg;

begin
    {$IFDEF WTHREAD_DEBUG_LOG}
    intPostToLog(Format('Thread %d : started.', [Handle]));
    {$ENDIF}
    // следующая строка должна быть всегда первой в процедуре, т.к. запускает очередь сообщений для потока
    {$HINTS OFF}
    PeekMessage(msg, THandle(-1), WM_USER, WM_USER, PM_NOREMOVE);
    {$HINTS ON}
    internalTimeout := TimeOut;
    busy := 0;
    InitThread;
    HandlesToWaitFor[0] := hCloseEvent;
    PostMessageFromThread(WM_THREAD_READY, 0, 0);

    {$IFDEF WTHREAD_DEBUG_LOG}
    intPostToLog(Format('Thread %d : main loop started.', [Handle]), WLL_EXTRA);
    {$ENDIF}
    while not Terminated do begin
        if ((FWindowHandle = 0) and(not PeekMessage(msg, THandle(-1), WM_USER, WM_THREAD_MAX, PM_REMOVE)))
            or((FWindowHandle <> 0) and (
                (not PeekMessage(msg, FWindowHandle, 0, 0, PM_REMOVE))
                and
                (not PeekMessage(msg, THandle(-1), WM_USER, WM_THREAD_MAX, PM_REMOVE))
            )) then begin

            if (FWindowHandle = 0) then
                dwHandleSignaled := MsgWaitForMultipleObjects(1, HandlesToWaitFor, false, internalTimeout, QS_ALLINPUT)
            else
                dwHandleSignaled := MsgWaitForMultipleObjects(1, HandlesToWaitFor, false, internalTimeout, QS_ALLEVENTS);
            busy := Now;
            case dwHandleSignaled of
                WAIT_OBJECT_0: begin // выставлено событие остановки потока
                    if not FreeOnTerminate then
                        Terminate;
                    break;
                end;
                WAIT_OBJECT_0 + 1: begin // получено сообщение
                    Continue;
                end;
                WAIT_TIMEOUT: begin
                    if FTimeOutIsDirect then begin
                        {$IFDEF WTHREAD_DEBUG_LOG}
                        intPostToLog(Format('Thread %d : DirectTimeOut.', [Handle]), WLL_EXTRA);
                        {$ENDIF}
                        DirectTimeOut;
                    end else begin
                        {$IFDEF WTHREAD_DEBUG_LOG}
                        intPostToLog(Format('Thread %d : Sent TimeOut.', [Handle]), WLL_EXTRA);
                        {$ENDIF}
                        PostMessageFromThread(WM_TIMEOUT, 0, 0);
                        // сообщение не занимает много времени, на точность не влияет
                        busy := 0;
                    end;
                    // выставляем новый интервал за минусом времени выполнения процедуры таймаута
                    if (busy <> 0) then begin
                        ms := GetMsFromDateTime(busy);
                        if (ms > TimeOut) then
                            internalTimeout := 0
                        else
                            internalTimeout := TimeOut - ms;
                    end else begin
                        internalTimeout := TimeOut;
                    end;
                    Continue;
                end;
            end;
        end;

        if Terminated then break;

        if LongBool(FWindowHandle) and (FWindowHandle = msg.hwnd) then begin
            // хэндл окна присвоен и пришло сообщение для окна
            TranslateMessage(msg);
            DispatchMessage(msg);
        end else case msg.message of
            WM_TIMEOUT: begin
                // изменен таймаут
                FTimeOut := msg.wParam;
                busy := 0;
            end;
            WM_THREAD_BASE..WM_THREAD_MAX: begin
                message.Message := Msg.message;
                message.WParam := Msg.wParam;
                message.LParam := Msg.lParam;
                {$IFDEF WTHREAD_DEBUG_LOG}
                intPostToLog(Format('Thread %d : dispatch msg %d (%1:x).', [Handle, Message.Message]), WLL_EXTRA);
                {$ENDIF}
                // ищем и вызываем procedure of object message
                Dispatch(message);
            end;
        end;

        // корректируем интервал таймаута на время выполнения кода выше
        if (busy <> 0) then begin
            ms := GetMsFromDateTime(busy);
            if (ms > TimeOut) then
                internalTimeout := 0
            else
                internalTimeout := TimeOut - ms;
        end else begin
            internalTimeout := TimeOut;
        end;
    end;
{$ENDIF}
    DoneThread;
    {$IFDEF WTHREAD_DEBUG_LOG}
    intPostToLog(Format('Thread %d : main loop stoped.', [Handle]), WLL_EXTRA);
    {$ENDIF}
end;

// отправка сообщения из этого потока для вызова обработчика OnThreadReceiveMessage
procedure TWThread.PostMessageFromThread(const Msg: Word; const WParam: Word; const LParam: NativeInt);
begin
{$IFDEF FPC}
    if Assigned(FGUIThread) then begin
        TGUIThread(FGUIThread).PostMessage(Msg, WParam, LParam);
    {$IFDEF WTHREAD_DEBUG_LOG}
        intPostToLog(Format('Thread %d : send FROM thread msg: %d (%1:x).', [Handle, Msg]), WLL_EXTRA);
    {$ENDIF}
    end
    {$ELSE}
    if FToolWindow <> 0 then begin
        PostMessage(FToolWindow, Msg, WParam, LParam);
        {$IFDEF WTHREAD_DEBUG_LOG}
        intPostToLog(Format('Thread %d : send FROM thread msg: %d (%1:x).', [Handle, Msg]), WLL_EXTRA);
        {$ENDIF}
    end
{$ENDIF}
    {$IFDEF WTHREAD_DEBUG_LOG}
    else
        intPostToLog(Format('Thread %d : cant send FROM thread msg: %d (%1:x).', [Handle, Msg]), WLL_EXTRA);
    {$ENDIF}
end;

// отправка любого сообщения В этот поток
procedure TWThread.StopThread;
begin
    {$IFDEF FPC}
    TGUIThread(FGUIThread).StopThread;
    Terminate;
    FMessageEvent.WSetEvent;
    {$ELSE}
    FreeWindow;
    Terminate;
    SetEvent(hCloseEvent);
    //SleepEx(1, false);
    //result := WaitForSingleObject(Handle, 10000) <> WAIT_TIMEOUT;
    {$ENDIF}
    {$IFDEF WTHREAD_DEBUG_LOG}
    intPostToLog(Format('Thread %d : stop signal.', [Handle]));
    {$ENDIF}
end;

function TWThread.GetTimeOut: Cardinal;
begin
    Result := FTimeOut;
end;

{$IFNDEF FPC}

procedure TWThread.WWindowProc(var Msg: TMessage);
var i: integer;
begin
    case Msg.Msg of
        WM_TIMEOUT: begin
            DoTimeout;
            Msg.Result := 1;
        end;
        WM_THREAD_READY: begin
            FQueueReady := true;
            // поток готов, отправляем ему все закэшированные сообщения
            if Length(FQueueMessages) > 0 then for i := Low(FQueueMessages) to High(FQueueMessages) do
                PostToThreadMessage(FQueueMessages[i].Message, FQueueMessages[i].WParam, FQueueMessages[i].LParam);
            SetLength(FQueueMessages, 0);
        end;
        WM_THREAD_BASE..WM_THREAD_MAX: begin
            DoThreadReceiveMessage(Msg.Msg, Msg.WParam, Msg.LParam);
            Msg.Result := 1;
        end
    else  if LongBool(FToolWindow) then
        Msg.Result := DefWindowProc(FToolWindow, Msg.Msg, Msg.wParam, Msg.lParam);
    end;
end;

procedure TWThread.GetWindow;
begin
    FToolWindow := AllocateHWnd(WWindowProc);
end;

procedure TWThread.FreeWindow;
begin
    DeallocateHWnd(FToolWindow);
    FToolWindow := 0;
end;

{$ENDIF}

end.



wlog.pas
Код: 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.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
unit wlog;
// wlog (c) wadman, 2015, 13.05.2015
// multithread safe logging

interface

// log level
type
    TLogLevel = (WLL_MINIMUM, WLL_NORMAL, WLL_MAXIMUM, WLL_EXTRA);

var WLogFileName: string;
    WLogEnabled: boolean;
    WLogLevel: TLogLevel;

function PostToLog(const Text: string): boolean; overload; // with WLL_NORMAL
function PostToLog(const Text: string; const Level: TLogLevel): boolean; overload;

implementation

uses SysUtils, WThread;

const
    WM_LOG      = WM_THREAD_BASE + 1;

type
    PLogRecord = ^TLogRecord;
    TLogRecord = record
        DT: TDateTime;
        PString: NativeInt;
    end;

    TLogThread = class(TWThread)
    private
        procedure WMLog(var Msg: TThreadMessage); message WM_LOG;
    end;

var
    LogThread: TLogThread;
    FirstLine: boolean;

function PostToLog(const Text: string): boolean;
begin
    result := PostToLog(Text, WLL_NORMAL);
end;

function PostToLog(const Text: string; const Level: TLogLevel): boolean;
var p: PLogRecord;
    d: TDateTime;
begin
    if SmallInt(Level) <= SmallInt(WLogLevel) then begin
        result := WLogEnabled and LongBool(LogThread);
        if result then begin
            d := Now;
            p := AllocMem(SizeOf(TLogRecord));
            p^.DT := d;
            p^.PString := NewString(Text);
{$HINTS OFF}
            result := LogThread.PostToThreadMessage(WM_LOG, 0, NativeInt(p));
{$HINTS ON}
            if not result then begin
                FreeString(p^.PString);
                FreeMem(p);
            end;
        end;
    end else
        Result := true;
end;

function InitLogs: boolean;
begin
    WLogFileName := ChangeFileExt(ParamStr(0), '.log');
    LogThread := TLogThread.Create(false);
    result := LongBool(LogThread);
end;

procedure DoneLogs;
begin
    if LongBool(LogThread) then begin
        LogThread.StopThread;
        LogThread.Free;
    end;
end;

function AddToLog(const DT: TDateTime; const Text: string): boolean;
var t: TextFile;
begin
    result := false;
    try
        AssignFile(t, WLogFileName);
        if (FileExists(WLogFileName)) then begin
            Append(t);
            if FirstLine then begin
                WriteLn(t, '');
            end;
        end else begin
            Rewrite(t);
        end;
        FirstLine := false;
        WriteLn(t, Format('%s : %s', [FormatDateTime('dd.mm.yyyy hh:nn:ss.zzz', DT), Text]));
        CloseFile(t);
        result := true;
    finally
    end;
end;

{ TLogThread }

procedure TLogThread.WMLog(var Msg: TThreadMessage);
var p: PLogRecord;
begin
{$HINTS OFF}
    p := PLogRecord(Msg.LParam);
{$HINTS ON}
    AddToLog(p^.DT, FreeString(p^.PString));
    FreeMem(p);
end;

initialization
    WLogLevel := WLL_NORMAL;
    WLogEnabled := InitLogs;
    FirstLine := true;

finalization
    DoneLogs;

end.

...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #38958272
Фотография brick08
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
wadman,

Спасибо мил человек, будем пробовать. Если будет без глюков работать, пожалуй заменю коллбэки на твой модуль. О багах отрапортую, если что)
...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #38959408
Фотография wadman
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Решил полностью отказаться от общения между потоками с помощью оконной функции, которая в винде имеет некоторые ограничения, а на линуксе и вовсе не имеет прав на жизнь.
И добавил пример использования. Три класса потоков:
1) Менеджер, который управляет остальными потоками.
2) Поток, который ищет файлы и передают их менеджеру.
3. Поток(и), которые получают имена найденных файлов от менеджера для обработки.

wthread.pas
Код: 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.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
261.
262.
263.
264.
265.
266.
267.
268.
269.
270.
271.
272.
273.
274.
275.
276.
277.
278.
279.
280.
281.
282.
283.
284.
285.
286.
287.
288.
289.
290.
291.
292.
293.
294.
295.
296.
297.
298.
299.
300.
301.
302.
303.
304.
305.
306.
307.
308.
309.
310.
311.
312.
313.
314.
315.
316.
317.
318.
319.
320.
321.
322.
323.
324.
325.
326.
327.
328.
329.
330.
331.
332.
333.
334.
335.
336.
337.
338.
339.
340.
341.
342.
343.
344.
345.
346.
347.
348.
349.
350.
351.
352.
353.
354.
355.
356.
357.
358.
359.
360.
361.
362.
363.
364.
365.
366.
367.
368.
369.
370.
371.
372.
373.
374.
375.
376.
377.
378.
379.
380.
381.
382.
383.
384.
385.
386.
387.
388.
389.
390.
391.
392.
393.
394.
395.
396.
397.
398.
399.
400.
401.
402.
403.
404.
405.
406.
407.
408.
409.
410.
411.
412.
413.
414.
415.
416.
417.
418.
419.
420.
421.
422.
423.
424.
425.
426.
427.
428.
429.
430.
431.
432.
433.
434.
435.
436.
437.
438.
439.
440.
441.
442.
443.
444.
445.
446.
447.
448.
449.
450.
451.
452.
453.
454.
455.
456.
457.
458.
459.
460.
461.
462.
463.
464.
465.
466.
467.
468.
469.
470.
471.
472.
473.
474.
475.
476.
477.
478.
479.
480.
481.
482.
483.
484.
485.
486.
487.
488.
489.
490.
491.
492.
493.
494.
495.
496.
497.
498.
499.
500.
501.
502.
503.
504.
505.
506.
507.
508.
509.
510.
511.
512.
513.
514.
515.
516.
517.
518.
519.
520.
521.
522.
523.
524.
525.
526.
527.
528.
529.
530.
531.
532.
533.
534.
535.
536.
537.
538.
539.
540.
541.
542.
543.
544.
545.
546.
547.
548.
549.
550.
551.
552.
553.
554.
555.
556.
557.
558.
559.
560.
561.
562.
563.
564.
565.
566.
567.
568.
569.
570.
571.
572.
573.
574.
575.
576.
577.
578.
579.
580.
581.
582.
583.
584.
585.
586.
587.
588.
589.
590.
591.
592.
593.
594.
595.
596.
597.
598.
599.
600.
601.
602.
603.
604.
605.
606.
607.
608.
609.
610.
611.
612.
613.
614.
615.
616.
617.
618.
619.
620.
621.
622.
623.
624.
625.
626.
627.
628.
629.
630.
631.
632.
633.
634.
635.
636.
637.
638.
639.
640.
641.
642.
643.
644.
645.
646.
647.
648.
649.
650.
651.
652.
653.
654.
655.
656.
657.
658.
659.
660.
661.
662.
663.
664.
665.
666.
667.
668.
669.
670.
671.
672.
673.
674.
675.
676.
677.
678.
679.
680.
681.
682.
683.
684.
685.
686.
687.
688.
689.
690.
691.
692.
693.
694.
695.
696.
697.
698.
699.
700.
unit wthread;
// модуль для работы с доп.потоками Delphi&Lazarus (win&wince&*nix)
// позволяет "общаться" дополнительному и основному потокам посредством очереди сообщений
// (c) wadman 2013-2015, версия от 14.05.2015
//
// использование:
// 1. Создать наследника с объявленными обработчиками сообщений
//  const WM_TEST_PROC = WM_THREAD_BASE + 1;
//  TMyThread = class(TWThread)
//     procedure WMTestProc(var Msg: TThreadMessage); message WM_TEST_PROC;
//  данные процедуры будут выполняться в доп.потоке путем отправки связанного с ними сообщения в поток
//  см. PostToThreadMessage
// 2. Присвоить форме обработчика(ов) события OnThreadReceiveMessage (OnTimeOut - при необходимости)
//
// Для обмена строками рекомендуется использовать функции NewString и FreeString
//
// Для корректной остановки потока и дальнейшей работы программы должна использоваться процедура StopThrad
// При компиляции под *nix в файле проекта до раздела USES вставить строку {$DEFINE UseCThreads}
{$IFDEF FPC}
{$mode objfpc}{$H+}
    {$IFDEF WINDOWS}
        {$DEFINE FPC_WIN}
        {$DEFINE ALL_WIN}
    {$ELSE}
        {$DEFINE FPC_NIX}
    {$ENDIF}
{$ELSE}
    {$DEFINE WIN}
    {$DEFINE ALL_WIN}
{$ENDIF}

// для передачи строк между потоками используется выделенная память
{$DEFINE ALLOC_STRING}
// добавлять отладочную информацию с использованием модуля wlog
// для сборки демки необходимо включить (убрать точку)
{.$DEFINE WTHREAD_DEBUG_LOG}

interface

uses
    SysUtils,
    {$IFDEF WTHREAD_DEBUG_LOG}
     wlog,
    {$ENDIF}
    {$IFDEF UNIX} // добавить эти строки в модуль проекта при разработке не под windows
        //cthreads,
        //cmem,
    {$ENDIF}
    Classes
    {$IFNDEF FPC}
    ,Messages
    {$ENDIF}
    ,contnrs
    {$IFDEF ALL_WIN}
    ,Windows
    {$ENDIF}
    ,SyncObjs
    ;

const
    {$IFDEF FPC}
    //INFINITE            = Cardinal($FFFFFFFF);
    {$ENDIF}
    WM_USER             = $400;
    WM_THREAD_BASE      = WM_USER + $110;
    WM_THREAD_MAX       = WM_USER + $300;

type
    PThreadMessage = ^TThreadMessage;
    TThreadMessage = record
        {$IFDEF FPC}
        Message: DWord;
        {$ELSE}
        Message: Word;
        {$ENDIF}
        WParam: Word;
        LParam: NativeInt;
    end;

    TWEvent = class(TEvent)
    protected
        FWHandle: THandle;
    public
        constructor Create; overload;
        destructor Destroy; override;
        procedure WSetEvent;
        procedure WResetEvent;
        function WWaitFor(const TimeOut: Cardinal): TWaitResult;
    end;


    TWThread = class;

        // событие на получение сообщения из потока
    TWThreadReceiveMessage = procedure(Sender: TWThread; var Msg: TThreadMessage) of object;
        // событие из потока на таймаут
    TWThreadTimeOut = procedure(Sender: TWThread) of object;

    { TWThread }

    TWThread = class(TThread)
    private
        FOwnerWThread: TWThread;
        FQueue: TQueue;
        FSection: TCriticalSection;
        FMessageEvent: TWEvent;
        FGUIThread: TThread;
        FOnThreadReceiveMessage: TWThreadReceiveMessage;
        FOnTimeOut: TWThreadTimeOut;
        FTimeOutIsDirect: boolean;
        FThreadName: string;
        {$IFDEF WTHREAD_DEBUG_LOG}
        FUseDebugLog: boolean;
        function intPostToLog(const Text: string): boolean; overload;
        function intPostToLog(const Text: string; const Level: TLogLevel): boolean; overload;
        {$ENDIF}
        function GetMsFromDateTime(const Value: TDateTime): Cardinal;
        procedure SetTimeOut(const Value: Cardinal);
        procedure FreeQueue;
    protected
        FTimeOut: Cardinal;
            // отправка сообщения из этого потока для вызова обработчика OnThreadReceiveMessage
        procedure PostMessageFromThread(const Msg: Word; const WParam: Word; const LParam: NativeInt);
            // отправка сообщения из этого потока другому потоку (см. OwnerWThread)
        procedure PostMessageToWThread(const Msg: Word; const WParam: Word; const LParam: NativeInt);
        procedure DoThreadReceiveMessage(const Msg: Word; const WParam: Word; const LParam: NativeInt);
        procedure DoTimeout;
        procedure Execute; override;
        function GetTimeOut: Cardinal; virtual;
            // процедура для очистки памяти в сообщениях, которые остались в очереди сообщений
            // при уничтожении потока
        procedure FreeMessage(const Msg: Word; const WParam: Word; const LParam: NativeInt); virtual;
    public
        constructor Create(const AThreadName: string
        {$IFDEF WTHREAD_DEBUG_LOG}
                ; AUseDebugLog: Boolean
        {$ENDIF}
            ); overload;
        constructor Create(const AThreadName: string; CreateSuspended
        {$IFDEF WTHREAD_DEBUG_LOG}
                , AUseDebugLog
        {$ENDIF}
                : Boolean); overload;
        destructor Destroy; override;
            // Две процедуры, которые выполняются в контексте потока. В начале и в конце.
        procedure InitThread; virtual;
        procedure DoneThread; virtual;
            // см TimeOutIsDirect, при true - перекрыть следующую процедуру
        procedure DirectTimeOut; virtual;
            // отправка любого сообщения В этот поток
        function PostToThreadMessage(const Msg: Word; const WParam: Word; const LParam: NativeInt): Boolean;
            // остановка потока по феншую
        procedure StopThread;
            // событие на получение данных ИЗ этого потока, вызывается в основном потоке
        property OnThreadReceiveMessage: TWThreadReceiveMessage read FOnThreadReceiveMessage write FOnThreadReceiveMessage;
            // доп.поток, которому отправляются сообщения с помощью PostMessageToWThread
        property OwnerWThread: TWThread read FOwnerWThread write FOwnerWThread;
            // событие, возникающее при превышении интервала ожидания
        property OnTimeOut: TWThreadTimeOut read FOnTimeOut write FOnTimeOut;
            // интервал ожидания в мс, по-умолчанию - бесконечно
        property TimeOut: Cardinal read GetTimeOut write SetTimeOut default INFINITE;
            // true = Будет вызван DirectTimeOut из этого потока, false = вызвано событие OnTimeOut в основном потоке.
        property TimeOutIsDirect: boolean read FTimeOutIsDirect write FTimeOutIsDirect default false;
        {$IFDEF WTHREAD_DEBUG_LOG}
        property UseDebugLog: boolean read FUseDebugLog write FUseDebugLog default false;
        {$ENDIF}
        property ThreadName: string read FThreadName;
    end;

    // подготовка строки к обмену между потоками
function NewString(const Text: string): NativeInt;

    // возвращение строки к привычному виду после приема из другого потока
function FreeString(var P: NativeInt): String;

implementation

const
    WM_INTERNAL_BASE    = WM_USER+$100;
    WM_TIMEOUT          = WM_INTERNAL_BASE+1;

    SizeOfChar          = SizeOf(Char);

    // константы из DateUtils
    OneMillisecond      = 1 / MSecsPerDay;

{$IFDEF ALLOC_STRING}
{$IFDEF ALL_WIN}
// для передачи строки между программами
function GlobalNewString(const Text: string): NativeInt;
var l: Integer;
    p: pointer;
begin
    l := Length(Text)*SizeOfChar;
    if L > SizeOfChar then begin
        Result := GlobalAlloc(GHND, l+SizeOfChar);
        if LongBool(Result) then begin
            {$HINTS OFF}
            p := GlobalLock(Result);
            {$HINTS ON}
            Move(Pointer(Text)^, Pointer(p)^, l);
            GlobalUnlock(Result);
        end
    end else
        Result := 0;
end;

// для передачи строки между программами
function GlobalFreeString(var P: NativeInt): String;
var ps: pointer;
begin
    if LongBool(P) then begin
        {$HINTS OFF}
        ps := GlobalLock(P);
        {$HINTS ON}
        SetLength(Result, Length(PChar(ps)));
        Move(Pointer(ps)^, Pointer(Result)^, Length(Result)*SizeOfChar);
        GlobalUnlock(P);
        P := GlobalFree(P);
    end;
end;
{$ENDIF}

// для передачи строки в пределах одной программы
function FreeString(var P: NativeInt): String;
begin
    {$HINTS OFF}
    if LongBool(P) then begin
        SetLength(Result, Length(PChar(P)));
        Move(Pointer(P)^, Pointer(Result)^, Length(Result)*SizeOfChar);
        {$IFDEF ALL_WIN}
        P := LocalFree(HLOCAL(P));
        {$ELSE}
        P := FreeMem(Pointer(P));
        {$ENDIF}
    end;
end;

// для передачи строки в пределах одной программы
function NewString(const Text: string): NativeInt;
var l: Integer;
begin
    l := Length(Text)*SizeOfChar;
    if L > SizeOfChar then begin
        {$IFDEF ALL_WIN}
        Result := LocalAlloc(LPTR, l+SizeOfChar);
        {$ELSE}
        Pointer(Result) := AllocMem(l+SizeOfChar);
        {$ENDIF}
        if LongBool(Result) then
            Move(Pointer(Text)^, Pointer(Result)^, l);
        {$HINTS ON}
    end else
        Result := 0;
end;
{$ELSE}
function NewString(const Text: string): NativeInt;
begin
    Result := 0;
    string(Result) := Text;
end;

function FreeString(var P: NativeInt): String;
begin
    Result := '';
    NativeInt(Result) := P;
end;
{$ENDIF}

constructor TWEvent.Create;
begin
    {$IFDEF WINCE}
    FWHandle := CreateEvent(nil, false, false, nil);
    {$ELSE}
    inherited Create(nil, false, false, '');
    {$ENDIF}
end;

destructor TWEvent.Destroy;
begin
    {$IFDEF WINCE}
    CloseHandle(FWHandle);
    {$ELSE}
    inherited;
    {$ENDIF}
end;

procedure TWEvent.WResetEvent;
begin
    {$IFDEF WINCE}
    Windows.ResetEvent(FWHandle);
    {$ELSE}
    inherited ResetEvent;
    {$ENDIF}
end;

procedure TWEvent.WSetEvent;
begin
    {$IFDEF WINCE}
    Windows.SetEvent(FWHandle);
    {$ELSE}
    inherited SetEvent;
    {$ENDIF}
end;

function TWEvent.WWaitFor(const TimeOut: Cardinal): TWaitResult;
{$IFDEF WINCE}
var
    dw: DWord;
begin
    dw := WaitForSingleObject(FWHandle, TimeOut);
    case dw of
        WAIT_ABANDONED: result := wrAbandoned;
        WAIT_OBJECT_0: result := wrSignaled;
        WAIT_TIMEOUT: result := wrTimeout;
    else
        Result := wrError;
    end;
{$ELSE}
begin
    result := WaitFor(TimeOut);
{$ENDIF}
end;

type

    { TGUIThread }

    TGUIThread = class(TThread)
    private
        FMessageEvent: TWEvent;
        FTimeOut: Boolean;
        FOwner: TWThread;
        FQueue: TQueue;
        FSection: TCriticalSection;
        FCurrentMessage: TThreadMessage;
        procedure FreeQueue;
    protected
        procedure Execute; override;
        procedure CallGUIThread;
    public
        constructor Create(AOwner: TWThread); overload;
        destructor Destroy; override;
        procedure PostMessage(const Message: DWord; const WParam: Word; const LParam: NativeInt);
        procedure StopThread;
    end;

{ TGUIThread }

procedure TGUIThread.FreeQueue;
var PMsg: PThreadMessage;
begin
    while FQueue.Count > 0 do begin
        PMsg := FQueue.Pop;
        FOwner.FreeMessage(PMsg^.Message, PMsg^.WParam, PMsg^.LParam);
    end;
end;

procedure TGUIThread.Execute;
var Message: PThreadMessage;
    wr: TWaitResult;
begin
    while not Terminated do begin
        wr := FMessageEvent.WWaitFor(INFINITE);
        if (not Terminated) and (wr = wrSignaled) then begin
            if FTimeOut then begin
                FTimeOut := False;
                if Assigned(FOwner.FOnTimeOut) then
                    FOwner.FOnTimeOut(FOwner);
            end;
            while FQueue.Count > 0 do begin
                FSection.Enter;
                Message := FQueue.Pop;
                FSection.Leave;
                FCurrentMessage := Message^;
                FreeMem(Message);
                if (FCurrentMessage.Message >= WM_THREAD_BASE)
                    and(FCurrentMessage.Message <= WM_THREAD_MAX) then
                    {$IFDEF FPC}
                    Synchronize(@CallGUIThread);
                    {$ELSE}
                    Synchronize(CallGUIThread);
                    {$ENDIF}
            end;
        end;
    end;
end;

procedure TGUIThread.CallGUIThread;
begin
    if Assigned(FOwner) then
        FOwner.DoThreadReceiveMessage(FCurrentMessage.Message, FCurrentMessage.WParam, FCurrentMessage.LParam);
end;

constructor TGUIThread.Create(AOwner: TWThread);
begin
    inherited Create(False);
    FMessageEvent := TWEvent.Create;
    FQueue := TQueue.Create;
    FSection := TCriticalSection.Create;
    FOwner := AOwner;
end;

destructor TGUIThread.Destroy;
begin
    FreeQueue;
    FSection.Free;
    FQueue.Free;
    FMessageEvent.Free;
    inherited Destroy;
end;

procedure TGUIThread.PostMessage(const Message: DWord; const WParam: Word; const LParam: NativeInt);
var Msg: PThreadMessage;
begin
    GetMem(Msg, SizeOf(TThreadMessage));
    Msg^.Message := Message;
    Msg^.WParam := WParam;
    Msg^.LParam := LParam;
    FSection.Enter;
    FQueue.Push(Msg);
    FSection.Leave;
    FMessageEvent.WSetEvent;
end;

procedure TGUIThread.StopThread;
begin
    Terminate;
    FMessageEvent.WSetEvent;
end;

{ TWThread }

constructor TWThread.Create(const AThreadName: string; CreateSuspended
        {$IFDEF WTHREAD_DEBUG_LOG}
        , AUseDebugLog
        {$ENDIF}
        : Boolean);
begin
    inherited Create(CreateSuspended);
    {$IFDEF WTHREAD_DEBUG_LOG}
    FUseDebugLog := AUseDebugLog;
    {$ENDIF}
    if Length(AThreadName) = 0 then
        FThreadName := 'Thread'
    else
        FThreadName := AThreadName;
    FQueue := TQueue.Create;
    FMessageEvent := TWEvent.Create;
    FSection := TCriticalSection.Create;
    FGUIThread := TGUIThread.Create(Self);
    FTimeOut := INFINITE;
    FTimeOutIsDirect := False;
end;

constructor TWThread.Create(const AThreadName: string
    {$IFDEF WTHREAD_DEBUG_LOG}
    ; AUseDebugLog: Boolean
    {$ENDIF}
    );
begin
    Create(AThreadName, false
        {$IFDEF WTHREAD_DEBUG_LOG}
        , AUseDebugLog
        {$ENDIF}
        );
end;

destructor TWThread.Destroy;
begin
    FMessageEvent.Free;
    FreeQueue;
    FQueue.Free;
    FGUIThread.Free;
    FSection.Free;
    inherited;
end;

procedure TWThread.DirectTimeOut;
begin
    // override
end;

// Процедура, выполняющая в контексте этого потока перед запуском очереди сообщений (в начале работы потока).
procedure TWThread.InitThread;
begin
    // empty
end;

// Процедура, выполняющая в контексте этого потока после отработки очереди сообщений (в конце работы потока).
procedure TWThread.DoneThread;
begin
    // empty
end;

procedure TWThread.DoThreadReceiveMessage(const Msg: Word; const WParam: Word; const LParam: NativeInt);
var ThreadMsg: TThreadMessage;
begin
    if Assigned(FOnThreadReceiveMessage) then begin
        ThreadMsg.Message := Msg;
        ThreadMsg.WParam := WParam;
        ThreadMsg.LParam := LParam;
        FOnThreadReceiveMessage(Self, ThreadMsg);
    end;
end;

procedure TWThread.DoTimeout;
begin
    TGUIThread(FGUIThread).FTimeOut := true;
    TGUIThread(FGUIThread).FMessageEvent.WSetEvent;
end;

// отправка любого сообщения В этот поток
function TWThread.PostToThreadMessage(const Msg: Word; const WParam: Word;
    const LParam: NativeInt): Boolean;
var PMsg: PThreadMessage;
begin
    GetMem(PMsg, SizeOf(TThreadMessage));
    PMsg^.Message := Msg;
    PMsg^.WParam := WParam;
    PMsg^.LParam := LParam;
    FSection.Enter;
    FQueue.Push(PMsg);
    FSection.Leave;
    {$IFDEF WTHREAD_DEBUG_LOG}
    intPostToLog(Format('send to me msg: %d (0x%0:x).', [Msg]), WLL_EXTRA);
    {$ENDIF}
    FMessageEvent.WSetEvent;
    result := true;
end;

{$IFDEF WTHREAD_DEBUG_LOG}
{$B-}
function TWThread.intPostToLog(const Text: string): boolean;
begin
    result := intPostToLog(Text, WLL_NORMAL);
end;

function TWThread.intPostToLog(const Text: string; const Level: TLogLevel): boolean;
begin
    result := FUseDebugLog and PostToLog(Format('%s(%d) : %s', [FThreadName, Handle, Text]), Level);
end;
{$ENDIF}

function TWThread.GetMsFromDateTime(const Value: TDateTime): Cardinal;
begin
    Result := Round((Now - Value) / OneMillisecond);
end;

procedure TWThread.SetTimeOut(const Value: Cardinal);
begin
    if FTimeOut <> Value then begin
        if (Value = 0) then
            FTimeOut := INFINITE
        else
            FTimeOut := Value;
        if not Suspended then
            FMessageEvent.WSetEvent;
    end;
end;

procedure TWThread.FreeQueue;
var PMsg: PThreadMessage;
begin
    while FQueue.Count > 0 do begin
        PMsg := FQueue.Pop;
        FreeMessage(PMsg^.Message, PMsg^.WParam, PMsg^.LParam);
    end;
end;

procedure TWThread.Execute;
var
    internalTimeout, ms: Cardinal;
    busy: TDateTime;
    Message: PThreadMessage;
    wr: TWaitResult;
begin
    {$IFDEF WTHREAD_DEBUG_LOG}
    intPostToLog('started.');
    {$ENDIF}
    InitThread;
    internalTimeout := TimeOut;
    busy := 0;
    {$IFDEF WTHREAD_DEBUG_LOG}
    intPostToLog('main loop started.', WLL_EXTRA);
    {$ENDIF}
    while not Terminated do begin
        wr := FMessageEvent.WWaitFor(internalTimeout);
        busy := Now;
        if not Terminated then
            case WR of
                wrSignaled: begin
                    {$IFDEF WTHREAD_DEBUG_LOG}
                    intPostToLog(Format('got signal (queue cnt: %d).', [FQueue.Count]), WLL_EXTRA);
                    {$ENDIF}
                    while FQueue.Count > 0 do begin
                        FSection.Enter;
                        Message := FQueue.Pop;
                        FSection.Leave;
                        if (Message^.message >= WM_THREAD_BASE)
                            and (Message^.Message <= WM_THREAD_MAX) then begin
                                {$IFDEF WTHREAD_DEBUG_LOG}
                                intPostToLog(Format('dispatch msg %d (0x%0:x).', [Message^.Message]), WLL_EXTRA);
                                {$ENDIF}
                                Dispatch(Message^);
                            end else if (Message^.message >= WM_INTERNAL_BASE)
                                and (Message^.Message <= WM_THREAD_BASE) then begin // внутренние сообщения
                            end;
                        FreeMem(Message);
                    end;
                end;
                wrTimeout: begin
                    internalTimeout := TimeOut;
                    if FTimeOutIsDirect then begin
                        {$IFDEF WTHREAD_DEBUG_LOG}
                        intPostToLog('DirectTimeOut.', WLL_EXTRA);
                        {$ENDIF}
                        DirectTimeOut;
                    end else begin
                        {$IFDEF WTHREAD_DEBUG_LOG}
                        intPostToLog('Sent TimeOut.', WLL_EXTRA);
                        {$ENDIF}
                        PostMessageFromThread(WM_TIMEOUT, 0, 0);
                        // сообщение не занимает много времени, на точность не влияет
                        busy := 0;
                    end;
                end;
            end;
        // корректируем интервал таймаута на время выполнения кода выше
        if (busy <> 0) then begin
            ms := GetMsFromDateTime(busy);
            if (ms > TimeOut) then
                internalTimeout := 0
            else
                internalTimeout := TimeOut - ms;
        end else begin
            internalTimeout := TimeOut;
        end;
    end;
    {$IFDEF WTHREAD_DEBUG_LOG}
    intPostToLog('main loop stoped.', WLL_EXTRA);
    {$ENDIF}
    DoneThread;
    {$IFDEF WTHREAD_DEBUG_LOG}
    intPostToLog('stoped.', WLL_EXTRA);
    {$ENDIF}
end;

// отправка сообщения из этого потока для вызова обработчика OnThreadReceiveMessage
procedure TWThread.PostMessageFromThread(const Msg: Word; const WParam: Word; const LParam: NativeInt);
begin
    if Assigned(FGUIThread) then begin
        TGUIThread(FGUIThread).PostMessage(Msg, WParam, LParam);
    {$IFDEF WTHREAD_DEBUG_LOG}
        intPostToLog(Format('send to receiver msg: %d (0x%0:x).', [Msg]), WLL_EXTRA);
    {$ENDIF}
    end
    {$IFDEF WTHREAD_DEBUG_LOG}
    else begin
        intPostToLog(Format('cant send to receiver msg: %d (0x%0:x).', [Msg]), WLL_EXTRA);
    end;
    {$ENDIF}
end;

procedure TWThread.PostMessageToWThread(const Msg: Word; const WParam: Word; const LParam: NativeInt);
begin
    if Assigned(FOwnerWThread) then begin
        FOwnerWThread.PostToThreadMessage(Msg, WParam, LParam);
   {$IFDEF WTHREAD_DEBUG_LOG}
        intPostToLog(Format('send to %s(%d) msg: %d (0x%2:x).', [FOwnerWThread.FThreadName, FOwnerWThread.Handle, Msg]), WLL_EXTRA);
    end else begin
        intPostToLog(Format('cant send to other wthread msg: %d (0x%0:x).', [Msg]), WLL_EXTRA);
    {$ENDIF}
    end;
end;

// отправка любого сообщения В этот поток
procedure TWThread.StopThread;
begin
    TGUIThread(FGUIThread).StopThread;
    Terminate;
    FMessageEvent.WSetEvent;
    {$IFDEF WTHREAD_DEBUG_LOG}
    intPostToLog('stop signal.');
    {$ENDIF}
end;

function TWThread.GetTimeOut: Cardinal;
begin
    Result := FTimeOut;
end;

procedure TWThread.FreeMessage(const Msg: Word; const WParam: Word; const LParam: NativeInt);
begin
    // эту процедуру необходимо перекрыть для освобождения памяти, которая была выделена и
    // передана через очередь в этот или из этого потока (через GUIThread)
    // исп. в FreeQueue
end;

end.



wlog.pas
Код: 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.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
unit wlog;
// wlog (c) wadman, 2015, 15.05.2015
// multithread safe logging

interface

// log level
type
    TLogLevel = (WLL_MINIMUM, WLL_NORMAL, WLL_MAXIMUM, WLL_EXTRA);

var // имя лог-файла (по ум. имя исполняемого файла с расширением log
    WLogFileName: string;
    // логирование включено (по ум. да)
    WLogEnabled: boolean;
    // очищать лог при каждом запуске (по ум. да)
    WLogClearOnStart: boolean;
    // уровень логирования по ум. см TLogLevel (по ум. WLL_NORMAL, т.е. MAXIMUM и EXTRA не будут логироваться)
    WLogLevel: TLogLevel;

function PostToLog(const Text: string): boolean; overload; // with WLL_NORMAL
function PostToLog(const Text: string; const Level: TLogLevel): boolean; overload;

implementation

uses SysUtils, WThread;

const
    WM_LOG      = WM_THREAD_BASE + 1;

type
    PLogRecord = ^TLogRecord;
    TLogRecord = record
        DT: TDateTime;
        PString: NativeInt;
    end;

    TLogThread = class(TWThread)
    private
        procedure WMLog(var Msg: TThreadMessage); message WM_LOG;
    end;

var
    LogThread: TLogThread;
    FirstLine: boolean;

function PostToLog(const Text: string): boolean;
begin
    result := PostToLog(Text, WLL_NORMAL);
end;

function PostToLog(const Text: string; const Level: TLogLevel): boolean;
var p: PLogRecord;
    d: TDateTime;
begin
    if SmallInt(Level) <= SmallInt(WLogLevel) then begin
        result := WLogEnabled and LongBool(LogThread);
        if result then begin
            d := Now;
            p := AllocMem(SizeOf(TLogRecord));
            p^.DT := d;
            p^.PString := NewString(Text);
{$HINTS OFF}
            result := LogThread.PostToThreadMessage(WM_LOG, 0, NativeInt(p));
{$HINTS ON}
            if not result then begin
                FreeString(p^.PString);
                FreeMem(p);
            end;
        end;
    end else
        Result := true;
end;

function InitLogs: boolean;
begin
    WLogFileName := ChangeFileExt(ParamStr(0), '.log');
    LogThread := TLogThread.Create('LogThread', false);
    result := LongBool(LogThread);
end;

procedure DoneLogs;
begin
    if LongBool(LogThread) then begin
        LogThread.StopThread;
        LogThread.Free;
    end;
end;

function AddToLog(const DT: TDateTime; const Text: string): boolean;
var t: TextFile;
begin
    result := false;
    try
        AssignFile(t, WLogFileName);
        if (FileExists(WLogFileName))and(not WLogClearOnStart) then begin
            Append(t);
            if FirstLine then begin
                WriteLn(t, '');
            end;
        end else begin
            WLogClearOnStart := false;
            Rewrite(t);
        end;
        FirstLine := false;
        WriteLn(t, Format('%s : %s', [FormatDateTime('dd.mm.yyyy hh:nn:ss.zzz', DT), Text]));
        CloseFile(t);
        result := true;
    finally
    end;
end;

{ TLogThread }

procedure TLogThread.WMLog(var Msg: TThreadMessage);
var p: PLogRecord;
begin
{$HINTS OFF}
    p := PLogRecord(Msg.LParam);
{$HINTS ON}
    AddToLog(p^.DT, FreeString(p^.PString));
    FreeMem(p);
end;

initialization
    WLogLevel := WLL_NORMAL;
    WLogEnabled := InitLogs;
    FirstLine := true;
    WLogClearOnStart := false;

finalization
    DoneLogs;

end.



main.pas (форма с кнопкой и несколько обработчиков)
Код: 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.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
261.
262.
263.
264.
265.
266.
267.
268.
269.
270.
271.
272.
273.
unit main;

{$IFDEF FPC}
{$mode objfpc}{$H+}
{$ENDIF}

interface

uses
    Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,
    wthread, wlog;

const
    WM_START_SEARCH     = WM_THREAD_BASE + 1;
    WM_FILE_FOUND       = WM_THREAD_BASE + 2;
    WM_JOB_START        = WM_THREAD_BASE + 3;
    WM_JOB_DONE         = WM_THREAD_BASE + 4;

type

    // этот поток ищет файлы

    { TSearchThread }

    TSearchThread = class(TWThread)
        // ищет файлы по указанному пути и маске
        procedure WMStartSearch(var Msg: TThreadMessage); message WM_START_SEARCH;
    end;

    // этот поток обрабатывает файл

    { TJobThread }

    TJobThread = class(TWThread)
    private
        FIsBusy: boolean;
        procedure WMJobStart(var Msg: TThreadMessage); message WM_JOB_START;
        procedure DoneThread; override;
    public
        property IsBusy: boolean read FIsBusy write FIsBusy;
    end;

    // менеджер потоков

    { TManageThread }

    TManageThread = class(TWThread)
        // поток для поиска файлов
        FSearchThread: TSearchThread;
        // потоки для обработки файлов
        FJobThreads: array [0..3] of TJobThread;
        // найденные файлы
        FFoundFiles: TStringList;
        // запускает поток, который будет искать нужные файлы
        procedure WMStartSearch(var Msg: TThreadMessage); message WM_START_SEARCH;
        // запускает поток на обработку
        procedure WMFileFound(var Msg: TThreadMessage); message WM_FILE_FOUND;
        // обработка файла закончена: отдаем потоку следующий файл, если есть в FFoundFiles
        procedure WMJobDone(var Msg: TThreadMessage); message WM_JOB_DONE;
        // инициализация потока
        procedure InitThread; override;
        // деинициализация потока
        procedure DoneThread; override;
        // возвращает свободный поток для обработки
        function GetFreeJobThread: TJobThread;
    end;

type

    { TForm1 }

    TForm1 = class(TForm)
        Button1: TButton;
        procedure Button1Click(Sender: TObject);
        procedure FormCreate(Sender: TObject);
        procedure FormDestroy(Sender: TObject);
    private
        FManageThread: TManageThread;
        procedure StartManager;
        procedure StopManager;
    public
    end;

var
    Form1: TForm1;

implementation

{$IFDEF FPC}
{$R *.lfm}
{$ELSE}
{$R *.dfm}
{$ENDIF}

{ TManageThread }

procedure TManageThread.WMStartSearch(var Msg: TThreadMessage);
begin
    // 3) отдаем команду потоку искать файлы по пути и маске
    FSearchThread.PostToThreadMessage(WM_START_SEARCH, 0, Msg.LParam);
end;

procedure TManageThread.WMFileFound(var Msg: TThreadMessage);
var job: TJobThread;
begin
    // 7) найден файл: ищем свободный поток для его обработки
    job := GetFreeJobThread;
    if Assigned(job) then begin
        // 8) свободный поток найден: запускаем обработку, передав ему имя файла напрямую
        job.IsBusy := true;
        job.PostToThreadMessage(WM_JOB_START, 0, Msg.LParam);
    end else begin
        // 9) свободный поток не найден: запоминаем его для последующей обработки по сообщению WM_JOB_DONE
        FFoundFiles.Add(FreeString(Msg.LParam));
    end;
end;

procedure TManageThread.WMJobDone(var Msg: TThreadMessage);
var job: TJobThread;
begin
    // освободившийся поток
    job := TJobThread(Msg.LParam);
    // 11) поток обработчик освободился
    if FFoundFiles.Count > 0 then begin
        // 12) есть не обработанные файлы
        job.PostToThreadMessage(WM_JOB_START, 0, NewString(FFoundFiles[0]));
        FFoundFiles.Delete(0);
    end else begin
        // файлов на обработку нет: выставляем флаг незанятости
        job.IsBusy := false;
    end;
    // если файлов на обработку нет, то поток "засыпает" до следующей команды
end;

procedure TManageThread.InitThread;
var i: integer;
begin
    // эта процедура выполняется в контексте доп. потока
    // здесь происходит инициализация объекто/потоков
    FFoundFiles := TStringList.Create;
    FSearchThread := TSearchThread.Create('SearchThread', false, true);
    FSearchThread.OwnerWThread := Self;
    for i := Low(FJobThreads) to High(FJobThreads) do begin
        FJobThreads[i] := TJobThread.Create(FOrmat('JobThread-%d', [i]), false, true);
        FJobThreads[i].OwnerWThread := Self;
    end;
end;

procedure TManageThread.DoneThread;
var i: integer;
begin
    // эта процедура выполняется в контексте доп. потока
    // здесь мы останавливаем другие потоки и подчищаем за собой
    FSearchThread.StopThread;
    FSearchThread.Free;
    for i := Low(FJobThreads) to High(FJobThreads) do begin
        FJobThreads[i].StopThread;
        FJobThreads[i].Free;
    end;
    FFoundFiles.Free;
end;

function TManageThread.GetFreeJobThread: TJobThread;
var i: integer;
begin
    for i := Low(FJobThreads) to High(FJobThreads) do begin
        if not FJobThreads[i].IsBusy then
            exit(FJobThreads[i]);
    end;
    result := nil;
end;

{ TJobThread }

procedure TJobThread.WMJobStart(var Msg: TThreadMessage);
var fileName: string;
begin
    // 10) симулируем долгую, трудную обработку файла
    fileName := FreeString(Msg.LParam);
    PostToLog(Format('Job: %s', [fileName]));
    //Sleep(1000);
    // отправляем имя обработанного файла потоку-менеджеру.
    PostMessageToWThread(WM_JOB_DONE, 0, NativeInt(Self));
end;

procedure TJobThread.DoneThread;
begin
end;

{ TSearchThread }

procedure TSearchThread.WMStartSearch(var Msg: TThreadMessage);
var path_and_mask: string;
    path, mask: string;

  procedure Search(const APath, AMask: string);
  var sr: TSearchRec;
      i: integer;
      ar: TStringList;
  begin
      ar := TStringList.Create;
      ar.Delimiter := ';';
      ar.DelimitedText := AMask;
      for i := 0 to ar.Count-1 do
          if (not Terminated) and (FindFirst(APath + Trim(ar[i]), faReadOnly or faArchive, sr) = 0) then begin
              // 6) сообщаем менеджеру о найденном файле
              PostMessageToWThread(WM_FILE_FOUND, 0, NewString(IncludeTrailingBackslash(APath) + sr.Name));
              break;
          end else
              FindClose(sr);
      FindClose(sr);
      if FindFirst(APath + '*', faDirectory, sr) = 0 then begin
          // ищем во всем подпапках / рекурсивный поиск
          repeat
              if LongBool((sr.Attr and faDirectory)) then begin
                  if (sr.Name <> '.') and (sr.Name <> '..') then
                      Search(IncludeTrailingBackslash(APath+sr.Name), AMask);
              end;
          until Terminated or LongBool(FindNext(sr));
          FindClose(sr);
      end;
      ar.free;
  end;

begin
    // 4) получаем строку, переданную на шаге 2
    path_and_mask := FreeString(Msg.LParam);
    path := Copy(path_and_mask, 1, Pos('|', path_and_mask)-1);
    mask := Copy(path_and_mask, Pos('|', path_and_mask)+1, 255);
    // 5) запускаем рекурсивный поиск
    Search(path, mask);
end;

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
begin
    StopManager;
    StartManager;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
    // устанавливаем максимульную детальность лога
    WLogLevel := WLL_EXTRA;
    // лог очищается при каждом запуске
    WLogClearOnStart := true;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
    StopManager;
end;

procedure TForm1.StartManager;
begin
    // 1) запускаем поток менеджер
    FManageThread := TManageThread.Create('ManageThread', false, true);
    // 2) отдаем ему команду на начало поиска по пути и маске. | - разделитель между путем и масками, ; - разделитель между масками
    FManageThread.PostToThreadMessage(WM_START_SEARCH, 0, NewString('D:\|*.pas;*.inc'));
end;

procedure TForm1.StopManager;
begin
    if Assigned(FManageThread) then begin
        // если поток создан, то останавливаем его и уничтожаем
        FManageThread.StopThread;
        FManageThread.Free;
        FManageThread := nil;
    end;
end;

end.

...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #38959880
Фотография wadman
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Забыл упомянуть кое-что важное: теперь потоки могут общаться между собой минуя главный поток (VCL/LCL).
Код: pascal
1.
2.
        // отправка сообщения из этого потока другому потоку (см. OwnerWThread)
        procedure PostMessageToWThread(const Msg: Word; const WParam: Word; const LParam: NativeInt);
...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #39008828
Фотография wadman
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
wadmanРешил полностью отказаться от общения между потоками с помощью оконной функции, которая в винде имеет некоторые ограничения
Ограничения, как оказалось, были во мне. Сам наложил фильтр на оконные сообщения...
Вернул все на место и подправил пару ошибок.
В этот раз без портянок.

main.pas - пример использования TWThread. Чтобы он заработал, нужно создать пустой проект, бросить на форму одну кнопку и заменить главный модуль main.pas (названия модуля должны совпадать).
В примере демонстрируется создание менеджера потоков, потока для поиска файлов и несколько потоков для обработки найденных файлов: Поток-менеджер собирает список найденных файлов, принимая их названия от потока-поисковика и координирует работу потоков-обработчиков.
Всё "общение" между потоками выводится в лог-файл.
...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #39008837
Фотография wadman
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Хотя нет, пример все таки выложу в портянку.
Код: 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.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
261.
262.
263.
264.
265.
266.
267.
268.
269.
270.
271.
272.
273.
274.
275.
276.
277.
278.
279.
280.
281.
282.
283.
unit main;

{$IFDEF FPC}
{$mode objfpc}{$H+}
{$ENDIF}

interface

uses
    Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls,
    wthread, wlog;

const
    WM_START_SEARCH     = WM_THREAD_BASE + 1;
    WM_FILE_FOUND       = WM_THREAD_BASE + 2;
    WM_JOB_START        = WM_THREAD_BASE + 3;
    WM_JOB_DONE         = WM_THREAD_BASE + 4;

type

    // этот поток ищет файлы

    { TSearchThread }

    TSearchThread = class(TWThread)
        // ищет файлы по указанному пути и маске
        procedure WMStartSearch(var Msg: TThreadMessage); message WM_START_SEARCH;
    end;

    // этот поток обрабатывает файл

    { TJobThread }

    TJobThread = class(TWThread)
    private
        FIsBusy: boolean;
        procedure WMJobStart(var Msg: TThreadMessage); message WM_JOB_START;
        procedure FreeMessage(const Msg: Word; const WParam: Word; const LParam: NativeInt); override;
    public
        property IsBusy: boolean read FIsBusy write FIsBusy;
    end;

    // менеджер потоков

    { TManageThread }

    TManageThread = class(TWThread)
        // поток для поиска файлов
        FSearchThread: TSearchThread;
        // потоки для обработки файлов
        FJobThreads: array [0..3] of TJobThread;
        // найденные файлы
        FFoundFiles: TStringList;
        // запускает другие потоки, которые будут искать и обрабатывать файлы
        procedure WMStartSearch(var Msg: TThreadMessage); message WM_START_SEARCH;
        // запускает поток на обработку
        procedure WMFileFound(var Msg: TThreadMessage); message WM_FILE_FOUND;
        // обработка файла закончена: отдаем потоку следующий файл, если есть в FFoundFiles
        procedure WMJobDone(var Msg: TThreadMessage); message WM_JOB_DONE;
        // инициализация потока
        procedure InitThread; override;
        // деинициализация потока
        procedure DoneThread; override;
        // возвращает свободный поток для обработки
        function GetFreeJobThread: TJobThread;
        procedure FreeMessage(const Msg: Word; const WParam: Word; const LParam: NativeInt); override;
    end;

type

    { TForm1 }

    TForm1 = class(TForm)
        Button1: TButton;
        procedure Button1Click(Sender: TObject);
        procedure FormCreate(Sender: TObject);
        procedure FormDestroy(Sender: TObject);
    private
        FManageThread: TManageThread;
        procedure StartManager;
        procedure StopManager;
    public
    end;

var
    Form1: TForm1;

implementation

{$IFDEF FPC}
{$R *.lfm}
{$ElSE}
{$R *.dfm}
{$ENDIF}

{ TManageThread }

procedure TManageThread.WMStartSearch(var Msg: TThreadMessage);
begin
    // 3) отдаем команду потоку искать файлы по пути и маске
    FSearchThread.PostToThreadMessage(WM_START_SEARCH, 0, Msg.LParam);
end;

procedure TManageThread.WMFileFound(var Msg: TThreadMessage);
var job: TJobThread;
begin
    // 7) найден файл: ищем свободный поток для его обработки
    job := GetFreeJobThread;
    if Assigned(job) then begin
        // 8) свободный поток найден: запускаем обработку, передав ему имя файла напрямую
        job.IsBusy := true;
        job.PostToThreadMessage(WM_JOB_START, 0, Msg.LParam);
    end else begin
        // 9) свободный поток не найден: запоминаем его для последующей обработки по сообщению WM_JOB_DONE
        FFoundFiles.Add(FreeString(Msg.LParam));
    end;
end;

procedure TManageThread.WMJobDone(var Msg: TThreadMessage);
var job: TJobThread;
begin
    // освободившийся поток
    job := TJobThread(Msg.LParam);
    // 11) поток обработчик освободился
    if FFoundFiles.Count > 0 then begin
        // 12) есть не обработанные файлы
        job.PostToThreadMessage(WM_JOB_START, 0, NewString(FFoundFiles[0]));
        FFoundFiles.Delete(0);
    end else begin
        // файлов на обработку нет: выставляем флаг незанятости
        job.IsBusy := false;
    end;
    // если файлов на обработку нет, то поток "засыпает" до следующей команды
end;

procedure TManageThread.InitThread;
var i: integer;
begin
    // эта процедура выполняется в контексте доп. потока
    // здесь происходит инициализация объекто/потоков
    FFoundFiles := TStringList.Create;
    FSearchThread := TSearchThread.Create(Self, 'SearchThread', false, true);
    for i := Low(FJobThreads) to High(FJobThreads) do begin
        FJobThreads[i] := TJobThread.Create(Self, Format('JobThread-%d', [i]), false, true);
    end;
end;

procedure TManageThread.DoneThread;
var i: integer;
begin
    // эта процедура выполняется в контексте доп. потока
    // здесь мы останавливаем другие потоки и подчищаем за собой
    FSearchThread.StopThread;
    FSearchThread.Free;
    for i := Low(FJobThreads) to High(FJobThreads) do begin
        FJobThreads[i].StopThread;
        FJobThreads[i].Free;
    end;
    FFoundFiles.Free;
end;

procedure TManageThread.FreeMessage(const Msg, WParam: Word; const LParam: NativeInt);
var P: NativeInt;
begin
    P := LParam;
    FreeString(P);
end;

function TManageThread.GetFreeJobThread: TJobThread;
var i: integer;
begin
    for i := Low(FJobThreads) to High(FJobThreads) do begin
        if not FJobThreads[i].IsBusy then
            exit(FJobThreads[i]);
    end;
    result := nil;
end;

{ TJobThread }

procedure TJobThread.FreeMessage(const Msg, WParam: Word; const LParam: NativeInt);
var P: NativeInt;
begin
    P := LParam;
    FreeString(P);
end;

procedure TJobThread.WMJobStart(var Msg: TThreadMessage);
var fileName: string;
begin
    // 10) симулируем долгую, трудную обработку файла
    fileName := FreeString(Msg.LParam);
    PostToLog(Format('Job: %s', [fileName]));
    //Sleep(1000);
    // отправляем имя обработанного файла потоку-менеджеру.
    PostMessageToWThread(WM_JOB_DONE, 0, NativeInt(Self));
end;

{ TSearchThread }

procedure TSearchThread.WMStartSearch(var Msg: TThreadMessage);
var path_and_mask: string;
    path, mask: string;

  procedure Search(const APath, AMask: string);
  var sr: TSearchRec;
      i: integer;
      ar: TStringList;
  begin
      ar := TStringList.Create;
      ar.Delimiter := ';';
      ar.DelimitedText := AMask;
      for i := 0 to ar.Count-1 do
          if (not Terminated) and (FindFirst(APath + Trim(ar[i]), faAnyFile and (not faDirectory), sr) = 0) then begin
              repeat
                  // 6) сообщаем менеджеру о найденном файле
                  PostMessageToWThread(WM_FILE_FOUND, 0, NewString(IncludeTrailingBackslash(APath) + sr.Name));
              until Terminated or LongBool(FindNext(sr));
              break;
          end else
              FindClose(sr);
      FindClose(sr);
      if FindFirst(APath + '*', faDirectory, sr) = 0 then begin
          // ищем во всем подпапках / рекурсивный поиск
          repeat
              if LongBool((sr.Attr and faDirectory)) then begin
                  if (sr.Name <> '.') and (sr.Name <> '..') then
                      Search(IncludeTrailingBackslash(APath+sr.Name), AMask);
              end;
          until Terminated or LongBool(FindNext(sr));
          FindClose(sr);
      end;
      ar.free;
  end;

begin
    // 4) получаем строку, переданную на шаге 2
    path_and_mask := FreeString(Msg.LParam);
    path := Copy(path_and_mask, 1, Pos('|', path_and_mask)-1);
    mask := Copy(path_and_mask, Pos('|', path_and_mask)+1, 255);
    // 5) запускаем рекурсивный поиск
    Search(path, mask);
end;

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
begin
    StopManager;
    StartManager;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
    // устанавливаем максимульную детальность лога
    WLogLevel := WLL_EXTRA;
    WLogClearOnStart := true;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
    StopManager;
end;

procedure TForm1.StartManager;
begin
    // 1) запускаем поток менеджер
    FManageThread := TManageThread.Create(nil, 'ManageThread', false, true);
    // 2) отдаем ему команду на начало поиска по пути и маске. | - разделитель между путем и масками, ; - разделитель между масками
    FManageThread.PostToThreadMessage(WM_START_SEARCH, 0, NewString('D:\!\|*.pas'));
end;

procedure TForm1.StopManager;
begin
    if Assigned(FManageThread) then begin
        // если поток создан, то останавливаем его и уничтожаем
        FManageThread.StopThread;
        FManageThread.Free;
        FManageThread := nil;
    end;
end;

end.

...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #39008879
Valery_B
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
wadman,

Procedure Execute у тебя явно в вне общего контекста... т.е. Слишком громоздкая
И зачем там столько IFDEF ? По моему, только глаза мазолят,а польза, на мой взгляд, сомнительна...
...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #39008887
Фотография wadman
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Valery_B Слишком громоздкая
И зачем там столько IFDEF ?
На это обратил внимание?
Код: pascal
1.
// модуль для работы с доп.потоками Delphi&Lazarus (win&wince&*nix)



Если ТАКИЕ объемы исходников тебя пугают, то никогда не заглядывай в VCL/LCL.
...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #39008908
Valery_B
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
wadman,

Я сказал только про один метод. Он действительно громоздкий.
Я бы сделал вот так:

Код: pascal
1.
2.
3.
4.
5.
if not Terminated then
   case WR of
     wrSignaled: DoWrSignaled(<парметры>);
     wrTimeout: DoWrTimeout(<параметры>);
   end;



+ ещё вынес бы пару методов.
Что бы в результате в Execute осталось 3-5 строчек.
...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #39008939
Фотография wadman
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Valery_BЯ бы сделал вот так:
Мысль понял, но так в принципе не получится, ибо бряки и континуумы еще нужны. А это сразу +3 строчки в каждый кейс. :)
Ну и да, в дельфи все это конечно громоздко выглядит, я предпочитаю последнее время в лазарусе редактировать, там ифдефы учитываются при отрисовке исходников.
...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #39009011
Valery_B
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
wadman, согласись, что так гораздо лучше.

Твои IFDEF LOG я удалил все принципиально. Можешь вернуть, если надо.
У меня на всё про всё заняло 10 минут.
Но результат проверить не могу :)

Код: 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.
73.
74.
75.
76.
77.
78.
TWThread = class(TThread)
 private
  .... 
  FInternalTimeout: Cardinal;
  FBusy: TDateTime;
  Procedure DoWrSignaled;
  Procedure DoWrTimeout;
  Procedure ProcessLoop;
  Procedure CalculateTimeOut;
end;


Procedure TWThread.DoWrTimeout;
begin
 FInternalTimeout := TimeOut;
 if FTimeOutIsDirect then DirectTimeOut
  else
 begin
  PostMessageFromThread(WM_TIMEOUT, 0, 0);    // сообщение не занимает много времени, на точность не влияет
  FBusy := 0;
 end;
end;


Procedure TWThread.DoWrSignaled;
var
  Msg: PThreadMessage;
begin
   while FQueue.Count > 0 do
    begin
     FSection.Enter;
     Msg := FQueue.Pop;
     FSection.Leave;
    if (msg^.message >= WM_THREAD_BASE) and (msg^.Message <= WM_THREAD_MAX) then
      Dispatch(Msg^)
       else
      FreeMem(msg);
    end;
end;

Procedure TWThread.CalculateTimeOut;
var ms: Cardinal;
begin
  if (FBusy <> 0) then
   begin
    ms := GetMsFromDateTime(FBusy);
    if (ms > TimeOut) then
        FInternalTimeout := 0
         else
        FinternalTimeout := TimeOut - ms;
    end
   else
     FinternalTimeout := TimeOut;
end;

Procedure TWThread.ProcessLoop;
var
 wr: TWaitResult;
begin
 wr := FMessageEvent.WWaitFor(FInternalTimeout);
 FBusy := Now;
  if not Terminated then
    case WR of
     wrSignaled: DoWrSignaled;
     wrTimeout: DoWrTimeOut;
    end;
  CalculateTimeout;        // корректируем интервал таймаута на время выполнения кода выше
end;

procedure TWThread.Execute;
begin
 InitThread;
 FInternalTimeout := TimeOut;
 FBusy := 0;
 while not Terminated do
  ProcessLoop;
 DoneThread;
end;

...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #39009023
Фотография wadman
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Valery_BНо результат проверить не могу :)
И это печально. :)
Это не взлетит, как минимум при общении между разными процессами под виндой, собственно почему я и вернул общение сообщениями.

П.С. Если тебе нужен именно такой вариант, то забирай, меняй как хочешь и пользуйся. Но зачем выкладывать нерабочий вариант, который даже не проверен (windows, wince, linux)?
...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #39009029
Valery_B
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
wadman Но зачем выкладывать нерабочий вариант

Как ни странно, это -твой вариант. Я просто разделил его на 5 методов.
...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #39009042
Фотография wadman
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Valery_BКак ни странно, это -твой вариант.
Мой - рабочий и проверенный на всех платформах.
...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #39009049
Valery_B
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
wadman,

Покажи, тогда какая же строчка всё ломает ?
...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #39009056
Фотография wadman
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Valery_BПокажи, тогда какая же строчка всё ломает ?
Хватит жечь напалмом. Твой модуль банально не компилируется, а переделывать и тестировать во всех нужных мне окружениях не вижу смысла. Он даже о сообщениях от других процессов не подозревает.
...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #39009071
Valery_B
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
wadman Твой модуль

Жаль только, что моего здесь ничего нету.
...
Рейтинг: 0 / 0
Шаблон класса для работы с потоком (WThread, Thread)
    #39009310
Фотография Gator
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Valery_Bwadman Твой модуль

Жаль только, что моего здесь ничего нету.Но перед этим
Valery_BТвои IFDEF LOG я удалил все принципиально. Можешь вернуть, если надо.
У меня на всё про всё заняло 10 минут.
Но результат проверить не могу :)А ещё что удалил/поправил?

Принцип знаешь? Работает? Не троржь!!!
...
Рейтинг: 0 / 0
25 сообщений из 469, страница 11 из 19
Форумы / Delphi [игнор отключен] [закрыт для гостей] / Шаблон класса для работы с потоком (WThread, Thread)
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


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