Dynamiczne tworzenie równoległych wątków

Dynamiczne tworzenie równoległych wątków
Pepe
  • Rejestracja:ponad 22 lata
  • Ostatnio:około 9 godzin
  • Postów:496
0

--- > Wzywam wszystkich ekspertów od aplikacji wielowątkowych!!! :)

Mój program (bez GUI) tworzy wiele plików (osobno dla każdego obsługiwanego języka) i zapisuje je w odpowiednim miejscu i formacie na dysku.
W moim odczuciu trwa to jednak nieco za długo (30s). Pomyślałem, że może warto użyć osobnych wątków dla każdego pakietu generowanych plików dla danego języka?
Zaznaczam, że program nie wie ile jest języków (to info odczytuje po uruchomieniu) - wątek może być jeden albo X (np. siedem).

Zatem, potrzeba utworzyć X wątków, które wygenerują pliki dla danego języka w tym samym czasie (teraz robię jeden język po drugim w pętli).
Załóżmy, że mam 5 obsługiwanych języków. Chciałbym, żeby program utworzył X=5 wątków i wykonał je równolegle. Po zakończeniu wszystkich program ma się zamknąć.
Wątki nie muszą komunikować się ze sobą ani synchronizować z głównym wątkiem. Mają zrobić swoją robotę i się zamknąć.

Nie mam pojęcia jak się do tego zabrać, bo też w sumie nigdy nie używałem wątków na poważnie.

Pytanie:

  • Jak to zrobić :P
  • Jak zadeklarować klasę potomną od TThread dla wielu wątków?
  • Co z metodą Execute? Jest wspólna dla wszystkich X wątków czy każdy ma swoją?

Jakby ktoś podrzucił jakiś przykład kodu (nawet najbardziej prymitywny) - byle pokazywał jak odpalić np. 3 wątki...
Głównie chodzi mi o to, że nie wiem ile watków ma być. Normalnie zrobiłbym coś takiego (gdybym wiedział, że wątki są tylko np. 2):

Kopiuj
// PIERWSZY JĘZYK
 TWatek_Jezyk_1 = class(TThread)
 private
 protected
   procedure Execute; override;
 end;

procedure TWatek_Jezyk_1.Execute;
begin
  FreeOnTerminate := True;
  while not (Application.Terminated) or (Terminated) do
    begin
       // Tworzenie plików dla 1 języka
    end;
end;

// DRUGI JĘZYK
 TWatek_Jezyk_2 = class(TThread)
 private
 protected
   procedure Execute; override;
 end;

procedure TWatek_Jezyk_2.Execute;
begin
  FreeOnTerminate := True;
  while not (Application.Terminated) or (Terminated) do
    begin
       // Tworzenie plików dla 2 języka
    end;
end;

Ale jak pisałem nie wiem ile ich ma być...

Być może pytam o proste i oczywiste rzeczy, albo źle zadaję pytanie... nie znam się na programowaniu wielo-wątkowym.
Program jest prosty i potrzebuję tylko przyspieszyć jego działanie.
Mam nadzieję, że rozumiecie o co mi chodzi.

Proszę o pomoc.
Z góry dziękuję.


Zobacz pozostałe 3 komentarze
Pepe
Uprzejmie donoszę, że kolegi nie rozumiem. Co mają nazwy do tego, nie ogarniam.
PerlMonk
@Pepe: To troll. Pisze od rzeczy. Zaraz znów pewnie kogoś wyzwie i dostanie bana.
masterc
Chodzi o złe praktyki, nawt chinczycy pisza kod po angielsku. Po prostu sugeruje tylko zeby nie uzywac polskich nazw a reszta zrob jak pisza koledzy nizej. p.s. wiesz jak ludzie odbieraja donosicieli ?
Pepe
Mistrzu. Nie interesuje mnie co ludzie odbierają. W pełni się zgadzam, że kod winien być po angielsku. Nigdzie nie napisałem inaczej. Chodzi o to, czego najwyraźniej nie potrafisz zrozumieć, że tutaj użyłem prostego przykładu dla zrozumienia o co mi chodzi. Dzięki za zainteresowanie. Szkoda jednak, że straciłeś czas na pisanie pierdół, zamiast pochylić się nad realnym problemem.
masterc
To jest pascal i delphi jezyki 4 generacji w tym nikt juz nie robi, szkoda czasu na to. Robilem watki , po prostu otwierasz watek wysylasz funkcje i kolejny i kolejny nic wiecej, zadna filozofia Panie
abrakadaber
abrakadaber
  • Rejestracja:ponad 12 lat
  • Ostatnio:7 miesięcy
  • Postów:6610
6

kod ze środka pętli zamykasz w metodzie execute wątku a przy tworzeniu wątku przekazujesz to od czego zależy dla jakiego języka jest generowany plik. Klasę wątku tworzysz JEDNĄ! a jedynie jej instancji tyle ile potrzebujesz plików wygenerować.
Pokaż pętlę to będzie można coś konkretniej powiedzieć


