Thread.Abort() Is Evil.

         文章标题是看的国外的一篇文章中的小标题,我想不出更好的汉语标题来表达这篇文章的含义。安全

         首先,让咱们从介绍thread.Abort()开始。服务器

         MS对thread.Abort()给出的解释是:在调用此方法的线程上引起 ThreadAbortException,以开始终止此线程的过程。 调用此方法一般会终止线程。但其实并无这几句话那么简单。首先看一个实验,尝试终止主线程异步

static voidMain(string[] args)
        {
            try
            {
                Thread.CurrentThread.Abort();
            }
            catch
            {
                //Thread.ResetAbort();
                Console.WriteLine("主线程接受到被释放销毁的信号");
                Console.WriteLine( "主线程的状态:{0}",Thread.CurrentThread.ThreadState);
            }
            finally
            {
                Console.WriteLine("主线程最终被被释放销毁");
                Console.WriteLine("主线程的状态:{0}",Thread.CurrentThread.ThreadState);
                Console.ReadKey();
            }
}

运行结果:
abort示例1

函数

         从运行结果上看很容易看出当主线程被终止时其实报出了一个ThreadAbortException, 从中咱们能够进行捕获,可是注意的是,主线程直到finally语句块执行完毕以后才真正结束(能够仔细看下主线程的状态一直处于AbortRequest),真正销毁主线程主线程是在finally语句块中,在try{}catch{}中并不能完成对主线程的销毁。工具

         一样,咱们看一个类似的例子,销毁工做线程。oop

static voidTestAbort()
        {
            try
            {
                Thread.Sleep(10000);
            }
            catch
            {
                Console.WriteLine("线程{0}接受到被释放销毁的信号",Thread.CurrentThread.Name);
                Console.WriteLine("捕获到异常时线程{0}主线程的状态:{1}",Thread.CurrentThread.Name,Thread.CurrentThread.ThreadState);
            }
            finally
            {
                Console.WriteLine("进入finally语句块后线程{0}主线程的状态:{1}", Thread.CurrentThread.Name,Thread.CurrentThread.ThreadState);
            }
        }
 
Main:
static voidMain(string[] args)
        {
        
            Thread thread1 = new Thread(TestAbort);
            thread1.Name = "Thread1";
            thread1.Start();
            Thread.Sleep(1000);
            thread1.Abort();
            thread1.Join();
            Console.WriteLine("finally语句块后,线程{0}主线程的状态:{1}",thread1.Name, thread1.ThreadState);
            Console.ReadKey();
        }


运行结果:this

abort示例2

         这个例子验证了咱们上面得出的结论:真正销毁主线程主线程是在finally语句块中,在try{}catch{}中并不能完成对主线程的销毁。线程

         另外,若是对一个还没有启动的线程调用Abort的话,一旦该线程启动就会被中止。若是在已挂起的线程上调用 Abort,则将在调用 Abort 的线程中引起 ThreadStateException,并将 AbortRequested 添加到被停止的线程的ThreadState 属性中。直到调用 Resume 后,才在挂起的线程中引起 ThreadAbortException。若是在正在执行非托管代码的托管线程上调用 Abort,则直到线程返回到托管代码才引起 ThreadAbortException。设计

         这些看上去并无什么不足,但这里咱们要注意:ThreadAbortException是一个异步异常。code

         那么什么是异步异常呢?异步异常不是某条语句当即引起的,而是在语句执行后,在整个运行期间都有可能发生的。在异步操做中,函数发起操做但并不等待操做完成就返回成功的消息。异步操做更相似于一个行动的发起,只要该行动被发起,便表示发起行为得到成功。至于行动自己在后续执行中是否顺利,发起语句并不负责。显然,异步异常发生时,主线程不能肯定程序究竟执行到了何处,也便是异常可能发生在整个工做线程中代码的任何位置,并且也没法在主线程中捕获异常对象。这即是ThreadAbortException的邪恶之处,在销毁工做线程以前你没法判断工做线程的工做状态。或许你会说:“哦,这没什么,反正我都已经决定要销毁这个工做线程了”。若是你真这么认为,那么请继续看下面这种状况。

或许你已经对如下代码习觉得常:

using (FileStream fs= File.Open(myDataFile,
    FileMode.Open, FileAccess.ReadWrite,FileShare.None))
{
    ...do stuff with data file...
}


这种打开文件的方式确实使你的程序更加健壮与安全,它等价于如下代码:

FileStream fs =File.Open(myDataFile,
    FileMode.Open, FileAccess.ReadWrite,FileShare.None);
