Avatar billede jespersp Nybegynder
06. september 2006 - 11:37 Der er 6 kommentarer og
1 løsning

Overføre Tung GraphPlot i TTimer til TThread?

Hej Alle,

Jeg har lavet et program der opsamler nogen data fra en serial port og plotter dem i nogen grafer.

Indtil videre kører programmet således, at jeg har en TTimer der kører hver 100ms og så opdaterer grafen hvis der er nye data. Problemet er at når der kommer mange data, således at det tager flere sekunder om at opdatere plottet, så hakker mit program ubruligt meget (fx. har jeg en command line promt hvor man kan sende kommandoer den anden vej).

Kan man få programmet til at køre mere "smooth", hvis jeg nu fx plottede grafen i en thread? Eller burde jeg gå efter at sætte nogen af de andre funktioner, som programmet udfører, over i en thread (fx dataopsamlingen - tager ingen CPU tid, men bliver også påviket af de langsomme grafer).

Mvh, Jesper.
Avatar billede psycosoft-funware Nybegynder
06. september 2006 - 16:18 #1
det er altid godt at have sådanne funktioner/procedure i et seperat Thread, så du ungår at programmet hakker/fryser :)
Avatar billede jespersp Nybegynder
07. september 2006 - 11:41 #2
>psycosoft-funware: Med "sådanne", mener du der GraphPlot funktionen, ikke? Det var også det jeg tænkte, men er der ikke nogen problemer med at have fælles data med resten af programmet - samt at opdatere en grafisk komponent fra en TThread?

Programmets funktioner (opdelt efter prioritet):

1. Opsamling af data fra seriel port. Lagring i Datastruktur (Dynamic Arrays)
2. GUI (Command line + Script editor + Manipulering (e.g. zoom) af grafer + valg af fremvisnings type)
3. Grafisk fremvisning af data fra Datastrukturen.
  a) Hentning af data til midlertidige variabler.
  b) Opsætning af TBitmap til at tegne på. (benytter en graf-komponent baseret på TPMATH)
  c) Tegning grafer udfra de midlertidige variabler
  d) Nedlukning af midlertidige variabler

Punkt 3c tager langt det meste af tiden, og jeg vil helst ikke have at det på virker punkt 1 og punkt 2.

Er ikke nogen ørn til objektorienteret Delphi - er selvlært med basis i assembler programmering fra C64 + Amiga.

Er der nogen der kan hjælpe mig igang ?
Avatar billede hrc Mester
07. september 2006 - 14:34 #3
Drop de dynamiske arrays. De er langsomme (og ineffektive her)! Data fra den serielle port skulle du proppe i en TQueue og så pop'e dem ud på en graf via en tråd der looper og sleep(0)'er på skift.

Der er lige noget mht. låsning af køen, men det kan laves meget simpelt ved at stoppe tråden fra at hente fra køen mens man propper i. En meget simpel mutex.

Det som du spørger om hjælp af er kun 1 og 3c, ikke? Hvad er det for midlertidige variable du skriver om?

Kan godt prøve at se på det i aften - ingen garanti.
Avatar billede jespersp Nybegynder
07. september 2006 - 17:38 #4
>hrc
Ved godt de dynamiske arrays måske ikke er verdens hurtigste, men det er nu ikke dem der er problemet her....
Tror måske du misforstår.. Det jeg måler er fx temperaturmålinger som funktion af tid... Jeg får en måling fra forskellige temperatur sensorer hver ca. 0.1 sekund. Disse skal lagres i hukommelsen, således at grafen kan vise så hvad temperaturen var i forskellige områder af apparatet som funktion af tid.

Hvis jeg brugte TQueue og pop'ede ville de gamle værdier vel forsvinde igen ?!?!
Avatar billede jespersp Nybegynder
07. september 2006 - 17:42 #5
De midlertidige variabler benyttes til :
1) Ændre formatet af data til det som plot-rutinen forstår.
2) Sikre at der ikke plottes halv-færdige data, hvis der skulle opsamles nye data mens plottet opdaterer...
Avatar billede hrc Mester
07. september 2006 - 22:40 #6
Jeg mener nu stadig godt at du kan bruge data fra dine loggere i en TQueue. Jeg har lavet nedenstående lille protram og det eneste "svære" punkt er, om min mutex-konstruktion er solid nok.

Prøv at kigge på det. Jeg mener i alt fald den er hurtigere end dine /&%#/!"% dynamiske lister.

unit FMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls, TeeProcs, TeEngine, Chart, StdCtrls,
  UTempLog, Series;

type
  TfrmMain = class(TForm)
    cTemps: TChart;
    Timer: TTimer;
    pBottom: TPanel;
    cbDataFed: TCheckBox;
    Series1: TLineSeries;
    lWait: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure TimerTimer(Sender: TObject);
    procedure cbDataFedClick(Sender: TObject);
  private
    fTempLogQueue : TTempLogQueue;
    fPlotThread : TPlotThread;
  public
  end;