Chcesz pomocy - pokaż kod - abrakadabra źle działa z techniką.
Pepe
  • Rejestracja:ponad 22 lata
  • Ostatnio:około 9 godzin
  • Postów:496
0

OK, rozumiem. Dla jednego wątku. Co jak jest ich więcej?
Jak pisałem, mogę nie wiedzieć o czym piszę, dlatego wklejam pseudo-kod pętli...
Pętla jak pętla, nie wiem co pomoże tutaj:

Info: Z pliku odczytuje nazwy języków, dla których mam utworzyć pliki\katalogi. Sprawdzam, czy mam je użyć i czy są "oficjalne". Nieważne co to znaczy.
I w każdej iteracji odpalam funkcje, której zapodaje dane (Katalog docelowy, Kod języka i parę innych). AInstallDir jest parametrem przekazanym wcześniej.

Kopiuj
var
   SL_LANG       : TStringList;
   MEM_INI       : TMemIniFile;
   sConfigFile   : string;

   sLangSection      : string;
   i                 : Integer;
   iInstall          : Integer;
   iOfficial         : Integer;
begin
   result := True;

   sConfigFile := AInstallDir + '\' + 'LangConfig.ini';
   MEM_INI := TMemIniFile.Create(sUFMConfigINI);

   SL_LANG := TStringList.Create;
   MEM_INI := TMemIniFile.Create(sConfigFile);
   try
      MEM_INI.ReadSections(SL_LANG); // Load Sections (1045 | 1033 | etc)
      for i := 0 to SL_LANG.Count-1 do
         begin
            sLangSection := SL_LANG.Strings[i];

            iInstall     := MEM_INI.ReadInteger(sLangSection, 'Install', 0); // Install Language
            iOfficial    := MEM_INI.ReadInteger(sLangSection, 'Official', 0); // Official language
            if (iInstall = 1) AND (iOfficial = 1) then
               begin
                  result := CREATE_FILE_FOR_GIVEN_LANGUAGE_ACTION(AInstallDir, sLangSection);
               end; // Supported Languages
         end; // Lang Loop
   finally
      SL_LANG.Free;
      MEM_INI.Free;
   end;
end;

Proszę o pokazanie przykładowego kodu z tworzeniem klasy wątku i potomnych.

-Pawel


edytowany 3x, ostatnio: Pepe
GS
  • Rejestracja:ponad 14 lat
  • Ostatnio:3 minuty
1

@Pepe:

Kopiuj
type
  TlanguageThread = class(TThread)
  public
    languageId: integer;
  protected
    procedure Execute; override;
  end;

procedure TlanguageThread.Execute;
begin
  FreeOnTerminate := true;
  case languageId of
    1:
      // jakaś akcja 1
      ;
    2:
      // jakaś akcja 2
      ;
    3:
      // jakaś akcja 3
      ;
  end;
end;
Kopiuj
procedure TForm1.Button1Click(Sender: TObject);
var
  i: integer;
  lThread: TlanguageThread;
begin
  for i := 1 to 3 do
  begin
    lThread := TlanguageThread.Create(true);
    lThread.languageId := i;
    lThread.Start;
  end;
end;

pisane z głowy .....

edytowany 2x, ostatnio: grzegorz_so
Pepe
  • Rejestracja:ponad 22 lata
  • Ostatnio:około 9 godzin
  • Postów:496
0

@grzegorz_so: Dziękuję!
Wygląda na to, że to jest bardzo proste. Sprawdzę ten kod.
Wygląda obiecująco i wydaje się, że jest zapisem sugestii @abrakadaber
-Pawel


GS
  • Rejestracja:ponad 14 lat
  • Ostatnio:3 minuty
0

@Pepe:
w twoim przypadku główna pętla

Kopiuj
  while not (Application.Terminated) or (Terminated) do 

w kodzie metody exceute jest zbędna ponieważ kod metody ma się wykonać tylko raz

edytowany 1x, ostatnio: grzegorz_so
Pepe
  • Rejestracja:ponad 22 lata
  • Ostatnio:około 9 godzin
  • Postów:496
0

@grzegorz_so: Tak, jeszcze się zastanawiam nad jednym aspektem. Wątek główny musi wiedzieć kiedy zakończyć działanie.
Pasowałoby jakoś poinformować, że wszystkie wątki poboczne zakończyły działanie... muszę nad tym pomyśleć jeszcze.


GS
  • Rejestracja:ponad 14 lat
  • Ostatnio:3 minuty
1

@Pepe:
Jakiej wersji Delphi używasz ?
W nowszych delphi jest klasa TInterlocked w module System.SyncObjs.TInterlocked której można użyć do tego celu