try
{
    ...do stuff with data file...
}
finally
{
    IDisposable disp = fs;
    disp.Dispose();
}

 

         不论你对文件的打开、操做过程如何,编译器最终都会在finally语句块中将文件流关闭,之因此这种方式使程序安全运行也是出于这一点,但异步异常的存在却打破了这一点。试想若是在工做线程即将开始执行finally语句块之初被主线程执行workthread.Abort(),ThreadAbortException发生在IDisposable disp=fs;或以前,那么工做线程便不能正常执行文件流fs的关闭,文件将会一直处于打开状态,这种状态可能一直维持到到你的程序彻底退出。不得不认可这种状况有可能发生。在这期间,其余程序若须要对这个文件执行打开和读写,就得一直等到你的程序彻底退出才行,若是你的程序是要一直运行几十天几个月的服务器程序的话,那这种状况显得更加糟糕。固然这个反例只是一种状况,异步异常使程序处于不受控制的状态,也便是你不知道会出现什么样的状况。

         正是异步异常的这种不肯定性,也正是Thread.Abort()总会致使ThreadAbortException,因此便印证了了这句话:Thread.Abort() Is Evil。

         那么又该怎么解决这个问题呢?或许你会应用另外一个方法,Thread.Interrupt(),在工做线程的安全点引起ThreadInterruptException,而后在这个异常的catch语句中使用Thread.CurrentThread.Abort()来销毁工做线程,可是我强烈建议你不要这么作。Thread.Interrupt()被设计的本意是中断目标线程(工做线程)的等待,继续它的运行,而不是为了让线程运行到安全点进行销毁而存在。何况你若是这么使用Thread.Interrupt(),岂不是又少了一个控制工做线程的工具。

         真正值得建议中止线程的方法是使用volatile bool变量,为你的工做线程设置一个状态量。当主线程发出让工做线程中止的信号时,就友好的中止工做线程。咱们且看MS给出的例子:

public class Worker
{
    // This method is called when the thread isstarted.
    public void DoWork()
    {
        while (!_shouldStop)
        {
            Console.WriteLine("Workerthread: working...");
        }
        Console.WriteLine("Worker thread:terminating gracefully.");
    }
    public void RequestStop()
    {
        _shouldStop = true;
    }
    // Keyword volatile is used as a hint tothe compiler that this data
    // member is accessed by multiple threads.
    private volatile bool _shouldStop;
}
 
public class WorkerThreadExample
{
    static void Main()
    {
        // Create the worker thread object.This does not start the thread.
        Worker workerObject = new Worker();
        Thread workerThread = newThread(workerObject.DoWork);
 
        // Start the worker thread.
        workerThread.Start();
        Console.WriteLine("Main thread:starting worker thread...");
 
        // Loop until the worker threadactivates.
        while (!workerThread.IsAlive) ;
 
        // Put the main thread to sleep for 1millisecond to
        // allow the worker thread to do somework.
        Thread.Sleep(1);
 
        // Request that the worker thread stopitself.
        workerObject.RequestStop();
 
        // Use the Thread.Join method to blockthe current thread
        // until the object's threadterminates.
        workerThread.Join();
        Console.WriteLine("Main thread:worker thread has terminated.");
    }
}

运行结果:

volatile示例

 

         受volatile方法的启发,你或许会写出如下的这种方法:

public class Worker
{
    readonly object stopLock = new object();
    bool stopping = false;
    bool stopped = false;
   
    public bool Stopping
    {
        get
        {
            lock (stopLock)
            {
                return stopping;
            }
        }
    }
   
    public bool Stopped
    {
        get
        {
            lock (stopLock)
            {
                return stopped;
            }
        }
    }
 
    public void Stop()
    {
        lock (stopLock)
        {
            stopping = true;
        }
    }
 
    void SetStopped()
    {
        lock (stopLock)
        {
            stopped = true;
        }
    }
 
    public void Run()
    {
        try
        {
            while (!Stopping)
            {
                //do your work
            }
        }
        finally
        {
            SetStopped();
        }
    }
}


我不知道为变量加锁的方法会不会对工做线程的运行效率带来怎样的影响,因此我很难说向你说明推荐这种方法或不推荐这种方法。但这确实是一种友好结束线程的方法之一。我真正推荐的方法除了使用volatile bool外,还有下面这种方法,运用事件通讯模型友好地中止线程。

    

ManualResetEvent _requestTermination = newManualResetEvent(false);
    ManualResetEvent _terminated = newManualResetEvent(false);
 
    public void Init()
    {
        new Thread(new ThreadStart(() =>
         {
 
             while (!_requestTermination.WaitOne(0))
             {
                 // do something usefull
 
             }
 
             _terminated.Set();
 
         })).Start();
    }
 
    public void Dispose()
    {
        _requestTermination.Set();
 
        // you could enter a maximum wait timein the WaitOne(...)
        _terminated.WaitOne();
 
    }


在Dispose()中,程序一直会等到工做线程正常结束。不得不说,事件通讯模型向来是处理棘手问题的能手。

That's all,转载请标明出处。

相关文章
相关标签/搜索