Page 1 of 1

Thread safety in TeeTree

Posted: Sat Jun 11, 2005 1:41 am
by 7728099
Hi

I want to populate a tree (ie add nodes) from 2 background threads while at the same time allowing the user to interact with the tree (move nodes in design mode, open and close nodes etc). D5. TeeTree 2.0 (is there a later??)

The background threads call synchronize and I can set it up so that they each call a separate tree updating procedure or I guess I could use a critical section in the main thread so that neither worker (background) thread can update the tree until the other is finished.

Maybe that is sufficient for the issues relating to the tree nodes (GUI issues below), but maybe I would be better off replacing TteeList with TthreadedList (keeping your speed enhancements of course <g>).

OK, can you give me your advice on this aspect?

Now, what about the gui and the canvas and any tree-modifying events that the user might generate through mouse and keyboard clicks (eg delete a node) and simple display-modifying events (like scroll). ??

It seems to me that it would be nice to lock the canvas when worker thread tree-updating events are happening, but I am unsure how to do this
(and I am not sure it would be sufficinet), and I would welcome some advice.

Also, I am unsure of the relationship between the various canvasses (canvi?) and some explanation would be appreciated.

I see that
Teecanvas is just class(Tobject) but contains a
property ReferenceCanvas : Tcanvas
but TcustomTeePanel has a DelphiCanvas.


Also, in your CustomShape.pas you draw direct to Tree.Canvas ..
so the tree must have a canvas (but is that a Tcanvas?)

hmm. How do all these fit together? And what do I lock?


I see at
http://www.teechart.net/support/modules ... icle&sid=6
which is the article on realtime charting you suggest directly fiddling around with critical sections for performance reasons

// When using only a single thread, disable locking:
Chart1.Canvas.ReferenceCanvas.Pen.OwnerCriticalSection := nil;
Series1.LinePen.OwnerCriticalSection := nil;

This seems to suggest that there are already critical sections in place but maybe these are just for low level objects like Pen and Brush.


Thanks, in advance, for your help.

Populating a tree using a thread

Posted: Tue Jun 21, 2005 8:28 am
by 7728099
Is there anyone listening to this ? Tom?
Any ideas?

Posted: Sat Jun 25, 2005 10:40 pm
by Tom
Hi,

Sorry, I'm not sure if I can provide you valuable information concerning your issues. I've forwarded your question to others who might give more information.

The latest version of TeeTree is available throught the TeeChart PRO package (starting from v7). This is still TeeTree 2.0

Isn't the synchronize method sufficient? This method is specially made for updating components of the main-thread.

eg:

Code: Select all

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, TeeProcs, TeeTree;

type
  TDrawThread = class(TThread)
  private
    FID: Integer;
    FTree: TTree;
    FSleep: Cardinal;
    procedure DoAddChild;
    procedure DrawTree;
  protected
    procedure Execute; override;
  public
    constructor Create(ATree: TTree; ID: integer; ASleep: Cardinal);
  end;

  TForm1 = class(TForm)
    Tree1: TTree;
    Button1: TButton;
    DesignMode: TButton;
    AllowDeleteMode: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure DesignModeClick(Sender: TObject);
    procedure Tree1DeletedShapes(Sender: TObject);
    procedure AllowDeleteModeClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TDrawThread }

constructor TDrawThread.Create(ATree: TTree; ID: integer; ASleep: Cardinal);
begin
  FTree := ATree;
  FID := ID;
  FSleep:= ASleep;
  FreeOnTerminate := True;
  inherited Create(False);
end;

procedure TDrawThread.DoAddChild;
begin
  with FTree do
  begin
    FTree.Roots[0].AddChild(
      'Child_'+IntToStr(FTree.Roots[0].Childs.Count)
      +'_Thread'+IntToStr(FID)
    );
  end;
end;

procedure TDrawThread.DrawTree;
begin
  Synchronize(DoAddChild);
end;

procedure TDrawThread.Execute;
var i: integer;
begin
  for i := 0 to 100 do
  begin
    DrawTree;
    Sleep(FSleep);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  TDrawThread.Create(Tree1, 1, 10000);
  TDrawThread.Create(Tree1, 2, 5000);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Tree1.Designing := False;
  Tree1.AllowDelete := False;
  Tree1.AddRoot('Root');
  Tree1.Roots[0].Expanded := True;
end;

procedure TForm1.DesignModeClick(Sender: TObject);
begin
  Tree1.Designing := not Tree1.Designing;
  if Tree1.Designing then
    DesignMode.Caption := 'Designing ON'
  else
    DesignMode.Caption := 'Designing OFF'
end;

procedure TForm1.AllowDeleteModeClick(Sender: TObject);
begin
  Tree1.AllowDelete := not Tree1.AllowDelete;
  if Tree1.AllowDelete then
    AllowDeleteMode.Caption := 'Deleting ON'
  else
    AllowDeleteMode.Caption := 'Deleting OFF'
end;

procedure TForm1.Tree1DeletedShapes(Sender: TObject);
begin
  ShowMessage('A shape has been deleted');
end;

