Page 2 of 5 FirstFirst 1 2 3 4 ... LastLast
Results 16 to 30 of 66

Thread: Threads om processen te versnellen

  1. #16
    TDigitalTrain user Hans Brenkman's Avatar
    Join Date
    Mar 2002
    Location
    Weert
    Posts
    1,861
    Je zou eens naar TTask (Parallel Programming) kunnen kijken (v.a Delphi XE7). Deze verzorgt multi-threading en maakt het echt veel gemakkelijker threading te implementeren. Kan vrij simpel meerdere threads parallel uitvoeren en bijv. ook loggen naar één log-file welke je per thread synchroniseert aan de main-thread. Ik ben hier toevallig vorige week mee begonnen omdat ik in een service applicatie meerdere threads met elk een andere doel en tijds-interval om te laten runnen ging maken. O.a. het boek "More coding in Delphi" van Nick Hodges geeft er over dit onderwerp in enkele hoofdstukken een heldere uitleg over.

    Voorbeeld uit het boek van Nick Hodges:

    Code:
    AllTasks: array[0..2] of ITask;
    
    
    procedure TMultipleTasksDemoForm.Button3Click(Sender: TObject);
    begin
      AllTasks[0] := TTask.Run(procedure
                               begin
                                 PrimesBelow(200000);
                                 TThread.Synchronize(TThread.Current,
                                                     procedure
                                                     begin
                                                       Memo1.Lines.Add('200000 is done');
                                                     end);
                               end);
      AllTasks[1] := TTask.Run(procedure
                               begin
                                 PrimesBelow(250000);
                                 TThread.Synchronize(TThread.Current,
                                                     procedure
                                                     begin
                                                       Memo1.Lines.Add('250000 is done');
                                                     end);
                               end);
      AllTasks[2] := TTask.Run(procedure
                               begin
                                 PrimesBelow(300000);
                                 TThread.Synchronize(TThread.Current,
                                                     procedure
                                                     begin
                                                       Memo1.Lines.Add('300000 is done');
                                                     end);
                               end);
    end;
    Last edited by Hans Brenkman; 11-Sep-17 at 13:13.
    Testen kan niet de afwezigheid van fouten aantonen, slechts de aanwezigheid van gevonden fouten.

    Het is verdacht als een nieuw ontwikkeld programma direct lijkt te werken: waarschijnlijk neutraliseren twee ontwerpfouten elkaar.

  2. #17
    Dankje Hans, zal er eens wat over opzoeken.. Misschien komt dat ook wel van pas..

  3. #18
    Dag mensen,

    Rik (RVK) heeft mij een mooi voorbeeld gegeven over hoe ik de boel goed kan opzetten om te werken met Threads. Dit heb ik gedaan, en werkt heel erg goed. Ik heb enkel de boel iets aangepast, nu maak ik voor elk "kanaal" dat ik scrape een thread, in plaats voor elk "programma". Dit omdat het scrapen van een programma een 2-5 seconden pakt, en ik anders heel veel threads ga aanmaken/sluiten waardoor de boel i.p.v. sneller juist trager word;

    Nu werkt dit perfect, maar zou ik graag een visueel beeld hebben van de voortgang, liefst dmv een progressbar. Nu gebruik ik synchronize voor het synchronizeren van de uitvoer naar een stringlist, maar is kan ik dat ook gebruiken voor een progressbar?

  4. #19
    Quote Originally Posted by Reidinga View Post
    Nu gebruik ik synchronize voor het synchronizeren van de uitvoer naar een stringlist, maar is kan ik dat ook gebruiken voor een progressbar?
    Ja. Je kunt voordat je de threads start een progressbar op max=geschatte aantal handelingen zetten.

    Daarna kun je in een synchronize procedure voor elk van die handeling de progressbar van je main form ophogen.

  5. #20
    Oke moet ik daarvoor ook terug een aparte procedure maken die ik aanroep in de synchronize? Moet zeggen met de threads gaat het scrapen een stuk sneller!! Nu haal ik de BE gids informatie uit een JSON Api, en zet deze zelf om naar XML (XMLTV Format). Nu haal ik een 100 belgishe kanalen informatie van een 3 dagen binnen in een paar seconden.. dat is echt prettig!!

  6. #21
    mov rax,marcov; push rax marcov's Avatar
    Join Date
    Apr 2004
    Location
    Ehv, Nl
    Posts
    10,357
    Let op dat TTask notatie vooral kortere notatie is. Alles in het voorbeeld is ook mogelijk in oudere delphi's met wat meer code.

  7. #22
    Wat bedoel je daarmee Marcov? ik gebruik nu Delphi 10.2

  8. #23
    mov rax,marcov; push rax marcov's Avatar
    Join Date
    Apr 2004
    Location
    Ehv, Nl
    Posts
    10,357
    Ik bedoel dat TTask een kortere notatie is voor dingen die in oudere delphis ook vrij makkelijk te doen zijn. Als je een nieuwere versie hebt, en geen plannen om oudere te gebruiken maakt het niet uit.

  9. #24
    Nee ik ben zeer tevreden met 10.2 ik gebruik toch altijd vcl.. firemonkey kan ik niet aan wennen..

  10. #25
    Toch nog eens een vraagje, de threads werken uitstekend, en scrapen netjes allemaal tegelijk. Toch als ik kijk in taskman lijkt de applicatie regelmatig te "wachten" processorverbruik ligt gemiddeld op 0.2/0.3% met uitschietertjes, zelfde met netwerkverbruik.. dat is maar af en toe dat hij een 0.1-1.1mb aangeeft, en geheugen zit op een constante 85 - 100 mb.

    Kan het zijn dat hij blijft wachten in de loop waar ik wacht tot alle threads klaar zijn? Ik heb het idee dat sommige threads eerder klaar zijn, maar staan te wachten op andere threads die niet klaar zijn, en daardoor de boel vertraagd.. kan dat?

  11. #26
    Dat kan sowieso. Als jij je lijst opgedeeld hebt in 20 stukken dan maak je dus 20 threads aan die allemaal hun eigen stuk afwerken. Dan kan het best zijn dat zo'n thread eerder klaar is dan de andere.

    Eigenlijk zou je dus niet je lijst op moeten delen in 20 stukken maar gewoon 20 threads maken die allemaal individueel via een Synchronize functie één item uit jouw lijst halen. Je moet dan in je lijst wel markeren dat die door een thread is opgehaald. Al je 20 threads halen dus elke keer 1 regel op waardoor alle 20 thread door blijven lopen tot al je regels afgehandeld zijn.

    (ik hoop dat ik het een beetje duidelijk uitleg)

  12. #27
    Ik begrijp niet helemaal hoe ik dat kan doen.. zal ik anders de code die ik heb hier plaatsen? Dan kan je zien hoe ik het nu doe..

    Code:
    unit unt_EPGBuilder;
    
    interface
    
    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
      Winapi.WinInet;
    
    type
      { TEPGChannel class }
      TEPGChannel = class
      private
        FChannelID   : string;
        FChannelName : string;
        FSiteID      : string;
      public
        constructor Create(AChannelID, AChannelName, ASiteID: string); virtual;
      published
        property ChannelID : string read FChannelID;
        property ChannelName : string read FChannelName;
        property SiteID : string read FSiteID;
      end;
    
      { TEPGChannel List class }
      TEPGChannelList = class(TObject)
      private
        FList  : Array of TEPGChannel;
        function GetEPGChannel(Index: Integer) : TEPGChannel;
        procedure SetEPGChannel(Index: Integer; EPGChannel: TEPGChannel);
        function GetCount : Integer;
      public
        constructor Create; virtual;
        destructor Destroy; override;
        procedure Add(AEPGChannel: TEPGChannel);
    
        property Items[Index: Integer]: TEPGChannel read GetEPGChannel write SetEPGChannel;
      published
        property Count : Integer read GetCount;
      end;
    
      { TEPGChannel Thread class }
      TEPGChannelThread = class(TThread)
      private
        FDays        : Integer;
        FChannelID   : string;
        FChannelName : string;
        FSiteID      : string;
        FUserAgent   : string;
        FLang        : string;
        FTimeShift   : Integer;
    
        FOutput    : TStrings;
        procedure WriteXMLTV;
      protected
        procedure ScrapeChannel; virtual;
        procedure Execute; override;
      public
        FResult : TStringList;
    
        constructor Create; overload;
        constructor Create(CreateSuspended: Boolean); overload;
        destructor Destroy; override;
    
        function GetURL(AURL: string) : string;
      published
        property Days : Integer read FDays write FDays;
        property ChannelID : string read FChannelID write FChannelID;
        property ChannelName : string read FChannelName write FChannelName;
        property SiteID : string read FSiteID write FSiteID;
        property UserAgent : string read FUserAgent write FUserAgent;
        property Lang: string read FLang write FLang;
        property TimeShift : Integer read FTimeShift  write FTimeShift;
        property OutputLines : TStrings read FOutput write FOutput;
      end;
    
      TEPGGrabber = class(TComponent)
      private
        FChannelList : TEPGChannelList;
        FDays        : Integer;
        FLang        : string;
        FAgent       : string;
        FTimeShift   : Integer;
        FOutput      : TStrings;
      public
        constructor Create(AOwner: TComponent); override;
        destructor Destroy; override;
    
        procedure Execute; virtual;
    
        function LoadConfig(Filename: string) : Boolean;
        function GetURL(AURL: string) : string;
    
        property Channels : TEPGChannelList read FChannelList write FChannelList;
      published
        property Days : Integer read FDays write FDays;
        property Lang : string read FLang write FLang;
        property UserAgent : string read FAgent write FAgent;
        property TimeShift : Integer read FTimeShift  write FTimeShift;
        property OutputLines : TStrings read FOutput write FOutput;
      end;
    
    const
      UserAgentIE11 : string = 'Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko';
    
    implementation
    
    { TEPGChannel class }
    (******************************************************************************)
    constructor TEPGChannel.Create(AChannelID, AChannelName, ASiteID: string);
    (******************************************************************************)
    begin
      FChannelID   := AChannelID;
      FChannelName := AChannelName;
      FSiteID      := ASiteID;
    end;
    
    { TEPGChannel List class }
    (******************************************************************************)
    constructor TEPGChannelList.Create;
    (******************************************************************************)
    begin
      inherited Create;
    end;
    
    (******************************************************************************)
    destructor TEPGChannelList.Destroy;
    (******************************************************************************)
    begin
      SetLength(FList, 0);
      inherited Destroy;
    end;
    
    (******************************************************************************)
    procedure TEPGChannelList.Add(AEPGChannel: TEPGChannel);
    (******************************************************************************)
    begin
      SetLength(FList, Length(FList) + 1);
      FList[High(FList)] := AEPGChannel;
    end;
    
    (******************************************************************************)
    function TEPGChannelList.GetEPGChannel(Index: Integer) : TEPGChannel;
    (******************************************************************************)
    begin
      Result := FList[Index];
    end;
    
    (******************************************************************************)
    procedure TEPGChannelList.SetEPGChannel(Index: Integer; EPGChannel: TEPGChannel);
    (******************************************************************************)
    begin
      FList[Index] := EPGChannel;
    end;
    
    (******************************************************************************)
    function TEPGChannelList.GetCount : Integer;
    (******************************************************************************)
    begin
      Result := Length(FList);
    end;
    
    { TEPGChannel Thread class }
    (******************************************************************************)
    constructor TEPGChannelThread.Create;
    (******************************************************************************)
    begin
      inherited Create;
      FResult := TStringList.Create;
    end;
    
    (******************************************************************************)
    constructor TEPGChannelThread.Create(CreateSuspended: Boolean);
    (******************************************************************************)
    begin
      inherited Create(CreateSuspended);
      FResult := TStringList.Create;
    end;
    
    (******************************************************************************)
    destructor TEPGChannelThread.Destroy;
    (******************************************************************************)
    begin
      FResult.Free;
      inherited Destroy;
    end;
    
    (******************************************************************************)
    procedure TEPGChannelThread.ScrapeChannel;
    (******************************************************************************)
    begin
      // FResult.Add('scraped_info');
    end;
    
    (******************************************************************************)
    procedure TEPGChannelThread.WriteXMLTV;
    (******************************************************************************)
    begin
      // Schrijf gescrape'te kanaal + programma's naar TStrings
      // deze dient opgegeven te worden voordat Execute wordt aangeroepen.
      if Assigned(FOutput) then
      FOutput.AddStrings(FResult);
    end;
    
    (******************************************************************************)
    function TEPGChannelThread.GetURL(AURL: string) : string;
    (******************************************************************************)
    var
      NetHandle : HINTERNET;
      UrlHandle : HINTERNET;
      Buffer    : array [0..999999] of Byte;
      BytesRead : dWord;
      StrBuffer : UTF8String;
      ResultStr : String;
    begin
      Result    := '';
      ResultStr := '';
      if Pos('http://', Lowercase(AURL)) = 0 then Exit;
      if (UserAgent = '') then UserAgent := UserAgentIE11;
      NetHandle := InternetOpen(PChar(UserAgent), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
      if Assigned(NetHandle) then
      try
        UrlHandle := InternetOpenUrl(NetHandle, PChar(AUrl), nil, 0, INTERNET_FLAG_RELOAD, 0);
        if Assigned(UrlHandle) then
        try
          repeat
            InternetReadFile(UrlHandle, @Buffer, SizeOf(Buffer), BytesRead);
            SetString(StrBuffer, PAnsiChar(@Buffer[0]), BytesRead);
            ResultStr := ResultStr + StrBuffer;
          until BytesRead = 0;
          if ResultStr <> '' then
          Result := ResultStr;
        finally
          InternetCloseHandle(UrlHandle);
        end;
      finally
        InternetCloseHandle(NetHandle);
      end;
    end;
    
    (******************************************************************************)
    procedure TEPGChannelThread.Execute;
    (******************************************************************************)
    begin
      // Scrape kanaal.
      ScrapeChannel;
      // Schrijf kanaal gegevens weg naar output.
      Synchronize(WriteXMLTV);
    end;
    
    { TEPGGrabber Class }
    (******************************************************************************)
    constructor TEPGGrabber.Create(AOwner: TComponent);
    (******************************************************************************)
    begin
      //inherited Create(Self);
      FChannelList := TEPGChannelList.Create;
    end;
    
    (******************************************************************************)
    destructor TEPGGrabber.Destroy;
    (******************************************************************************)
    begin
      FChannelList.Free;
      inherited Destroy;
    end;
    
    (******************************************************************************)
    procedure TEPGGrabber.Execute;
    (******************************************************************************)
    var
      I : Integer;
      GrabberThreads: array of TEPGChannelThread;
    begin
      // Eerst alle threads (kanalen) aanmaken.
      SetLength(GrabberThreads, Channels.Count);
      for I := 0 to Channels.Count -1 do
      begin
        GrabberThreads[I] := TEPGChannelThread.Create(True);
        GrabberThreads[I].Days        := Days;
        GrabberThreads[I].ChannelID   := Channels.Items[I].ChannelID;
        GrabberThreads[I].ChannelName := Channels.Items[I].ChannelName;
        GrabberThreads[I].SiteID      := Channels.Items[I].SiteID;
        GrabberThreads[I].UserAgent   := UserAgent;
        GrabberThreads[I].Lang        := Lang;
        GrabberThreads[I].TimeShift   := TimeShift;
        GrabberThreads[I].OutputLines := OutputLines;
      end;
    
      // Dan alle threads (kanalen) activeren, dus alle kanalen scrapen.
      for I := 0 to Channels.Count -1 do
      begin
        GrabberThreads[I].Start;
      end;
    
      // Dan wachten op alle threads (kanalen) en vrijmaken.
      for I := 0 to Channels.Count -1 do
      begin
        GrabberThreads[I].WaitFor;
        GrabberThreads[I].Free;
      end;
    end;
    
    (******************************************************************************)
    function TEPGGrabber.LoadConfig(Filename: string) : Boolean;
    (******************************************************************************)
    var
      C        : TStringList;
      I        : Integer;
      TEMP     : string;
      XMLTVID  : string;
      SITEID   : string;
      DISPNAME : string;
    const
      CXMLTVID  : string = 'XMLTVID=';
      CSITEID   : string = 'SITEID=';
      CDISPNAME : string = 'DISPLAYNAME=';
    
      // Voeg kanaal toe aan XML.
      procedure AddXMLChannel;
      const
        XML : string = '  <channel id="%s">' + #13#10 +
                       '    <display-name lang="%s">%s</display-name>' + #13#10 +
                       '  </channel>';
      begin
        if Assigned(OutputLines) then
        OutputLines.Add(Format(XML, [XMLTVID, Lang, DISPNAME]));
      end;
    
    begin
      Result := False;
      C := TStringList.Create;
      try
        if FileExists(Filename) then
        begin
          C.LoadFromFile(Filename);
          for I := 0 to C.Count -1 do
          begin
            Temp := C[I];
            if (Pos('*', Temp) = 1) then Continue;
            if (Pos(CXMLTVID, Temp) > 0) and
               (Pos(CSITEID, Temp) > 0) and
               (Pos(CDISPNAME, Temp) > 0) then
            begin
              XMLTVID  := Copy(Temp, 1 + Length(CXMLTVID), Pos('|', Temp) - Length(CXMLTVID) - 1);
              Delete(Temp, 1, Pos('|', Temp));
              SITEID   := Copy(Temp, 1 + Length(CSITEID), Pos('|', Temp) - Length(CSITEID) -1);
              Delete(Temp, 1, Pos('|', Temp));
              DISPNAME := Copy(Temp, 1 + Length(CDISPNAME), Length(C[I]));
              if Assigned(FChannelList) then
              FChannelList.Add(TEPGChannel.Create(XMLTVID, DISPNAME, SITEID));
              AddXMLChannel;
            end;
          end;
          if FChannelList.Count > 0 then Result := True;
        end;
      finally
        C.Free;
      end;
    end;
    
    (******************************************************************************)
    function TEPGGrabber.GetURL(AURL: string) : string;
    (******************************************************************************)
    var
      NetHandle : HINTERNET;
      UrlHandle : HINTERNET;
      Buffer    : array [0..999999] of Byte;
      BytesRead : dWord;
      StrBuffer : UTF8String;
      ResultStr : String;
    begin
      Result    := '';
      ResultStr := '';
      if Pos('http://', Lowercase(AURL)) = 0 then Exit;
      if (UserAgent = '') then UserAgent := UserAgentIE11;
      NetHandle := InternetOpen(PChar(UserAgent), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
      if Assigned(NetHandle) then
      try
        UrlHandle := InternetOpenUrl(NetHandle, PChar(AUrl), nil, 0, INTERNET_FLAG_RELOAD, 0);
        if Assigned(UrlHandle) then
        try
          repeat
            InternetReadFile(UrlHandle, @Buffer, SizeOf(Buffer), BytesRead);
            SetString(StrBuffer, PAnsiChar(@Buffer[0]), BytesRead);
            ResultStr := ResultStr + StrBuffer;
          until BytesRead = 0;
          if ResultStr <> '' then
          Result := ResultStr;
        finally
          InternetCloseHandle(UrlHandle);
        end;
      finally
        InternetCloseHandle(NetHandle);
      end;
    end;
    
    end.
    Dat is de basis die ik heb gemaakt, ik maak dus voor elke scraper hiervan een afgeleide classe van TEPGGrabber en TEPChannelThread. Ik doe het nu zoals je me had aangegeven.. Ik scrape elk kanaal in een aparte thread, en schrijf die via synchronize weg naar mijn memo. Als ik debug zie ik ook netjes dat hij alle threads start, dus neem aan dat die ook synchroom samen lopen? of wachten die in volgorde tot de volgende is afgelopen? Want als ik een logging inbouw in de thread die elk naar een eigen log schrijft, zie ik ook dat ze naast elkaar gestart worden).
    Last edited by Reidinga; 13-Sep-17 at 18:50.

  13. #28
    Je start nu voor elke channel een TEPGChannelThread.
    Je had 100 channels dus dan zul je 100 TEPGChannelThread hebben.

    Maar je zei dat dit in een paar seconden klaar was. Hoe heb jij dan de tijd om te zien dat je op een thread zou moeten wachten?

    Je hebt het over dat iets de boel zou vertragen maar als dit met 100 channels in een paar seconden klaar is, op welke vertraging doel je dan?

    (Ps. Het is overigens ook nog de vraag of InternetReadFile() thread-safe is. Als dit niet het geval is moet je met een thread-safe GetURL aan de gang.)

  14. #29
    Als ik geen threads gebruik - heb ik dmv een counter kunnen timen hoe lang hij er over doet per kanaal. Maar nu merk ik dat hij wel meerdere threads heeft, maar toch wacht. Ik ben bang dat t inderdaad in de geturl ligt..

    Weet jij of Indy HTTP thread safe is? Kan ik daarvan meerdere instanties openen in mijn threads?

  15. #30
    Ik weet natuurlijk niet wat jij in TEPGChannelThread.ScrapeChannel doet.
    Maar hoe snel is het nu om die 100 channels te scrapen met threads? (je had het over een paar seconden)

    Wat maakt het dan uit dat de threads een paar milliseconden op het einde moeten wachten tot ze allemaal klaar zijn?

    Of ik heb iets niet goed begrepen.

    Tevens kun je natuurlijk in je Synchronize(WriteXMLTV) inbouwen dat je visueel kunt zien welke threads klaar zijn. Alles wat nog geen Synchronize heeft gedaan moeten alle andere dus op wachten maar dat mag geen probleem zijn zolang je het scrapen van 1 channel niet op zou kunnen delen.

Page 2 of 5 FirstFirst 1 2 3 4 ... LastLast

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •