我的线程类中包含了对VCL组件的操作,并且这些组件不在一个unit中,还要调用一些写在其他单元的方法、函数。我要的效果是,在一个命令执行过程中,可以通过对程序界面的操作停止该命令的执行。如果在execute方法中用synchronize()方法,则程序在线程执行中不会响应其他的操作,须等到线程执行后方可。我这个线程类应该怎么写?是不是要把VCL组件和一些方法函数变为参数传递给线程?请各位指点一下。

解决方案 »

  1.   

    Delphi只允许一个线程访问用户界面
    但好象用synchronize()同步方法,可以实现的要的功能吧?
      

  2.   

    To cyblueboy83(爱情白痴—电脑迷) :用synchronize()方法只能实现对组件的操作以及方法的调用,但是不能在执行过程中终止线程执行。
      

  3.   

    可以用一个全局变量比如IsStop,在命令按钮中写 IsStop := True;
    在线程中Execute中调用的方法里面检测IsStop 的值
    if IsStop then
    begin
      Abort; //或者EXIT;
    end;
      

  4.   

    可以用SendMessage或PostMessage向线程发自定义消息,在线程中处理这个消息即可
    如:SendMessage(AThread1.Handle, WM_USER + 1000, 0, 0);
    在线程AThread1中处理WM_USER + 1000这个消息
      

  5.   

    在TThread中有一个Terminated的属性,你可以用它来中止线程。
    一般来说,用线程都是工作线程,应该都有一个循环体例如
    procedure WordThread()
    begin
    ....
    while (not FEnd) do
    begin
    ....
    if self.Terminated then break;
    ....
    end;
    .....
    end;procedure Button.Click()
    begin
       AThread.Terminated := true;
    end;
      

  6.   

    我的问题是这个意思:
       如果运用synchronize()方法,那么应该怎么做?
       类似这样的问题:有一个进度条,先在窗体的方法中进行初始化,然后用线程的方法操作进度条,这样的话它会抱报错。最后关掉窗体时,报一个“句柄错误”。是不是操作组件的方法都要写在线程中,是不是需要在线程类中设计组件类的变量,用于初始化时类似FProg=form1.progressbar;这样。
       请各位指点。
      

  7.   

    首先要搞清楚下面几点:
    1、在线程里面调用Synchronize是迫不得已的时候才会做的,因为调用该方法后,线程将附着在主线程里运行,也就是说多线程此时已经退化成为单线程;
    2、我们都知道,为了线程安全,从不同线程里面访问VCL可视组件是需要调用Synchronize方法的。但是,一定要把这种情况尽最大可能地减少。如果你的一个线程里,绝大多数甚至全部的时间都是在调用Synchronize方法,那么根据上面第一点,你相当于用多线程来实现单线程。从成本收益来说,这是不划算的,你还不如不用多线程。
    3、基于第二点,并且假设你的设计是符合成本收益最大化原则的,那么,你的线程里面肯定只有少数时间在用Synchronize访问VCL控件,而大多数时间在做其他的不涉及Synchronize的操作。例如,你可能在一个循环里面做排序的操作,每次循环的最后一步调用Synchronize显示一下结果,然后再次脱离主线程进入下一次循环。
    4、这样,在你的线程执行过程中,主线程就有机会去响应用户的操作,而不会出现线程执行后不响应用户操作的情况。此时,用按钮事件和线程的Terminated属性,就可以轻易地达成你的目的。但是,我高度怀疑,你把整个线程的Execute都放在Synchronize里面执行了。如果真的是这样,我认为你应该从整体上修改一下你的设计。
      

  8.   

    可能是我描述的不好,大家没明白我的意思。以下是我写的代码,也是问题出现的地方。我的目的是,按下button1进度条前进,前进过程中,按下button2,进度条回到0位置。可当我按下button1后,程序报错"canvas does not allow drawing",程序关闭后又报"无效的窗口句柄"错误。我应如何对这个程序进行改动,才能达到我要的效果,为什么?unit Unit1;interfaceuses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, StdCtrls, ComCtrls;type
      Tdd=class(tthread)
        protected
        procedure execute;override;
        procedure sss;
        procedure aaa;
        procedure bbb;
        procedure InitProgBar(const stepcount:integer;const curpos:integer=0);
          FProgRect:trect;      //进度条组件尺寸
      end;
      TForm1 = class(TForm)
        Button1: TButton; Button2: TButton; 
        StatusBar1: TStatusBar;
        procedure Button1Click(Sender: TObject);
        procedure Button2Click(Sender: TObject);
        procedure FormClose(Sender: TObject; var Action: TCloseAction);
        procedure StatusBar1DrawPanel(StatusBar: TStatusBar;
          Panel: TStatusPanel; const Rect: TRect);
      private
        { Private declarations }
        dd:tdd;
        ProgressBar2: TProgressBar;  //进度条组件
        ProgressBarRect:trect;      //进度条组件尺寸
        procedure ss(sender:tobject);
        procedure InitProgBar(const stepcount:integer;const curpos:integer=0);
        //初始化进度条
      public
        { Public declarations }
      end;var
      Form1: TForm1;implementation
    uses mainfrm;
    {$R *.dfm}{ Tdd }procedure Tdd.aaa;
    begin
      showmessage('aaa');
      self.bbb;  
    end;procedure Tdd.bbb;
    begin
      showmessage('bbb');
      self.sss;
    end;procedure Tdd.execute;
    begin
      inherited;
      //self.FreeOnTerminate:=true;
      self.OnTerminate:=form1.ss;
      self.aaa;
    end;procedure Tdd.sss;
    var i:integer;
    begin
      form1.InitProgBar(2000,0);
      while form1.ProgressBar2.Position<form1.ProgressBar2.Max do
      begin
        if self.Terminated=false then
        begin
          for i:=0 to 200000 do;
          form1.ProgressBar2.StepIt;
          FrmMain.connection:=true;
        end
        else break;
      end;
      form1.ProgressBar2.Free;
    end;procedure TForm1.Button1Click(Sender: TObject);
    begin
      dd:=Tdd.Create(form1);
    end;procedure TForm1.ss(sender: tobject);
    begin
      self.ProgressBar2.Position:=0;
    end;procedure TForm1.Button2Click(Sender: TObject);
    begin
      dd.Terminate;
    end;procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
    begin
      if dd<>nil then
      begin
        dd.Terminate;
        dd.Free;
      end;
      action:=cafree;
    end;procedure TForm1.StatusBar1DrawPanel(StatusBar: TStatusBar;
      Panel: TStatusPanel; const Rect: TRect);
    begin
      ProgressBarRect:= Rect;
    end;procedure TForm1.InitProgBar(const stepcount, curpos: integer);
    begin
      self.ProgressBar2:= tprogressbar.Create(self);
      with ProgressBar2 do
      begin
        top:= ProgressBarrect.Top; left:= ProgressBarrect.Left;
        width:= ProgressBarrect.Right-ProgressBarrect.Left;
        height:= ProgressBarrect.Bottom-ProgressBarrect.Top;
        visible:=true;   parent:=self.StatusBar1;
        min:=0;      max:=stepcount;
        step:=1;     Position:=curpos;
      end;
    end;end.
      

  9.   

    忘了说了,其中有一个循环,可以用synchronize(),再在循环中用application.postmessage;。如果没有循环过程,又该怎么实现?
      

  10.   

    只能严厉地说:楼主,你的基础太不扎实了!你来看看你的错误:
    1、dd:=Tdd.Create(form1);
    这个能编译过去?没有看到你重载了Thread的Create;2、来看看你的线程起来后,一路走下来的历程:
    excute -> self.aaa -> self.bbb -> self.sss -> form1.InitProgBar(2000,0) -> ... -> form1.ProgressBar2.Free 
    好,到了这里,你的form1里面的PogressBar2已经free掉了,不存在了。
    然后接下来你知道会怎么样吗?
    因为你前面指定了self.OnTerminate:=form1.ss;所以,现成就会调用Form1.ss。再看看你在form1.ss里面做了什么:ProgressBar2.Position:=0;
    能不出错才真正怪了,ProgressBar2早就已经free掉了,你还要调用它的属性!3、你在线程里面所有对form1上面的控件(例如ProgressBar2)进行的操作,都没有调用Synchronize,这是严重的错误;4、在线程里面调用Application.postMessage?正常的情况不会出现如此奇怪的组合,线程用得好的话,根本就不可能产生主界面死掉的情况(就算你的线程死掉了,主界面也不会死),也不必要调用PostMessage去强制处理消息!除非你用的是单线程,才会需要在做很耗时间的循环时,偷空PostMessage一下,不让主界面死掉。5、结构太混乱了,应该在form1的东西,出现在线程里面,使得一切就向面条一样的缠绕着。写的好的程序,结构是非常美丽和清晰的,也是非常的简单的。你能用面向对象的东西,作出以前用Goto作出的效果,真的不知道赞美还是贬低!6、变量、过程函数命名一塌糊涂,dd是什么?ss是什么?sss是什么?想让人看,还是不想让人看?楼主,建议你:
    在这个往往充斥着某种语言好或者坏的极端的争论的世界里面,要安静下来,多学学基础的东西。你要相信,当你在思维里面已经把一切都理顺了,不用语言,就用平常生活油盐酱醋的比喻,用公共汽车排队来比喻,用分工协作来比喻,用这些来帮助你理解什么叫做流程,一切搞清楚了,你要相信,用什么语言来实现,已经是非常简单的了。不要学着人家,拿一本破书照着抄写,然后就以为一切都可以了!你忽视了最重要的东西,你不去搞懂这个最重要的东西,反而去弄皮毛的东西。你不搞懂线程是什么东东,你写出来的东西,能不奇怪么?这是一个公司聚餐后的夜晚,红酒的作用充斥在我的身体,直到我写完上面的这些。不要见怪,我并没有恶意。
      

  11.   

    >>使得一切就向面条一样的缠绕着
    面条没有煮过的时候,是一根根的,条理很清晰^_^
      

  12.   

    看来CSDN强人还是不少,我以前做过一个硬件控制的程序,一个线程监听一个硬件设备的输入,总共有10来个线程,每一个线程随时都要将设备的状态信息反映到主界面上。我采用的方法就是向主线程PostMessage,在工作线程中分配内存,主线程接受到消息后释放内存
      

  13.   

    酒精还有有影响的,我把Applicaton.ProcessMessages写成了Application.PostMessage.
    但我想我没有误会楼主的意思,楼主指的也就是ProcessMessages吧,否则Application.PostMessage,Application看来是没有这个方法的。
      

  14.   

    To zeroxing(胸无半点墨,腰有万贯财;此般理想高,怎奈做不到!--) :
    对于你的第一个提议,那是我的笔误。第二个,我是希望在我按下另一个按钮后,正运行的线程能停止运行,也许用挂起会更好。第三个,如果我用了Synchronize()方法,在一个线程执行时,我就没法让用户结束或挂起它,这样与用单线程没有操作上的区别。第四个,我需要多线程并非因为耗费时间的循环,而是因为耗费时间的下载过程,客户的服务器是无线上网(我们提过用专线,但无效),客户端用ftp下载很多文件,因为速度慢所以等待时间长且程序位单线程程序,所以下载执行不完界面就不响应用户了(死了)。第五,我真的没写过线程程序,所以有很多地方做得不好,我接受批评。第六,对于起名,那只不过是我的试验程序,当然我还是要接收你的建议应该让名字有意义。
    虽然一直没有好的方法可以解决我的问题,但我还是感谢各位。
      

  15.   

    用线程,一定可以达到你的目的的。你的线程在后台跑着,而主界面则不受影响。线程可以偷空把自己的信息通过Synchronize放到主界面上面去显示给用户看,用户也可以随时终止线程。线程里面跑FTP,我做过的。
      

  16.   

    To zeroxing(胸无半点墨,腰有万贯财;此般理想高,怎奈做不到!):
       按你的建议我试了一下,有个问题,请见程序代码,简单写了:
    procedure Tdd.execute;
    begin
      inherited;
      self.OnTerminate:=form1.termdd;
      self.prog;
    end;procedure Tdd.moveprogbar;
    begin
      form1.ProgressBar1.StepIt;
    end;procedure Tdd.prog;
    var i,j:integer;
    begin
      for j:=0 to 2000 do
      begin
        if self.Terminated=false then
        begin
          for i:=0 to 2000000 do;//代表进行的一些数据库操作或传数据
          synchronize(self.moveprogbar);
          FrmMain.connection:=true;
        end
        else break;
      end;
    end;
    1.但在线程运行中,如果我点一按钮terminate它,不起作用,suspend它则起作用。
    2.如果在它运行的时候,移动了一下form1,那么form1界面移到位置后就不再立即响应用户操作了,很奇怪。
    3.还有就是进度条走完它执行了form1.termdd,但它的terminated还是为false,只有在进度条走完并显示调用terminate后terminated才为true。
    4.“线程里面跑FTP,我做过的。”ftp主键是在线程里生成的吗,怎么做的?如果是,传输时ftp事件你又是怎么写的?如果不是,那么ftp组建仍是VCL,这么用也要对ftp组件的传输过程加synchronize?
      

  17.   

    1.但在线程运行中,如果我点一按钮terminate它,不起作用,suspend它则起作用。
    =================
    terminate 之前最好把运行中的线各挂起
      

  18.   

    第一和第二我猜不出是什么原因,我想可能你真正的代码和贴出来的还有些差异。
    FrmMain.connection是什么东西?
    1里面的按钮响应事件是怎么写的?3,terminated除非你显性调用terminate或者terminated := true,否则它不会自己成为false的,即便线程跑完了,它的作用就仅仅是提供用户一个变量用来控制excute里面循环的退出,你也可以不用它,而定义自己的一个boolean变量,例如myTerminated这样。4,Ftp组件是在线程里面动态生成的,所以不涉及synchronize的问题(其实上,这种不可视的组件一般来说是线程安全的)。
      

  19.   

    procedure fireTaskNormalMessage;
    begin
      //往主窗体写信息。现写的,不是我原来代码的。
      FrmMain.mmLog.Lines.Add(FTaskStatus);
    end;//FTP的事件响应过程。设定看Excute里面。
    procedure TUploadTask.OnIdFTPWork(Sender: TObject; AWorkMode: TWorkMode;
      const AWorkCount: Integer);
    var
      fPercent: double;
      fFileSize: double;
      fWorkCount: double;
    begin
      if FCurFileSize <> 0 then
        fPercent := (AWorkCount / FCurFileSize) * 100 
      else
        fPercent := 0.0;  fFileSize := FCurFileSize;
      fWorkCount := AWorkCount;
      FTaskStatus := Format('进度:%.0n字节,总共:%.0n字节,完成率%3.3f%s',
        [fWorkCount, fFileSize, fPercent, '%']);
      FTaskEventType := tetProgress;
      self.Synchronize(fireTaskEvent);
    end;
    procedure TUploadTask.execute;
    var
      IdFtp: TIdFtp;
      i: integer;
    begin
      IdFtp := TIdFtp.Create(nil);
      IdFtp.OnStatus := OnIdFTPStatus;
      IdFtp.OnWork := OnIdFtpWork;//////////事件是这么设定的。
      try
        try
          with IdFtp do
          begin
            TransferType := ftBinary;
            Host := FFtpHost;
            Port := FFtpPort;
            Passive := FPassiveMode;
            
            User := FFtpUser;
            Password := FFtpPwd;        //连接服务器。
            Connect(true);
            //转到工作目录。
            ChangeDir(FFtpPath);
            FTaskStatus := '成功登录到FTP服务器,当前目录:' + retrieveCurrentDir;
            self.Synchronize(fireTaskNormalMessage);        FTotalFileNum := FFilesList.Count;
            FTaskStatus := Format('总共%d个文件',[FTotalFileNum]);
            self.Synchronize(fireTaskNormalMessage);
            FCurFileSeq := 0;
            for i := 0 to FTotalFileNum - 1 do
            begin
              FCurFileSeq := i + 1;
              FCurFileName := FFilesList.Strings[i];
              FTaskStatus := Format('第%d个文件:%s', [FCurFileSeq, FCurFileName]);
              self.Synchronize(fireTaskNormalMessage);
              Put(FCurFileName, ExtractFileName(FCurFileName));
            end;
            
            FEndSuccess := true;
          end;
        except
          on E: Exception do
          begin
            FTaskStatus := '发生错误,任务停止执行。原因:' + E.Message;
            FEndSuccess := false;
            self.Synchronize(fireTaskNormalMessage);
          end;
        end;
      finally
        IdFtp.Disconnect;
        IdFtp.Free;
      end;
    end;