Pepe
Najnowszej CE. Na razie dumam jak to wrzucić do kodu...
GS
  • Rejestracja:ponad 14 lat
  • Ostatnio:3 minuty
0
Kopiuj
procedure TForm1.Button1Click(Sender: TObject);
var
  i: integer;
  lThread: TlanguageThread;
begin
 threadsCounter := 3;
  for i := 1 to 3 do
  begin
    lThread := TlanguageThread.Create(true);
    lThread.languageId := i;
    lThread.Start;
  end;
  while threadsCounter <> 0 do
    sleep(100);
  showmessage('Wszytskie wątki zamknięte ');
end;
Kopiuj
unit Unit2;

interface

uses
  Classes, Windows, system.SyncObjs;

type
  TlanguageThread = class(TThread)
  public
    languageId: integer;
  protected
    procedure Execute; override;
  end;

var
  threadsCounter: integer;

implementation

procedure TlanguageThread.Execute;
begin
  self.FreeOnTerminate := true;
  try
    case languageId of
      1:
        // jakaś akcja 1
        ;
      2:
        // jakaś akcja 2
        ;
      3:
        // jakaś akcja 3
        ;
    end;
  finally
    TInterlocked.Decrement(threadsCounter);
  end;
end;
edytowany 3x, ostatnio: grzegorz_so
Pepe
Dzięki za starania! Powoli się tutaj poddaje... chodzi o to, że Twój kod zakłada 3 wątki (Metoda Execute, instrukcja Case). A ich może być 4, 7 czy 10...
GS
liczba wątków nie jest żadnym problemem
GS
pytałeś o mechanizm dynamicznego tworzenia wielu wątków i kontroli ich zakończenia. case użyłem w celach przykładowych
Pepe
Tak. Pozwól mi to przetrawić :) Za szybki jesteś dla mnie :P
GS
  • Rejestracja:ponad 14 lat
  • Ostatnio:3 minuty
2
Kopiuj
var
  languagesCount: integer;

procedure TForm1.Button1Click(Sender: TObject);
var
  i: integer;
  lThread: TlanguageThread;
begin
  languagesCount := 3; /// albo jakakolwiek inna wartość

  threadsCounter := languagesCount;
  for i := 1 to languagesCount do
  begin
    lThread := TlanguageThread.Create(true);
    lThread.languageId := i;
    lThread.Start;
  end;
  while tinterlocked.Add(threadsCounter, 0) <> 0 do
    sleep(100);
  showmessage('Wszytskie wątki zamknięte ');
end;
Kopiuj
type
  TlanguageThread = class(TThread)
  public
    languageId: integer;
  protected
    procedure processLanguage(aId: integer);
    procedure Execute; override;
  end;

var
  threadsCounter: integer;

implementation

procedure TlanguageThread.Execute;
begin
  self.FreeOnTerminate := true;
  try
    processLanguage(self.languageId);
  finally
    TInterlocked.Decrement(threadsCounter);
  end;
end;

procedure TlanguageThread.processLanguage(aId: integer);
begin
  // tutaj obsługa wątku w zależności od wartości aID
end;
edytowany 2x, ostatnio: grzegorz_so
Pepe
  • Rejestracja:ponad 22 lata
  • Ostatnio:około 9 godzin
  • Postów:496
0

@grzegorz_so: Twój kod po dopasowaniu działa.
Dziękuję Ci. Na testowych plikach utworzenie 7 katalogów z plikami zajmowało 3.5s - przy zastosowaniu wątków - 0.5s.
Zatem, zgodnie z oczekiwaniami jest duży progres. Poczytam jeszcze o tym cudzie TInterLocked i ogólnie o wątkach, bo jak widać warto.
Wiem, że u mnie to jest trywialne zastosowanie, bo nie muszę synchronizować danych z GUI lub innymi wątkami, ale wydaje się działać.

Edit: Żeby nie było za łatwo. Część plików nie została utworzona (zatem czas się nieco wydłuży), pewnie jakaś kolizja po drodze, bo to w sumie testowe środowisko. Ale myślę, że to uporządkuje i będzie śmigać.


edytowany 1x, ostatnio: Pepe
flowCRANE
TInterlocked — to jedno słowo. :P
Pepe
  • Rejestracja:ponad 22 lata
  • Ostatnio:około 9 godzin
  • Postów:496
0

