delphi 如何在自动终止线程和手动终止线程时都正确的设置窗体上的控件

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了delphi 如何在自动终止线程和手动终止线程时都正确的设置窗体上的控件相关的知识,希望对你有一定的参考价值。

delphi中释放线程有两种方法,自动释放和手动释放。

1、自动释放:将FreeOnTerminate属性设置为True,Execute方法执行完后会自动执行析构函数。
2、手动释放:
我是这样处理的:
thread.Terminate;
thread.WaitFor;
thread.Free;
释放前必须调用WaitFor方法等待线程执行完后,否则Free的时候可能因为线程还未执行完报错。

我的界面上有两个按钮,启动和停止,点击启动按钮启动线程,启动按钮置灰,停止按钮可用。当线程执行完后需要将启动按钮置为可用,停止按钮置灰。

启动按钮.Enabled := True;
停止按钮.Enabled := False;
这两句我是在线程的Destroy方法是执行的。

如果FreeOnTerminate设置为True,线程自动释放,线程执行完后没有问题。

但我在界面上点击停止按钮的时候,需要手动去终止并释放线程,必须调用WaitFor等线程执行完后才能释放,而这个方法不允许FreeOnTerminate属性设置为True。

我该如何处理线程,使之自己执行完或手动终止它都能正确的设置两个按钮的Enabled状态呢?

同事给我的建议是,FreeOnTerminate属性设置为True,执行完后自动释放。点击停止时给一个自定义属性Stop设置为True(在Execute方法中判断如果Stop为True,则Exit),而

不调用Terminate方法。这样当Execute执行时判断Stop属性为True,结束执行方法,自动释放。基本上可以解决我的问题。但是如果我关闭窗体,这时也需要将Stop设置为True终

止线程,但将该属性设置为True,线程可能不会马上执行完(因为线程执行调用的方法是一个递归方法),这个时候线程未执行完而窗体释放会提示地址错误。

各位有什么更好的办法吗?让一个线程不管是自动终止还是手动终止都能正确的设置这两个按钮的状态。

在Delphi中使用线程,当窗体关闭时,如果窗体中启用了线程,一般需要手动关闭,以释放资源。 常用来结束线程的代码为:
thread.Terminate;
thread.WaitFor;
即先触发Terminate方法,然后等待线程的结束。这种方法要求线程不能使用
FreeOnTerminate := True;
否则在WaitFor即将结束的时候会引发“无效句柄”的错误。 这种方法在窗体关闭的时候会等待一段事件(因为WaitFor)。
因此,如果不是在主窗体中结束线程时,其实我们可以不必使用WaitFor。而是采用如下方法: 将FreeOnTerminate := True;这样在窗体关闭的代码中直接调用
thread.Terminate; 即可。
注意: 如果设置了 thread.OnTerminate := SomeFunction; 那么在调用PcmThrd.Terminate;前尽量将thread.OnTerminate := nil,以免结束线程后SomeFunction中的变量出现空指针错误。当然,这不是绝对的,需要根据具体程序而定 .追问

确实,线程不是在主窗体上定义的,是在MDI子窗体上定义的,按照你的做法,我在窗体的析构函数(不是Close事件中)中执行thread.Terminate方法,仍报内存地址错误。
运行期在创建的对象按理说应该手动释放的,窗体释放了,那么窗体上的对象也应该释放了,为什么还会报内存地址错误呢?

追答

那只能看你的源代码了.需要分析一下.才知道.

参考技术A 手动时,FreeOnTerminate 也设置为true
启动按钮.Enabled := True;
停止按钮.Enabled := False;
这2句不放在destroy里,放在execute方法的最后面执行,应该可以.追问

如果设置了FreeOnTerminate为True,execute执行完后会自动执行析构函数,放在execute最后和Destroy里不是一样吗?如果使用者不点击停止,而是直接关闭窗体,这时我也要在窗体的关闭事件中执行thread.Terminate方法,但执行这个方法线程可能不会马上就停止,而这时窗体却执行了释放,也就是窗体释放时,线程还未执行完,不就会有内存泄露了吗?

追答

那你在线程里判读下,窗体是否已经不存在了nil.不存在就不要再刷新窗体了.

参考技术B 感觉是你的线程没写好,不然的话不会出现这种情况。

多线程编程中的EventWaitHandler

如果事件初始为终止状态,首次waitone()时候不进行线程阻塞,为非终止状态时候,首次waitone()时候进行线程阻塞。当然,该状态也要结合一下的EventResetMode的值进行结合使用。

 EventResetMode.AutoReset 自动重置,也就是自动重置事件状态,自动的话,他会在首次waitone之后立即改变事件为非终止状态。就像这样{如果手动重置事件的初始化为终止状态,则首次waitone()不进行线程阻塞,但是会立即改变事件状态的状态为非终止状态,当以后waitone()的时候,都会阻塞线程}

EventResetMode.ManualReset 手动重置,它对于线程的阻塞决定于上一个参数是否为终止状态,也就是说,如果初始值设置为终止状态,则它的线程如果不手动进行手动设置,则一直不进行线程阻塞,如果初始值是非终止状态,则每个waione()都会阻塞,除非手动使用set()之类的方法设置为终止状态为止。

 

