Wątek odliczający przetworzone rekordy

0

Pomóżcie, bo nie mam kompletnie pojęcia jak się za to zabrać.
Mam aplikację, która pobiera plik XML z sieci i następnie go przetwarza. Rekordów w tym pliku jest kilkadziesiąt tysięcy. Oczywiście podczas przetwarzania, aplikacja "wisi". Chciałbym zrobić licznik, który wyświetlałby, który aktualnie rekord jest przetwarzany. Wiem, że muszę zastosować wątki. Przewertowałem kilka stron internetowych na ten temat i przyznam szczerze, że nie jestem, ani odrobinę mądrzejszy. Czy jakaś dobra dusza poratowałaby receptą na moje schorzenie???
Z góry bardzo dziękuję.

1

Szczerze jeśli przerobiłeś kilka stron i dalej nie rozumiesz to ciężko podjąć wyzwanie żeby to wytłumaczyć lepiej.
Najprostszym rozwiązaniem jest wywołanie Application.ProcessMessages po każdym rekordzie, wtedy aplikacja się na chwilę odwiesi i odświeży interfejs żeby pokazać aktualny rekord. To nie jest idealne rozwiązanie bo aplikacja nadal będzie w większości wisieć i nie będzie odpowiadać ale może ci to wystarczy.

Delphi z tego co wiem nie ma łatwego modelu wielowątkowości, więc jeśli chcesz to rozwiązać lepiej to albo musisz poszukać jakiejś biblioteki która w tym pomoże albo usiąść i zrozumieć wątki, z tym że czeka na ciebie pewnie parę pułapek na początku.

0

Ale problem jest w tym, że aplikacja pobierając plik zamarza, czy podczas przetwarzania zawiesza się?

Nie mniej jednak jedno i drugie da się rozwiązać. Nie wiem jak pobierasz plik, ale dodanie progressbar'a podczas pobierania pliku będzie fajnym rozwiązaniem, do tego łatwym w zaimplementowaniu. Tak samo można pobrać liczbę rekordów w pliku i wyświetlić postęp za pomocą kolejnego albo tego samego progress'bara. Przy czym jak @obscurity zauważył potrzeba po ustawieniu postępu dać Application.ProcessMessages inaczej Window's nie będzie odświeżał okna.

Do takiego problemu moim zdaniem nie ma co ładować wątków. I tak program nie będzie w stanie niczego innego zrobić.

1

coś mi świta że działał pasek postępu działał mi kiedyś w taki sposób

function DevChannel.DownloadID(SourceFile, DestFile: string): Boolean;
var
  Http: TIdHTTP;
  MS: TMemoryStream;
begin
  Http := TIdHTTP.Create(nil);
  try
    MS := TMemoryStream.Create;
    try
      Http.OnWork:= HttpWork;

      Http.Get(SourceFile, MS);
      MS.SaveToFile(DestFile);

    finally
      MS.Free;
    end;
  finally
    Http.Free;
    pb1.Position := 0;
  end;
  Result := True;
end;

procedure DevChannel.HttpWork(ASender: TObject; AWorkMode: TWorkMode;
  AWorkCount: Int64);
var
  Http: TIdHTTP;
  ContentLength: Int64;
  Percent: Integer;
begin
  Http := TIdHTTP(ASender);
  ContentLength := Http.Response.ContentLength;

  if (Pos('chunked', LowerCase(Http.Response.ToString)) = 0) and
     (ContentLength > 0) then
  begin
    Percent := 100*AWorkCount div ContentLength;
    pb1.Position := Percent;
  end;
end;

teraz to był zrobił wątek i wysyłał do głównego wątku wiadomości o postępie prac, bo takie używanie Application.ProcessMessages w długo trwającej funkcji też może być problemem a przynajmniej na Linux+Lazarus

3

Przecież do tego wystarczy prosty TThread — do tego celu istnieje. Odpal sobie cały proces w wątku, a w Synchronize odświeżaj interfejs. Ta klasa jest tak prosta w obsłudze, że nawet nie ma co się za bardzo rozpisywać.

Application.ProcessMessages od biedy można też użyć, ale działa to dobrze tylko wtedy, gdy wywołuje się tę metodę w bardzo krótkich odstępach czasu (aby maksymalnie ukryć zamrażanie się interfejsu). Wątek poboczny jest zdecydowanie lepszy, bo interfejs w ogóle nie zamarza, a wszystko co musi być w twoim przypadku synchronizowane, to jedynie aktualizacja interfejsu, co i tak jest banalne.

W sieci znajdziesz masę przykładów jak używać TThread. Natomiast w Kompendium jest przykładowa aplikacja, której zadaniem jest przeszukiwać dysk w wielu wątkach (po jednym wątku na partycję) i wyświetlać wyniki w komponencie. Pobaw się w coś takiego, tylko o synchronizacji nie zapomnij (Adam zapomniał).

1

Jeżeli nie masz zabytkowego Delphi to chyba prościej już się nie da:

unit Unit2;

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.Threading;

type
  TForm2 = class(TForm)
    btnStart: TButton;
    ProgressBar1: TProgressBar;
    btnCancel: TButton;
    procedure btnStartClick(Sender: TObject);
    procedure btnCancelClick(Sender: TObject);
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
  private
    { Private declarations }

    aTask: ITask;
    fCancel: Boolean;
  public
    { Public declarations }
  end;

var
  Form2: TForm2;

implementation

{$R *.dfm}


procedure TForm2.btnCancelClick(Sender: TObject);
begin
  if (Assigned(ATask)) then
  begin
    fCancel:= True;
    aTask.Cancel;
    aTask:= nil;
  end;
end;

procedure TForm2.btnStartClick(Sender: TObject);
begin
  if Assigned(ATask) and (Atask.Status = TTaskStatus.Running) then
    exit;

  fCancel:= False;

  //początkowe ustawienia paska postępu
  ProgressBar1.Min:= 0;
  ProgressBar1.Max:= 100;
  ProgressBar1.Position:= 0;

  var totalCount:= 1000; //ile zadań do zrobienia
  var percent:= 0;  //na poczatkek init z 0 postępu

  aTask:= TTask.Create(
  procedure
  begin
    for var i:= 1 to totalCount do
    begin
       if fCancel then
         break;

       Sleep(1000); // symuulacja czasochłonnej operacji

       TThread.Synchronize(TThread.Current,
       procedure
       begin
         if not fCancel then
         begin
           percent:= Trunc((i / totalCount) * 100); //obliczanie procent
           ProgressBar1.Position:= percent;  //wyświretl postęp
           Form2.Caption:= Format('Wykonano %0:d z %1:d [%2:d%%]', [i, totalCount, percent]);
         end
         else
           Form2.Caption:= 'Anulowano';
       end);
     end;
  end);
  aTask.Start;
end;

procedure TForm2.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  try
    if (Assigned(ATask)) then
    begin
      fCancel:= True;
      aTask.Cancel;
      aTask:= nil;
    end;
  except
    on E: EOperationCancelled do
      CanClose:= True;
  end;
  CanClose:= True;
end;

end.
2
Buster napisał(a):

Pomóżcie, bo nie mam kompletnie pojęcia jak się za to zabrać.

Najpierw zdefiniuj problem.

A IMO to mogą być (luba nawet są) dwa odrębne problemy.

  1. Przetwarzanie pliku XML
  2. Przetwarzanie danych z XML dalej - nie wiem co robisz z tymi danymi z XML, być może coś tam robisz może w bazie.

No, ale dano Ci już odpowiedź o wątkach albo nawet o pobieraniu plików, a nawet pojawił się auto-magiczny Application.ProcessMessages🤦‍♂️
Chociaż na samą wizualizację "który rekord jest przetwarzany" to nie jest złe rozwiązanie.
Tylko, że to żadne rozwiązanie.

Jeśli problem dotyczy przetwarzania XML'a a używasz DOM, to nic z tym nie zrobisz.
Dopóki nie przejdziesz na przetwarzanie zdarzeniowe, czyli SAX.

Jeśli ciężar jest przesunięty na moment przetwarzania odczytanych danych z XML (nie wiem co robisz, może import do bazy?) to pewnie pula tzw. worker-threads będzie rozwiązaniem.
Ale jak nie wiadomo dokładnie z czym masz problem, to jak Ci pomóc?

Coś mi pachnie, że problem dotyczy obu obszarów - tj. samo prasowanie i odczyt danych z XML i przetwarzanie danych z XML.

1 użytkowników online, w tym zalogowanych: 0, gości: 1