Solved

Threading and 'Canvas does not allow drawing' exception.

Posted on 2006-11-23
3
552 Views
Last Modified: 2010-04-05
I must be missing something here.

I made the following small demo application just in case I was doing something wrong in the other, but seems that maybe my understanding of threads and events might be the issue.

basically what I am trying to do is make a client server application using indy tcpclient/server where on the client side I have a thread that is trying to read data from the server, and when success, fire an event letting the main vcl trhead of the application know that there is data available that can be read. so the client will read up the data and display it.

problem is that when firing the event, a 'Canvas does not allow drawing' exception is being thrown.

AFAIK, such events like the one in the demo app are executed from the main vcl thread. is this not so? because if I know right, I cannot explain why I am getting this exception.

The way I used to to these client/server app back in the old days was using a timer and readln(terminator,5); (checked my old sources to see how I done it) but it's just not elegant enough for me anymore :D

But since I have to move on with the project, I'll do it with the timer, but until then I'll start up a new thread about the best way to approach this.

So points go to the one who can explain why that exception is being thrown. (I repeat, indy solution goes to another thread)

code follows

unti1.dfm
object Form1: TForm1
  Left = 192
  Top = 114
  Width = 870
  Height = 640
  Caption = 'Form1'
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object Button1: TButton
    Left = 280
    Top = 96
    Width = 75
    Height = 25
    Caption = 'Button1'
    TabOrder = 0
    OnClick = Button1Click
  end
  object client: TIdTCPClient
    MaxLineAction = maException
    ReadTimeout = 0
    Host = 'localhost'
    Port = 488
    Left = 112
    Top = 104
  end
  object server: TIdTCPServer
    Active = True
    Bindings = <
      item
        IP = '0.0.0.0'
        Port = 488
      end>
    CommandHandlers = <>
    DefaultPort = 0
    Greeting.NumericCode = 0
    MaxConnectionReply.NumericCode = 0
    OnExecute = serverExecute
    ReplyExceptionCode = 0
    ReplyTexts = <>
    ReplyUnknownCommand.NumericCode = 0
    Left = 200
    Top = 112
  end
  object IdAntiFreeze1: TIdAntiFreeze
    IdleTimeOut = 50
    OnlyWhenIdle = False
    Left = 112
    Top = 64
  end
end


--------------------------------
unit1.pas
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, IdTCPServer, IdBaseComponent, IdComponent, IdTCPConnection,
  IdTCPClient, StdCtrls, unit2, IdAntiFreezeBase, IdAntiFreeze;

type
  TForm1 = class(TForm)
    client: TIdTCPClient;
    server: TIdTCPServer;
    Button1: TButton;
    IdAntiFreeze1: TIdAntiFreeze;
    procedure Button1Click(Sender: TObject);
    procedure serverExecute(AThread: TIdPeerThread);
  private
    { Private declarations }
    r:TReader;
    procedure OnCommandAvailable(Sender:TObject; name:string);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  client.connect;
  r:=treader.create(client);
  r.OnCommandAvailable:=OnCommandAvailable;
  client.WriteLn('test string');
end;

procedure TForm1.OnCommandAvailable(Sender: TObject; name: string);
begin
  showmessage(name+' received.');
  showmessage(client.readln+' read.');
end;

procedure TForm1.serverExecute(AThread: TIdPeerThread);
begin
  AThread.Connection.WriteLn('something else');
end;

end.


---------------------

unit2.pas
unit Unit2;

interface

uses
  Classes, IdTcpConnection;

type
  TOnCommandAvailable=procedure(Sender:TObject; name:string) of object;

  TReader = class(TThread)
  private
    FOnCommandAvailable: TOnCommandAvailable;
    con:TIdTcpConnection;
    { Private declarations }
  public
    constructor create(c:TIdTcpConnection); reintroduce;
  protected
    procedure Execute; override;
  published
    property OnCommandAvailable:TOnCommandAvailable read FOnCommandAvailable write FOnCommandAvailable;
  end;

implementation

{ Important: Methods and properties of objects in visual components can only be
  used in a method called using Synchronize, for example,

      Synchronize(UpdateCaption);

  and UpdateCaption could look like,

    procedure Reader.UpdateCaption;
    begin
      Form1.Caption := 'Updated in a thread';
    end; }

{ Reader }

uses sysutils;

constructor TReader.create(c: TIdTcpConnection);
begin
  inherited Create(false);
  FreeOnTerminate:=true;
  con:=c;
end;

procedure TReader.Execute;
var s:string;
begin
  { Place thread code here }
  try
    while not terminated do
    begin
      s:=con.ReadLn(' ');
      if assigned(OnCommandAvailable) then
        OnCommandAvailable(self, s);
    end;
  except
    on e:exception do
    begin
      s:=e.message;  // put the breakpoint here.
      OnCommandAvailable(self, s);
    end;
  end;
end;

end.
 

0
Comment
Question by:ciuly
  • 2
3 Comments
 
LVL 21

Accepted Solution

by:
ziolko earned 125 total points
ID: 18003875
if assigned(OnCommandAvailable) then
        OnCommandAvailable(self, s);

is called in thread's Execute so this event is triggered by thread, it's the same as calling ShowMessage() inside Execute,
pass to TReader main form's handle instead event handler and use PostMessage()
is the first solution that comes into my mind.

ziolko.
0
 
LVL 28

Author Comment

by:ciuly
ID: 18003917
indeed, that works.
so actually the event is being executed in the context of the thread that called the event and not the main vcl. Makes sense especially since you can make an app with no vcl in it. don't know why it was the othre way around in my head. now that I think of how it could be implemented in a generic class, it looks stupid.
oh well, thanks for the heads up :)

0
 
LVL 21

Expert Comment

by:ziolko
ID: 18006730
event is executed by thread because:
r.OnCommandAvailable:=OnCommandAvailable;
is nothing more than copying pointer to class procedure, so

if assigned(OnCommandAvailable) then
        OnCommandAvailable(self, s);

executes procedure at given pointer which still belongs to vcl thread.

ziolko.
0

Featured Post

What Is Threat Intelligence?

Threat intelligence is often discussed, but rarely understood. Starting with a precise definition, along with clear business goals, is essential.

Join & Write a Comment

Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
In my programming career I have only very rarely run into situations where operator overloading would be of any use in my work.  Normally those situations involved math with either overly large numbers (hundreds of thousands of digits or accuracy re…
This demo shows you how to set up the containerized NetScaler CPX with NetScaler Management and Analytics System in a non-routable Mesos/Marathon environment for use with Micro-Services applications.
When you create an app prototype with Adobe XD, you can insert system screens -- sharing or Control Center, for example -- with just a few clicks. This video shows you how. You can take the full course on Experts Exchange at http://bit.ly/XDcourse.

707 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

17 Experts available now in Live!

Get 1:1 Help Now