using System;
using System.Threading;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EventWaitHandleDemo
{
    class Program
    {
        static EventWaitHandle eHandle;

        static void UnblockDemo()
        {
            Console.WriteLine("测试EventWaitHandle的初始终止状态");
            eHandle = new EventWaitHandle(true, EventResetMode.AutoReset);//eHandle初始为终止状态,模式为AutoReset
            eHandle.WaitOne();//由于EventWaitHandle对象eHandle初始状态为终止状态,所以这里第一次调用WaitOne时阻塞被立即释放,又由于eHandle为AutoReset模式,所以之后eHandle会被置为非终止状态
            Console.WriteLine("线程未被阻塞");
            eHandle.WaitOne();//由于此时eHandle已经为非终止状态,所以此时调用WaitOne线程会被阻塞
            Console.WriteLine("线程被阻塞");
        }

        static void BlockDemo()
        {
            Console.WriteLine("测试EventWaitHandle的初始非终止状态");
            eHandle = new EventWaitHandle(false, EventResetMode.AutoReset);//eHandle初始为非终止状态,模式为AutoReset
            eHandle.WaitOne();//由于EventWaitHandle对象eHandle初始状态为非终止状态,所以这里第一次调用WaitOne时,线程就被组塞了
            Console.WriteLine("线程被阻塞");
        }

        static void AutoResetDemo()
        {
            Console.WriteLine("测试EventWaitHandle的AutoReset模式");
            eHandle = new EventWaitHandle(false, EventResetMode.AutoReset);//eHandle初始为非终止状态,模式为AutoReset
            ThreadPool.QueueUserWorkItem(new WaitCallback((object o) =>
            {
                //启动另一个线程,每隔3秒钟调用一次eHandle.Set方法,为主线程释放一次阻塞,一共释放3次
                for (int i = 0; i < 3; i++)
                {
                    Thread.Sleep(3000);
                    eHandle.Set();//由于eHandle处于AutoReset模式,所以每次使用Set将eHandle置为终止状态后,待被WaitOne阻塞的线程被释放后,eHandle又会被自动置回非终止状态
                }
            }), null);

            eHandle.WaitOne();//线程第一次被WaitOne阻塞
            Console.WriteLine("第一次WaitOne调用阻塞已被释放,3秒后第二次WaitOne调用的阻塞会被释放");
            eHandle.WaitOne();//线程第二次被WaitOne阻塞
            Console.WriteLine("第二次WaitOne调用阻塞已被释放,3秒后第三次WaitOne调用的阻塞会被释放");
            eHandle.WaitOne();//线程第三次被WaitOne阻塞
            Console.WriteLine("第三次WaitOne调用阻塞已被释放,所有WaitOne调用的阻塞都已被释放");
        }

        static void ManualResetDemo()
        {
            Console.WriteLine("测试EventWaitHandle的ManualReset模式");
            eHandle = new EventWaitHandle(false, EventResetMode.ManualReset);//eHandle初始为非终止状态,模式为ManualReset
            ThreadPool.QueueUserWorkItem(new WaitCallback((object o) =>
            {
                //启动另一线程,3秒后调用一次eHandle.Set方法,为主线程释放WaitOne阻塞
                Thread.Sleep(3000);
                eHandle.Set();//由于eHandle处于ManualReset模式,所以一旦使用Set将eHandle置为终止状态后,在eHandle的Reset被调用前eHandle会一直处于终止状态,在eHandle调用Reset前,所有被WaitOne阻塞的线程会立即得到释放
            }), null);

            eHandle.WaitOne();//线程第一次被WaitOne阻塞
            Console.WriteLine("第一次WaitOne调用阻塞已被释放,第二次WaitOne调用的阻塞会被立即释放");
            eHandle.WaitOne();//线程第二次被WaitOne阻塞
            Console.WriteLine("第二次WaitOne调用阻塞已被释放,第三次WaitOne调用的阻塞会被立即释放");
            eHandle.WaitOne();//线程第三次被WaitOne阻塞
            Console.WriteLine("第三次WaitOne调用阻塞已被释放,所有WaitOne调用的阻塞都已被释放");

            eHandle.Reset();//调用eHandle的Reset方法,将eHandle手动置回非终止状态,之后再调用WaitOne方法就会被阻塞了
            eHandle.WaitOne();//线程第四次被WaitOne阻塞
            Console.WriteLine("第四次WaitOne调用阻塞已被释放");
        }

        static void Main(string[] args)
        {
            Console.Write("你想测试哪一个方法1=UnblockDemo,2=BlockDemo,3=AutoResetDemo,4=ManualResetDemo:");
            switch (Console.ReadLine())
            {
                case "1":
                    UnblockDemo();
                    break;
                case "2":
                    BlockDemo();
                    break;
                case "3":
                    AutoResetDemo();
                    break;
                case "4":
                    ManualResetDemo();
                    break;
                default:
                    break;
            }
        }
    }
}

 

以上是关于delphi 如何在自动终止线程和手动终止线程时都正确的设置窗体上的控件的主要内容,如果未能解决你的问题,请参考以下文章

delphi 在线程A中终止线程B

多线程 Delphi 7 App - 应用程序终止问题

使用 boost 线程:发出信号并等待终止

4.如何终止线程

如何优雅地终止一个线程?

多线程编程中的EventWaitHandler