Delphi Thread(3)

来源:互联网 发布:网络流行关键词 编辑:程序博客网 时间:2024/05/21 07:33

    http://blog.csdn.net/lailai186/article/details/8776005

Delphi Thread(3)

标签: delphi多线程
4638人阅读 评论(0)收藏举报
本文章已收录于:
分类:
作者同类文章X

    TThread 详解
            我们常有工作线程和主线程之分,工作线程负责作一些后台操作,比如接收邮件

                                                                  主线程负责界面上的一些显示

    工作线程的好处在某些时候是不言而喻的,你的主界面可以响应任何操作,而背后的线程却在默默地工作。

             VCL中,工作线程执行在Execute方法中,你必须从TThread继承一个类并覆盖Execute方法,在这个方法中,所有代码都是在另一个 线程中执行的,除此之外,你的线程类的其他方法都在主线程执行,包括构造方法,析构方法,Resume等,很多人常常忽略了这一点。

    最简单的一个线程类如下

    TMyThread = class(TThread)
    protected
    procedure Execute; override;
    end;

    在Execute中的代码,有一个技术要点,如果你的代码执行时间很短,像这样,Sleep(1000),那没有关系;如果是这样Sleep (10000),10秒,那么你就不能直接这样写了,须把这10秒拆分成10个1秒,然后判断Terminated属性,像下面这样:

    procedure TMyThread.Execute;
    var
       i: Integer;
    begin
       for i := 0 to 9 do
          if not Terminated then
            Sleep(1000)
         else
            Break;
    end;

    这样写有什么好处呢

          想想你要关闭程序,在关闭的时候调用MyThread.Free,这个时候线程并没有马上结束,它调用WaitFor,等待 Execute执行完后才能释放。

           你的程序就必须等10秒以后才能关闭,受得了吗。如果像上面那样写,在程序关闭时,调用Free之后,它顶多再等一秒就 会关闭。

            为什么?答案得去线程类的Destroy中找,它会先调用Terminate方法,在这个方法里面它把Terminated设为True(仅此而 已,很多人以为是结束线程,其实不是)。

          请记住这一切是在主线程中操作的,所以和Execute是并行执行的。既然Terminated属性已为 Ture,那么在Execute中判断之后,当然就Break了,Execute执行完毕,线程类也正常释放。

    或者有人说,TThread可以设FreeOnTerminate属性为True,线程类就能自动释放。除非你的线程执行的任务很简单,不然,还是不要去理会这个属性,一切由你来操作,才能使线程更灵活强大。

              接下来的问题是如何使工作线程和主线程很好的通信,很多时候主线程必须得到工作线程的通知,才能做出响应。
    比如接收邮件,工作线程向服务器收取邮件,收取完毕之后,它得通知主线程收到多少封邮件,主线程才能弹出一个窗口通知用户

    在VCL中,我们可以用两种方法,一种是向主线程中的窗体发送消息,另一种是使用异步事件

    第一种方法其实没有第二种来得方便。想想线程类中的OnTerminate事件,这个事件由线程函数的堆栈引起,却在主线程执行。

    事实上,真正的线程函数是这个:
    function ThreadProc(Thread: TThread): Integer;

    函数里面有Thread.Execute,这就是为什么Execute是在其他线程中执行,该方法执行之后,有如下句:
    Thread.DoTerminate;

    而线程类的DoTerminate方法里面是
    if Assigned(FOnTerminate) then Synchronize(CallOnTerminate);

    显然Synchronize方法使得CallOnTerminate在主线程中执行,而CallOnTerminate里面的代码其实就是:
    if Assigned(FOnTerminate) then FOnTerminate(Self);

    只要Execute方法一执行完就发生OnTerminate事件。不过有一点是必须注意,OnTerminate事件发生后,线程类不一定会释 放,只有在FreeOnTerminate为True之后,才会Thread.Free。看一下ThreadProc函数就知道。

    依照Onterminate事件,我们可以设计自己的异步事件。

    Synchronize方法只能传进一个无参数的方法类型,但我们的事件经常是要带一些参数的,这个稍加思考就可以得到解决,即在线程类中保存参数,触发事件前先设置参数,再调用异步事件,参数复杂的可以用记录或者类来实现。

    假设这样,上面的代码每睡一秒,线程即向外面引发一次事件,我们的类可以这样设计

    [delphi] view plain copy print?
    1. TSecondEvent = procedure (Second: Integer) of object;  
    2. TMyThread = class(TThread)  
    3. private  
    4. FSecond: Integer;  
    5. FSecondEvent: TSecondEvent;  
    6. procedure CallSecondEvent;  
    7. protected  
    8. procedure Execute; override;  
    9. public  
    10. property SencondEvent: TSecondEvent read FSecondEvent  
    11. write FSecondEvent;  
    12. end;  
    13.   
    14. { TMyThread }  
    15.   
    16. procedure TMyThread.CallSecondEvent;  
    17. begin  
    18. if Assigned(FSecondEvent) then  
    19. FSecondEvent(FSecond);  
    20. end;  
    21.   
    22. procedure TMyThread.Execute;  
    23. var  
    24. i: Integer;  
    25. begin  
    26. for i := 0 to 9 do  
    27. if not Terminated then  
    28. begin  
    29. Sleep(1000);  
    30. FSecond := i;  
    31. Synchronize(CallSecondEvent);  
    32. end  
    33. else  
    34. Break;  
    35. end;   
    36. 在主窗体中假设我们这样操作线程:  
    37.   
    38. procedure TForm1.Button1Click(Sender: TObject);  
    39. begin  
    40. MyThread := TMyThread.Create(true);  
    41. MyThread.OnTerminate := ThreadTerminate;  
    42. MyThread.SencondEvent := SecondEvent;  
    43. MyThread.Resume;  
    44. end;  
    45.   
    46. procedure TForm1.ThreadTerminate(Sender: TObject);  
    47. begin  
    48. ShowMessage('ok');  
    49. end;  
    50.   
    51. procedure TForm1.SecondEvent(Second: Integer);  
    52. begin  
    53. Edit1.Text := IntToStr(Second);  
    54. end;  
    55.   
    56. 我们将每隔一秒就得到一次通知并在Edit中显示出来。  
    57.   
    58. 现在我们已经知道如何正确使用Execute方法,以及如何在主线程与工作线程之间通信了。但问题还没有结束,有一种情况出乎我的意料之外,即如果 线程中有一些资源,Execute正在使用这些资源,而主线程要释放这个线程,这个线程在释放的过程中会释放掉资源。想想会不会有问题呢,两个线程,一个 在使用资源,一个在释放资源,会出现什么情况呢,   
    59.   
    60. 用下面代码来说明:  
    61.   
    62. type  
    63. TMyClass = class  
    64. private  
    65. FSecond: Integer;  
    66. public  
    67. procedure SleepOneSecond;  
    68. end;  
    69.   
    70. TMyThread = class(TThread)  
    71. private  
    72. FMyClass: TMyClass;  
    73. protected  
    74. procedure Execute; override;  
    75. public  
    76. constructor MyCreate(CreateSuspended: Boolean);  
    77. destructor Destroy; override;  
    78. end;  
    79.   
    80. implementation  
    81.   
    82. { TMyThread }  
    83.   
    84. constructor TMyThread.MyCreate(CreateSuspended: Boolean);  
    85. begin  
    86. inherited Create(CreateSuspended);  
    87. FMyClass := TMyClass.Create;  
    88. end;  
    89.   
    90. destructor TMyThread.Destroy;  
    91. begin  
    92. FMyClass.Free;  
    93. FMyClass := nil;  
    94. inherited;  
    95. end;  
    96.   
    97. procedure TMyThread.Execute;  
    98. var  
    99. i: Integer;  
    100. begin  
    101. for i := 0 to 9 do  
    102. FMyClass.SleepOneSecond;  
    103. end;  
    104.   
    105. { TMyClass }  
    106.   
    107. procedure TMyClass.SleepOneSecond;  
    108. begin  
    109. FSecond := 0;  
    110. Sleep(1000);  
    111. end;  
    112.   
    113. end.   
    114.   
    115. 用下面的代码来调用上面的类:  
    116.   
    117. procedure TForm1.Button1Click(Sender: TObject);  
    118. begin  
    119. MyThread := TMyThread.MyCreate(true);  
    120. MyThread.OnTerminate := ThreadTerminate;  
    121. MyThread.Resume;  
    122. end;  
    123.   
    124. procedure TForm1.Button2Click(Sender: TObject);  
    125. begin  
    126. MyThread.Free;  
    127. end;  

     

    先点击Button1创建一个线程,再点击Button2释放该类,出现什么情况呢,违法访问,是的,MyThread.Free时,MyClass被释放掉了

    FMyClass.Free;

    FMyClass := nil;

    而此时Execute却还在执行,并且调用MyClass的方法,当然就出现违法访问。对于这种情况,有什么办法来防止呢,我想到一种方法,即在线程类中使用一个成员,假设为FFinished,在Execute方法中有如下的形式:

    FFinished := False;
    try
    //... ...
    finally
    FFinished := True;
    End;

    接着在线程类的Destroy中有如下形式:

    While not FFinished do
    Sleep(100);
    MyClass.Free;

    这样便能保证MyClass能被正确释放。

                 线程是一种很有用的技术。但使用不当,常使人头痛。在CSDN论坛上看到一些人问,我的窗口在线程中调用为什么出错,主线程怎么向其他线程发送消息等等,其实,我们在抱怨线程难用时,也要想想我们使用的方法对不对,

    只要遵循一些正确的使用规则,线程其实很简单

    后记

    上面有一处代码有些奇怪:FMyClass.Free; FMyClass := nil;如果你只写FMyClass.Free,线程类还不会出现异常,即调用FMyClass.SleepOneSecond不会出错。我在主线程中试了下面的代码

    MyClass := TMyClass.Create;
    MyClass.SleepOneSecond;
    MyClass.Free;
    MyClass.SleepOneSecond;

    同样也不会出错,但关闭程序时就出错了,如果是这样:

    MyClass := TMyClass.Create;
    MyClass.SleepOneSecond;
    MyClass.Free;
    MyThread := TMyThread.MyCreate(true);
    MyThread.OnTerminate := ThreadTerminate;
    MyThread.Resume;
    MyClass.SleepOneSecond;

    马上就出错。所以这个和线程类无线,应该是Delphi对于堆栈空间的释放规则,

    我想MyClass.Free之后,该对象在堆栈上空间还是保留 着,只是允许其他资源使用这个空间,

    所以接着调用下面这一句MyClass.SleepOneSecond就不会出错,当程序退出时可能对堆栈作一些清理 导致出错。而如果MyClass.Free之后即创建MyThread,大概MyClass的空间已经被MyThread使用,所以再调用 MyClass.SleepOneSecond就出错了。

    0 0
    原创粉丝点击