var
  frmMain: TfrmMain;

implementation

{$R *.dfm}

procedure TfrmMain.cbDataFedClick(Sender: TObject);
begin
  Timer.Enabled := (Sender as TCheckBox).Checked;
end;

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  Randomize;
  fTempLogQueue := TTempLogQueue.Create;

  fPlotThread := TPlotThread.Create(fTempLogQueue,cTemps);
  fPlotThread.Resume;
end;

procedure TfrmMain.FormDestroy(Sender: TObject);
begin
  Timer.Enabled := false;
  while fTempLogQueue.Count > 0 do
    Sleep(11);

  fPlotThread.Terminate; // Frigiver sig selv
  fTempLogQueue.Free;
end;

procedure TfrmMain.TimerTimer(Sender: TObject);
begin
  fTempLogQueue.Push(TTempLogData.Create(now,random(100)));
  lWait.Caption := format('Ventet: %d',[fTempLogQueue.Waiting]);
  Application.ProcessMessages;
end;

end.


Min kø-unit og tråd:

unit UTempLog;

interface

uses
  SysUtils, Classes, ContNrs, TeeProcs, TeEngine, Chart;

type
  TPlotThread = class;
  TTempLogData = class;
  TTempLogQueue = class;

  TPlotThread = class(TThread)
  private
    fChart : TChart;
    fTempLogQueue : TTempLogQueue;
    fTempLogData : TTempLogData;
  public
    constructor Create(aTempLogQueue : TTempLogQueue; aChart : TChart);
    procedure Execute; override;
    procedure PlotValue;
  end;

  TTempLogData = class
  private
    fDateTime : TDateTime;
    fValue : double;
  public
    constructor Create(const aDateTime : TDateTime; const aValue : double);
    property DateTime : TDateTime read fDateTime;
    property Value : double read fValue;
  end;

  TTempLogQueue = class(TQueue)
  private
    fLocked : boolean;
    fWaiting : integer;
  public
    constructor Create;
    procedure Push(aTempLogData : TTempLogData);
    function Pop : TTempLogData;
    function Peek : TTempLogData;
    property Locked : boolean read fLocked write fLocked;
    property Waiting : integer read fWaiting;
  end;

implementation

{ TTempLogData }

constructor TTempLogData.Create(const aDateTime: TDateTime; const aValue: double);
begin
  inherited Create;
  fDateTime := aDateTime;
  fValue := aValue;
end;

{ TTempLogQueue }

constructor TTempLogQueue.Create;
begin
  inherited Create;
  fLocked := false;
  fWaiting := 0;
end;

function TTempLogQueue.Peek: TTempLogData;
begin
  result := TTempLogData(inherited Peek);
end;

function TTempLogQueue.Pop: TTempLogData;
begin
  result := TTempLogData(inherited Pop);
end;

procedure TTempLogQueue.Push(aTempLogData: TTempLogData);
begin
  while fLocked do
    sleep(7); // Primtal for ikke at løbe ind i den anden sleeper hele tiden

  fLocked := true;
  try
    inherited Push(aTempLogData);
  finally
    fLocked := false;
  end;
end;

{ TPlotThread }

constructor TPlotThread.Create(aTempLogQueue: TTempLogQueue; aChart : TChart);
begin
  inherited Create(true);
  fTempLogQueue := aTempLogQueue;
  fTempLogData := nil;
  fChart := aChart;
  FreeOnTerminate := true;
end;

procedure TPlotThread.Execute;
begin
  inherited;
  while not Terminated do
  begin
    if fTempLogQueue.Count > 0 then
    begin
      while fTempLogQueue.Locked do
        sleep(3); // Primtal for ikke at løbe ind i den anden sleeper

      fTempLogQueue.Locked := true;
      try
        fTempLogData := fTempLogQueue.Pop;
      finally
        fTempLogQueue.Locked := false;
      end;

      Synchronize(PlotValue);

      if assigned(fTempLogData) then
        FreeAndNil(fTempLogData); // Skil os af med forbrugte data.
    end
    else
      sleep(0);
  end;
end;

procedure TPlotThread.PlotValue;
begin
  if fChart.Series[0].Count > 300 then
    fChart.Series[0].Delete(0);
  fChart.Series[0].AddXY(fTempLogData.fDateTime,fTempLogData.Value);
end;

end.

I øvrigt bør du placere dine temporære variable i tråden så den kan for lov til at tygge på det. Intet må forstyrre din dataopsamler. Hvis du vil have en kopi af koden så kan du skrive til mig på hrc_public at hotmail.
Avatar billede jespersp Nybegynder
11. september 2006 - 14:04 #7
Jeg har løst problemet ved kun at indsætte en Application.Processmessages i plotter-rutinen. Jeg kalder GraphPlot fra TTimer, som nu er sat til at køre hver 10ms. Programmet/Dataopsamling kører smooth selvom den skal plotte >100k punkter.

hrc får points for hjælpen.
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester