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ę.
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.
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ć.
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
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ł).
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.
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.
- Przetwarzanie pliku XML
- 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.