Wszystko ładnie działa. W aplikacji wielowątkowej uzyskałem 4.5s (normalnie 12s). Ale teraz łyżka dziegciu. Co z tego, że człowiek się stara, chce zminimalizować czas wykonania - skoro program antywirusowy WYDŁUŻA ten czas (nawet do 25s!). Teraz nie wiem, czy poświęcony czas na przepisanie aplikacji miał sens, bo tak czy siak, AV na komputerze użytkownika końcowego może skanować każdą operację i w efekcie zniwelować cały uzysk... eh :(


Pepe
  • Rejestracja:ponad 22 lata
  • Ostatnio:około 9 godzin
  • Postów:496
0

@grzegorz_so:
Pozwolę sobie przywołać ten wątek (nomen omen)...

Jak zmodyfikować ten kod, żeby móc użyć metody Synchronize?
Chciałbym pokazać postęp przy użyciu TProgressBar (np. 5 wątków, to co zakończony wątek, ustawiamy ProgressBar.Position na 20% więcej).

Mógłbyś podać prosty przykład wykorzystania Synchrnize (dostęp do wątka głównego) z ProgressBar?
Dzięki,
-Pawel


Pepe
  • Rejestracja:ponad 22 lata
  • Ostatnio:około 9 godzin
  • Postów:496
0

Przygotowałem prosty przykład o co mi chodzi. Thread Test.zip

Aplikacja ma pobrać dane w wielu wątkach (w dynamicznie tworzonej formie), wyświetlić postęp oraz opis danej akcji i pokazać główne okno.
Mam taki problem, że nie wiem jak wyświetlić Postęp (opis danej akcji oraz pasek postępu ProgressBar dla danej akcji).

Nie wiem jak zastosować metodę Synchronize (jak ją dodam to aplikacja zwiesza się (czeka na lepszą Polskę :P)... Z pewnością coś robię źle.
Poza tym, nie wiem jak przekazać informację w Synchronize, która akcja jest wykonywana, żeby pokazać odpowiedni tekst informacyjny.
Mam nadzieję, że opisałem to dobrze.

Bardzo proszę o wskazówki jak to zrobić.
Szczególnie Ciebie @grzegorz_so, bo Ty pokazałeś mi jak odpalić kilka wątków na raz...

-Pawel


GS
przyjrzę się temu wieczorem
GS
już widzę dlaczego aplikacja się zwiesza, wieczorem coś więcej napiszę
GS
  • Rejestracja:ponad 14 lat
  • Ostatnio:3 minuty
0

@Pepe:
w metodzie

Kopiuj
TProgressFrm.UFM_SYSINFO_GET_SYSTEM_INFORMATION_DATA(var Msg: TMessage);
....
  while TInterLocked.Add(ThreadsCounter, 0) <> 0 do Sleep(50);
....

po odpaleniu wszystkich wątków program w pętli czekał na ich zakończenie co wstrzymywało przetwarzanie komunikatów i tym samym blokowało wykonanie metody Synchronize z wątków pobocznych.
Wątek główny czekał na zakończenie wątków pobocznych, a wątki poboczne czekały na wykonanie Synchronize, dochodziło do zakleszczenia i zwiechy aplikacji

Sprawdzenie czy wątki są zakończone podpiąłem pod Timera na formie

Kopiuj
procedure TProgressFrm.Timer1Timer(Sender: TObject);
begin
  if TInterLocked.Add(ThreadsCounter, 0) <> 0 then
    exit;
  ModalResult := mrOK;
end;
Kopiuj
unit Progress_Form;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, Vcl.StdCtrls, System.SyncObjs, Vcl.ExtCtrls;

type
  TMyMultiThread = class(TThread)

  public
    Thread_Action: Integer;
  private
    procedure SYNCHRONIZE_AVAILABLE_DATA;
  protected
    procedure GET_AVAILABLE_DATA(AAction: Integer);
    procedure Execute; override;
  end;

var
  ThreadsCounter: Integer = 0;

const
  USER_MSG = wm_User + 1;

type
  TProgressFrm = class(TForm)
    Lbl_Action: TLabel;
    PB_Action: TProgressBar;
    Timer1: TTimer;
    procedure FormCreate(Sender: TObject);
    procedure FormActivate(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    { Private declarations }
    procedure UFM_SYSINFO_GET_SYSTEM_INFORMATION_DATA(var Msg: TMessage); message USER_MSG;
  public
    { Public declarations }
  end;

var
  ProgressFrm: TProgressFrm;

implementation

{$R *.dfm}

procedure TProgressFrm.FormCreate(Sender: TObject);
begin
  //
end;

procedure TProgressFrm.FormActivate(Sender: TObject);
begin
  ProgressFrm.PB_Action.Position := 0;
  ProgressFrm.Update;

  PostMessage(Handle, USER_MSG, 0, 0);
end;

procedure TProgressFrm.FormShow(Sender: TObject);
begin
  //
end;

procedure TProgressFrm.Timer1Timer(Sender: TObject);
begin
  if TInterLocked.Add(ThreadsCounter, 0) <> 0 then
    exit;
  ModalResult := mrOK;
end;

procedure TProgressFrm.UFM_SYSINFO_GET_SYSTEM_INFORMATION_DATA(var Msg: TMessage);
var
  lThread: TMyMultiThread;
  i: Integer;
  iThreadNo: Integer;
begin
  iThreadNo := 5;

  ThreadsCounter := iThreadNo;
  for i := 1 to iThreadNo do
  begin
    lThread := TMyMultiThread.Create(True);
    lThread.Priority := tpNormal; // tpHighest;
    lThread.Thread_Action := i;
    lThread.Start;
  end;
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// tutaj  była  pętla która wstrzymywała przetwarzanie komunikatów i tym samym blokowała wykonanie metody Synchronize. 
// pętla czekała na zakończenie wątków, a wątki czekały na wykonanie Synchronize, powstawało zakleszczenie i zwiecha aplikacji
// 

end;

procedure TMyMultiThread.Execute;
begin
  self.FreeOnTerminate := True;
  try
    GET_AVAILABLE_DATA(self.Thread_Action);


    // IT HANGS!!!!!!
    // HOW TO PASS PARAMETER INTO SYNCHRONIZE PROCEDURE? (TO KNOW WHAT ACTION IS DONE)
    // Synchronize(SYNCHRONIZE_AVAILABLE_DATA);

  finally
    TInterLocked.Decrement(ThreadsCounter);
  end;
end;

procedure TMyMultiThread.GET_AVAILABLE_DATA(AAction: Integer);
begin
  sleep(random(2000));
  synchronize(self.SYNCHRONIZE_AVAILABLE_DATA);
end;

// HOW TO USE SYNCHRONIZE?!!!!!
// HOW TO SHOW PROGRESS FOR DIFFERENT THREAD WITH PROGRESSBAR AND ACTION DESCRIPTION IN LABEL
procedure TMyMultiThread.SYNCHRONIZE_AVAILABLE_DATA();
begin
  ProgressFrm.Lbl_Action.Caption := 'Some text (different for different thread)...'+  inttostr(self.Thread_Action);
  ProgressFrm.PB_Action.StepBy(10);
end;

initialization 
  randomize;

end.

BTW.
Nazwy metod pisane w całości dużymi literami kłują w oczy.
Ten sposób nazewnictwa stosuje się najczęściej do nazw stałych (const) i tym samym jest to mocno mylące

edytowany 4x, ostatnio: grzegorz_so
Pepe
Zgadzam się. Ale ja tutaj stosuję w testach, bo mi się rzuca w oczy i od razu wiem co jest gdzie.
Pepe
Moja pierwsza uwaga, to nie wiem czy będę stosował opis danej akcji - bo każdy wątek wykonuje się w innym czasie i równolegle - zatem może być, że niektóre info pokażą się na króciutki czas, a inne na dłużej... Ważne, że ProgressBar się ruszył. Muszę opracować metodę zamknięcia formy Postępu -> Teraz robi to Timer (nie wiem czy Timer to dobre rozwiązanie).
Pepe
  • Rejestracja:ponad 22 lata
  • Ostatnio:około 9 godzin
  • Postów:496
0

Dzięki. Analizuję... Testuję... Przystosowuję...
Być może będę miał uwagi.
-Pawel


GS
  • Rejestracja:ponad 14 lat
  • Ostatnio:3 minuty
0

Muszę opracować metodę zamknięcia formy Postępu -> Teraz robi to Timer (nie wiem czy Timer to dobre rozwiązanie).

Możesz zrobić to z wątku pobocznego, callbackiem albo wprost zamykając formę z wątku.

Kopiuj
procedure TMyMultiThread.Execute;
begin
 self.FreeOnTerminate := True;
  try
    GET_AVAILABLE_DATA(self.Thread_Action);

    // IT HANGS!!!!!!
    // HOW TO PASS PARAMETER INTO SYNCHRONIZE PROCEDURE? (TO KNOW WHAT ACTION IS DONE)
    // Synchronize(SYNCHRONIZE_AVAILABLE_DATA);

  finally
    if TInterLocked.Decrement(ThreadsCounter)=0 then 
     // ostatni wątek zamyka formę
      ProgressFrm.close;

  end;
Kopiuj
var
  ProgressFrm: TProgressFrm;

Dodał bym że operowanie na formie podpiętej pod zmienną globalną nie jest dobrym rozwiązaniem
ponieważ w kodzie wątku odwołujesz się do jednej globalnej instancji formy ProgressBar

edytowany 1x, ostatnio: grzegorz_so
Pepe
Forma główna wywołuje 2 formę (progressową) i ją zamyka (niszczy). To jest nieprawidłowe? Działa zamykanie z Execute (Timer mi nie pasuje - wystarczy przytrzymać mysz na pasku formy i timer "śpi".
GS
" Forma główna wywołuje 2 formę (progressową) i ją zamyka (niszczy). To jest nieprawidłowe? " Nieprawidłowe jest to że masz jedną zmienną globalną wskazującą na formę
Pepe
  • Rejestracja:ponad 22 lata
  • Ostatnio:około 9 godzin
  • Postów:496
0

Czy jest możliwość odczekania powiedzmy 1s prze zamknięciem formy (ja to nie robię poprzez ProgressFrm.close a poprzez ProgressFrm.ModalResult := mrOK;).
Chciałbym tuż przed zamknięciem formy z wątkami, po ich zakończeniu wyświetlić tekst, w stylu - Hurra, udało się! Możesz używać aplikacji :P.

Niestety, to nie działa:

Kopiuj
procedure TMyMultiThread.Execute;
begin
   Self.FreeOnTerminate := True;
   try
      GET_AVAILABLE_DATA(Self.Thread_Action);
   finally
      if TInterLocked.Decrement(ThreadsCounter) = 0 then 
         begin
            Synchronize(self.SYNCHRONIZE_JOB_FINISHED);  // To ma wyświetlić info, odczekać 1s i zamknąć formę...
         end;
   end;
end;

procedure TMyMultiThread.SYNCHRONIZE_JOB_FINISHED;
begin
   ProgressFrm.Lbl_Action.Caption := 'Action Finished!';
   ProgressFrm.Lbl_Action.Update;

   ProgressFrm.PB_Action.Position := 100;      
   ProgressFrm.PB_Action.Update;   
   ProgressFrm.Update;

   Sleep(1000);
   ProgressFrm.ModalResult := mrOK;
end;

Te Sleep tutaj blokuje formę (czeka te 1s, ale nie odświeża labela i progressa...


edytowany 1x, ostatnio: flowCRANE
GS
  • Rejestracja:ponad 14 lat
  • Ostatnio:3 minuty
0

@Pepe:
nieco zmieniony kod, z callbackiem do zamknięcia formy Progress, bez zmiennej globalnej ProgressFrm

Kopiuj
unit Progress_Form;
interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, Vcl.StdCtrls, System.SyncObjs, Vcl.ExtCtrls;

type
  TMyMultiThread = class(TThread)

  public
    Thread_Action: Integer;
    onAfterExecute: TNotifyEvent;
    pb: TprogressBar;
    lbl: Tlabel;
  private
    procedure SYNCHRONIZE_AVAILABLE_DATA;
  protected
    procedure GET_AVAILABLE_DATA(AAction: Integer);
    procedure Execute; override;
    procedure AfterConstruction; override;
  end;

var
  ThreadsCounter: Integer = 0;

const
  USER_MSG = wm_User + 1;

type
  TProgressFrm = class(TForm)
    Lbl_Action: Tlabel;
    PB_Action: TprogressBar;
    procedure FormCreate(sender: tobject);
    procedure FormActivate(sender: tobject);
    procedure FormShow(sender: tobject);

  private
    { Private declarations }
    procedure UFM_SYSINFO_GET_SYSTEM_INFORMATION_DATA(var Msg: TMessage); message USER_MSG;
    procedure onThreadClose(sender: tobject);
  public
    { Public declarations }
  end;

implementation

{$R *.dfm}

procedure TProgressFrm.FormCreate(sender: tobject);
begin
  randomize;
end;

procedure TProgressFrm.FormActivate(sender: tobject);
begin
  self.PB_Action.Position := 0;
  self.Update;

  PostMessage(Handle, USER_MSG, 0, 0);
end;

procedure TProgressFrm.FormShow(sender: tobject);
begin
  //
end;

procedure TProgressFrm.onThreadClose(sender: tobject);
begin
  if TInterLocked.Add(ThreadsCounter, 0) <> 0 then
    exit;
  ModalResult := mrOK;
end;

procedure TProgressFrm.UFM_SYSINFO_GET_SYSTEM_INFORMATION_DATA(var Msg: TMessage);
var
  lThread: TMyMultiThread;
  i: Integer;
  iThreadNo: Integer;
begin
  iThreadNo := 5;

  ThreadsCounter := iThreadNo;
  for i := 1 to iThreadNo do
  begin
    lThread := TMyMultiThread.Create(True);
    lThread.Priority := tpNormal; // tpHighest;
    lThread.Thread_Action := i;
    lThread.pb := self.PB_Action;
    lThread.lbl := self.Lbl_Action;
    lThread.onAfterExecute := self.onThreadClose;
    lThread.Start;
  end;
end;

procedure TMyMultiThread.AfterConstruction;
begin
  inherited;
  self.onAfterExecute := nil;
  self.pb := nil;
end;

procedure TMyMultiThread.Execute;
begin
  self.FreeOnTerminate := True;
  try
    GET_AVAILABLE_DATA(self.Thread_Action);


    // IT HANGS!!!!!!
    // HOW TO PASS PARAMETER INTO SYNCHRONIZE PROCEDURE? (TO KNOW WHAT ACTION IS DONE)
    // Synchronize(SYNCHRONIZE_AVAILABLE_DATA);

  finally
    TInterLocked.Decrement(ThreadsCounter);
  end;
  if assigned(self.onAfterExecute) then
    self.onAfterExecute(self);
end;

procedure TMyMultiThread.GET_AVAILABLE_DATA(AAction: Integer);
begin
  sleep(random(2000));
  synchronize(self.SYNCHRONIZE_AVAILABLE_DATA);

end;

// HOW TO USE SYNCHRONIZE?!!!!!
// HOW TO SHOW PROGRESS FOR DIFFERENT THREAD WITH PROGRESSBAR AND ACTION DESCRIPTION IN LABEL
procedure TMyMultiThread.SYNCHRONIZE_AVAILABLE_DATA();
begin
  if assigned(self.lbl) then
    self.lbl.Caption := 'Some text (different for different thread)...' + inttostr(self.Thread_Action);
  if assigned(self.pb) then
    pb.StepBy(10);
end;

end.

oraz utworzenie formy z formy głównej

Kopiuj

procedure TMainFrm.FormCreate(Sender: TObject);
var
  ProgressFrm: TProgressFrm;
begin
  // DO SOME BASIC STUFF

  // CREATE PROGRESS FORM
  try
    try
      ProgressFrm := TProgressFrm.Create(Application);
      ProgressFrm.ShowModal; // MUST BE MODAL!
      ProgressFrm.Update;
    except
      ShowMessage('Something went wrong!');
    end;
  finally
    freeandnil(ProgressFrm);
  end;
end;

;--------------------------

Kopiuj
procedure TProgressFrm.FormActivate(Sender: TObject);
begin
  ProgressFrm.PB_Action.Position := 0;
  ProgressFrm.Update;

  PostMessage(Handle, USER_MSG, 0, 0);
end;

To jest koszmarek, odwołując się do elementów klasy(formy) wiążesz kod metody ze zmienną globalną.
Jeśli tak ma być tylko w kodzie testowym to jest słabe wyjaśnienie.
Ale ja w nawet w kodzie testowym posłużył bym się poniższym kodem

Kopiuj
procedure TProgressFrm.FormActivate(Sender: TObject);
begin
  self.PB_Action.Position := 0;
  self.Update;

  PostMessage(Handle, USER_MSG, 0, 0);
end;
GS
  • Rejestracja:ponad 14 lat
  • Ostatnio:3 minuty
0

@Pepe:
patrz powyżej, możesz to zrobić w kodzie metody kod metody TProgressFrm.onThreadClose(sender: tobject)

edytowany 1x, ostatnio: grzegorz_so
GS
  • Rejestracja:ponad 14 lat
  • Ostatnio:3 minuty
0

Te Sleep tutaj blokuje formę (czeka te 1s, ale nie odświeża labela i progressa...

w moim kodzie wątek tylko powiadamia formę o swoim zakończeniu

Pepe
  • Rejestracja:ponad 22 lata
  • Ostatnio:około 9 godzin
  • Postów:496
0

Tak, analizuję!
Wstawiasz kod szybciej, niż ja go kopiuję :P
Dziękuję ci za czas i pomoc!
-Pawel


GS
  • Rejestracja:ponad 14 lat
  • Ostatnio:3 minuty
0

@Pepe:

Kopiuj
procedure TProgressFrm.onThreadClose(sender: tobject);
begin
  if TInterLocked.Add(ThreadsCounter, 0) <> 0 then
    exit;
  self.Lbl_Action.Caption := 'Wątki zamknięte';
  sleep(3000);
  ModalResult := mrOK;
end;

po zakończeniu wszystkich wątków przez 3 sekundy label informuje o ich zamknięciu po czym forma się zamyka

Pepe
  • Rejestracja:ponad 22 lata
  • Ostatnio:około 9 godzin
  • Postów:496
0

To działa, ale czas Sleep(3000) nie jest 3 sekundami...

Poza tym:

Kopiuj
[dcc32 Hint] Progress_Form.pas(23): H2269 Overriding virtual method 'TMyMultiThread.AfterConstruction' has lower visibility (protected) than base class 'TThread' (public)

edytowany 2x, ostatnio: flowCRANE
Pepe
Przeniosłem do Public i jest gitara.... Ale czas zamknięcia jest nieprawidłowy. Za długi.
GS
  • Rejestracja:ponad 14 lat
  • Ostatnio:3 minuty
0

@Pepe:
sleep(3000) to 3 tys. milisekund = 3 sekundy
nie wiem skąd masz błąd kompilacji, wklejałem działający kod

Przeniosłem do Public i jest gitara.... Ale czas zamknięcia jest nieprawidłowy. Za długi
czas trzy sekundy jest liczony od momentu zakończenia ostatniego wątku

spakuj i podaj cały kod projektu

edytowany 3x, ostatnio: grzegorz_so
Pepe
To było tylko ostrzeżenie, nie błąd. Spoko. Nie zrozumiałeś mnie z tym, czasem Sleep. Oczywiście, że Sleep(3000) to 3 sekundy - chodzi o to, że wpisując taką wartość, program czeka np. 6s, albo 10s...
GS
już widzę że to tylko hint
GS
nie zaloguję się teraz do dropboxa
GS
zalogowałem się
GS
  • Rejestracja:ponad 14 lat
  • Ostatnio:3 minuty
0

@Pepe:
Komentarze służą do pobocznych od tematu postu wymiany zdań.
Pisz w postach.

chodzi o to, że wpisując taką wartość, program czeka np. 6s, albo 10s.

odpaliłem Twój projekt i nie widzę żadnej czasowej zwłoki przy zamykaniu formy

edytowany 1x, ostatnio: grzegorz_so
GS
  • Rejestracja:ponad 14 lat
  • Ostatnio:3 minuty
0

@Pepe:
w metodzie

Kopiuj
TMyMultiThread.SYNCHRONIZE_AVAILABLE_DATA();

zupełnie niepotrzebnie w każdym przypadku case wstawiasz kod sprawdzający if assigned(self.LBL) then

Kopiuj
case self.Thread_Action of
    1: // Thread 1
      begin
        // Do Something (Thread 1)
        if assigned(self.LBL) then
          self.LBL.Caption := 'Gathering Operating System Information (' + inttostr(self.Thread_Action) + ')';
      end;

można to zrobić raz

Kopiuj
procedure TMyMultiThread.SYNCHRONIZE_AVAILABLE_DATA();
begin
  if assigned(self.LBL) then
    case self.Thread_Action of
      1: // Thread 1
        self.LBL.Caption := 'Gathering Operating System Information (' + inttostr(self.Thread_Action) + ')';
      2: // Thread 2
        self.LBL.Caption := 'Gathering Computer System Information (' + inttostr(self.Thread_Action) + ')';
      3: // Thread 3
        self.LBL.Caption := 'Gathering Operating Memory Information (' + inttostr(self.Thread_Action) + ')';
      4: // Thread 4
        self.LBL.Caption := 'Gathering Processor (CPU) Information (' + inttostr(self.Thread_Action) + ')';
      5: // Thread 5
        self.LBL.Caption := 'Gathering Video Controller Information (' + inttostr(self.Thread_Action) + ')';
    end;
  if assigned(self.PB) then
  begin
    PB.Position := PB.Position + 20;
    PB.Update;
  end;
end;

tym samym kod metody skraca się trzykrotnie

Pepe
Tak. Przypominam, testowa apka
GS
i w testowej apce bezsensownie pięciokrotnie wklejasz w kod to samo... bo samo się nie wkleiło, zupełnie niepotrzebny nakład pracy jeśli można to było załatwić jedną linią kodu
Pepe
W pełni się zgadzam, ale jednocześnie to nie było bezsensownie. Po prostu to nie miało znaczenia - bo jak to będzie ostatecznie wyglądać, zdecyduje później. Teraz chodzi o to, żeby się nauczyć obsługi wątków (w pewnym małym zakresie oczywiście), żeby ładnie działało to co ma działać. Reszta jest do dopieszczenia później.
GS
miałem na myśli to że więcej czasy zajęło Ci pięciokrotne wklejenie tego samego kodu zamiast opakować to wszystko w jeden if i tym samym jeden raz wkleić warunek. To że piszesz testowy kod nie usprawiedliwia niechlujstwa, szczególnie że ten testowy kod wrzucasz na publiczne forum do oceny albo pomocy w rozwiązaniu problemu. Podobnie jest z nazewnictwem metod pisanymi dużymi literami
Pepe
  • Rejestracja:ponad 22 lata
  • Ostatnio:około 9 godzin
  • Postów:496
0

Film pokazujący problem Sleep i zamykaniem: https://www.dropbox.com/s/c36frmu2npr3wje/TEST.mp4?dl=1
Nie trzeba się logować na swój dropbox, można obejrzeć niezalogowany...


GS
  • Rejestracja:ponad 14 lat
  • Ostatnio:3 minuty
1

@Pepe:
spróbuj tak

Kopiuj
procedure TProgressFrm.onThreadClose(Sender: TObject);
begin
  if TInterLocked.Add(ThreadsCounter, 0) <> 0 then
    exit;

  self.Lbl_Action.Caption := 'Wątki zamknięte';
  application.ProcessMessages;

  Sleep(3000); // !!!!
  ModalResult := mrOK;
end;

u mnie zamyka się w czasie ok 3 sek.

edytowany 2x, ostatnio: grzegorz_so
Pepe
  • Rejestracja:ponad 22 lata
  • Ostatnio:około 9 godzin
  • Postów:496
0

Działa prawidłowo. Dziękuję.
Na dzisiaj mam dość. Wspaniała robota!


Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)