end.
I'm not sure how this will behave with certain gui issues (I think that most are handled by the use of synchronize). Deleting shapes might be an issue (eg if you delete the root in the example code, there will be an error, but this is just bad coding; Good coding should take such issues into account). You could also disable some gui handlings during thread update (eg AllowDeleting = false, Designing = false)

Tree.Canvas is a TCanvas3D class (the canvas is inherited from TCustomTeePanel). This is an abstract class, which handles the 3D issues which need to be drawn on the chart canvas.

TCustomTeePanel
|
TCustomTeePanelExtender
|
TCustomTree

An example is TTeeCanvas3D. TTeeCanvas3D uses a Delphi TCanvas to draw upon... You can access the Delphi TCanvas directly through the ReferenceCanvas property of TTeeCanvas3D.

I hope this info is somewhat useful to you.

Regards,
Tom.[/code]

Posted: Sun Jun 26, 2005 11:07 am
by Tom
I did another test, by using Canvas.Lock in the thread, instead of Synchronize.

The application is not responding as long as the lock is in place, so no events are fired as well. As always, long locks should be avoided, since a non-responding application won't be very good.

Code: Select all

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ExtCtrls, TeeProcs, TeeTree;

type
  TDrawThread = class(TThread)
  private
    FTree: TTree;
    FID : String;
    FSleep: Cardinal;
    procedure DoAddChild;
  protected

  public
    constructor Create(ATree: TTree; ID: String; ASleep: Cardinal);
  end;

  TDrawSynchronizedThread = class(TDrawThread)
  private
    procedure DrawTree;
  protected
    procedure Execute; override;
  public
  end;

  TDrawLockedThread = class(TDrawThread)
  private
  protected
    procedure Execute; override;
  public
  end;

  TForm1 = class(TForm)
    Tree1: TTree;
    RynSynchroButton: TButton;
    DesignMode: TButton;
    AllowDeleteMode: TButton;
    RunLockedButton: TButton;
    procedure RynSynchroButtonClick(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure DesignModeClick(Sender: TObject);
    procedure Tree1DeletedShapes(Sender: TObject);
    procedure AllowDeleteModeClick(Sender: TObject);
    procedure RunLockedButtonClick(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TDrawThread }

constructor TDrawThread.Create(ATree: TTree; ID: String; ASleep: Cardinal);
begin
  FTree := ATree;
  FID := ID;
  FSleep:= ASleep;
  FreeOnTerminate := True;
  inherited Create(False);
end;

procedure TDrawThread.DoAddChild;
begin
  with FTree do
  begin
    FTree.Roots[0].AddChild(
      'Child_'+IntToStr(FTree.Roots[0].Childs.Count)
      +'_Thread_'+FID
    );
  end;
end;

{ TDrawSynchronizedThread }
procedure TDrawSynchronizedThread.DrawTree;
begin
  Synchronize(DoAddChild);
end;

procedure TDrawSynchronizedThread.Execute;
var i: integer;
begin
  for i := 0 to 100 do
  begin
    DrawTree;
    Sleep(FSleep);
  end;
end;

{ TDrawLockedThread }

procedure TDrawLockedThread.Execute;
var i: integer;
begin
  FTree.Canvas.ReferenceCanvas.Lock;   //placed here to see what happens with long lock
  for i := 0 to 10 do
  begin
    //FTree.Canvas.ReferenceCanvas.Lock;
    DoAddChild;
    //  FTree.Canvas.ReferenceCanvas.UnLock;
    Sleep(FSleep);
  end;
  FTree.Canvas.ReferenceCanvas.UnLock;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Tree1.Designing := False;
  Tree1.AllowDelete := False;
  Tree1.AddRoot('Root');
  Tree1.Roots[0].Expanded := True;
end;

procedure TForm1.DesignModeClick(Sender: TObject);
begin
  Tree1.Designing := not Tree1.Designing;
  if Tree1.Designing then
    DesignMode.Caption := 'Designing ON'
  else
    DesignMode.Caption := 'Designing OFF'
end;

procedure TForm1.AllowDeleteModeClick(Sender: TObject);
begin
  Tree1.AllowDelete := not Tree1.AllowDelete;
  if Tree1.AllowDelete then
    AllowDeleteMode.Caption := 'Deleting ON'
  else
    AllowDeleteMode.Caption := 'Deleting OFF'
end;

procedure TForm1.Tree1DeletedShapes(Sender: TObject);
begin
  ShowMessage('A shape has been deleted');
end;

procedure TForm1.Tree1SelectShape(Sender: TTreeNodeShape);
begin
  ShowMessage('A shape has been selected');
end;

procedure TForm1.RynSynchroButtonClick(Sender: TObject);
begin
  TDrawSynchronizedThread.Create(Tree1, 'Sync1', 10000);
  TDrawSynchronizedThread.Create(Tree1, 'Sync2', 5000);
end;

procedure TForm1.RunLockedButtonClick(Sender: TObject);
begin
  TDrawLockedThread.Create(Tree1, 'Run1', 1000);
  TDrawLockedThread.Create(Tree1, 'Run2', 2000);
end;

end.

Thread safety in Teetree

Posted: Fri Jul 08, 2005 11:44 pm
by 7728099
thanks Tom,
there is enough there to get me started .. I will post here if there are any issues or if I find other approaches