?
Solved

Threading and 'Canvas does not allow drawing' exception.

Posted on 2006-11-23
3
Medium Priority
?
572 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:2266180
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 2
3 Comments
 
LVL 21

Accepted Solution

by:
ziolko earned 500 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:2266180
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

On Demand Webinar: Networking for the Cloud Era

Did you know SD-WANs can improve network connectivity? Check out this webinar to learn how an SD-WAN simplified, one-click tool can help you migrate and manage data in the cloud.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Objective: - This article will help user in how to convert their numeric value become words. How to use 1. You can copy this code in your Unit as function 2. than you can perform your function by type this code The Code   (CODE) The Im…
Have you ever had your Delphi form/application just hanging while waiting for data to load? This is the article to read if you want to learn some things about adding threads for data loading in the background. First, I'll setup a general applica…
In this brief tutorial Pawel from AdRem Software explains how you can quickly find out which services are running on your network, or what are the IP addresses of servers responsible for each service. Software used is freeware NetCrunch Tools (https…
In this video, Percona Solution Engineer Dimitri Vanoverbeke discusses why you want to use at least three nodes in a database cluster. To discuss how Percona Consulting can help with your design and architecture needs for your database and infras…
Suggested Courses

764 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