【问题标题】:C# Threading Patterns - is this a good idea?C# 线程模式 - 这是个好主意吗?
【发布时间】:2009-02-28 05:57:31
【问题描述】:

我今天在玩我的一个项目,发现了一个有趣的小sn-p,鉴于以下模式,您可以安全地清理线程,即使它被强制提前关闭。我的项目是一个网络服务器,它为每个客户端生成一个新线程。我发现这对于从远程端提早终止很有用,但也适用于本地端(我可以从我的处理代码中调用.Abort())。

您对此是否有任何问题,或者您对任何寻求类似方法的人有什么建议?

测试用例如下:


using System;
using System.Threading;

class Program
{
    static Thread t1 = new Thread(thread1);
    static Thread t2 = new Thread(thread2);

    public static void Main(string[] args)
    {
        t1.Start();
        t2.Start();
        t1.Join();
    }

    public static void thread1() {
        try {
            // Do our work here, for this test just look busy.
            while(true) {
                Thread.Sleep(100);
            }
        } finally {
            Console.WriteLine("We're exiting thread1 cleanly.\n");
            // Do any cleanup that might be needed here.
        }
    }

    public static void thread2() {
        Thread.Sleep(500);
        t1.Abort();
    }
}

作为参考,如果没有 try/finally 块,线程就会像预期的那样死掉。

【问题讨论】:

    标签: c# .net multithreading


    【解决方案1】:

    除非整个应用程序停止运行,否则中止另一个线程只是一个坏主意。让您的程序处于未知状态太容易了。中止自己的线程有时很有用——例如,如果你想提前结束响应,ASP.NET 会抛出ThreadAbortException——但这不是一个非常好的设计。

    线程的安全清理应该是相互的 - 应该有一些共享标志 requesting 线程关闭。线程应定期检查该标志并适当退出。

    【讨论】:

    • 线程代表网络连接。只有当远程端过早关闭连接,或者我们想关闭我们的端时,它才会被 Abort()。
    • 这样做的主要原因是因为当我有随机远程断开连接时,我似乎突然得到了 NullReferenceExceptions,这是通过这种方法修复的,并在终于阻塞了。
    • 你当然应该在 finally 块中正确清理你的连接,但你不应该使用 Thread.Abort。
    • 另外,我想我在你的回答中错过了这一点:与上面给出的示例相反,为了简洁起见,使用两个线程,我只是从中止线程本身。鉴于你所说的,我不知道这是否会改变你对我的情况的建议,但你有它。
    • @monoxide:好的,如果您要从中止线程本身,那么它还不错。我认为最好还是让它正常终止。为什么不能直接返回堆栈?
    【解决方案2】:

    不幸的是,无法从一般代码示例中辨别这是否会“安全地”清理线程。它高度依赖于线程内执行的实际代码。您必须考虑多个问题。每个都代表代码中的一个潜在错误。

    1. 如果线程当前在本地代码中,它不会立即执行 Thread.Abort 调用。它将在本机代码中完成它想做的所有工作,并且在代码返回托管之前不会抛出。在这种情况发生之前,thread2 将挂起。
    2. 在 finally 块中未释放的任何本机资源都将在这种情况下泄漏。所有本机资源都应在 finally 块中释放,但并非所有代码都这样做,这是一个需要考虑的问题。
    3. 任何未在 finally 块中释放的锁都将保持锁定状态,并可能导致将来出现死锁。

    目前还有其他一些问题在我脑海中闪过。但希望这将为您的申请提供一些指导。

    【讨论】:

    • 如何手动释放锁?我只见过 lock(foo) {} 块,如果它们通过异常或类似的方式退出,它们是否仍然解锁?使用(foo) {} 块也是如此。
    • @monoxide,在底层 lock() 只是一个 try/finally 围绕 Monitor.Enter 和 Monitor.Exit。在某些情况下,人们会在 try/finally 之外有效地调用它们。很少见,但确实会发生
    • 对于上下文 - 使用 Monitor.Exit 的最大原因是与 Monitor.TryEnter 结合使用 - 即在获取锁时使用超时。这没有缩写语法。
    • @Marc 我见过的唯一其他有效用途是当一个对象纯粹为了持有锁而存在时。通常,尽管它们在 Dispose 方法中实现 IDisposable 并退出锁。虽然是可抛弃的,但他们最终应该尝试一下......
    【解决方案3】:

    中止线程通常不是一个好主意。您可以做的是轮询可以从其他线程设置的 stopRequested 标志。下面是一个示例 WorkerThread 类供您参考。更多使用方法请参考http://devpinoy.org/blogs/jakelite/archive/2008/12/20/threading-patterns-the-worker-thread-pattern.aspx

    public abstract class WorkerThreadBase : IDisposable
    {
        private Thread _workerThread;
        protected internal ManualResetEvent _stopping;
        protected internal ManualResetEvent _stopped;
        private bool _disposed;
        private bool _disposing;
        private string _name;
    
        protected WorkerThreadBase()
            : this(null, ThreadPriority.Normal)
        {
        }
    
        protected WorkerThreadBase(string name)
            : this(name, ThreadPriority.Normal)
        {
        }
    
        protected WorkerThreadBase(string name,
            ThreadPriority priority)
            : this(name, priority, false)
        {
        }
    
        protected WorkerThreadBase(string name,
            ThreadPriority priority,
            bool isBackground)
        {
            _disposing = false;
            _disposed = false;
            _stopping = new ManualResetEvent(false);
            _stopped = new ManualResetEvent(false);
    
            _name = name == null ? GetType().Name : name; ;
            _workerThread = new Thread(threadProc);
            _workerThread.Name = _name;
            _workerThread.Priority = priority;
            _workerThread.IsBackground = isBackground;
        }
    
        protected bool StopRequested
        {
            get { return _stopping.WaitOne(1, true); }
        }
    
        protected bool Disposing
        {
            get { return _disposing; }
        }
    
        protected bool Disposed
        {
            get { return _disposed; }
        }
    
        public string Name
        {
            get { return _name; }            
        }   
    
        public void Start()
        {
            ThrowIfDisposedOrDisposing();
            _workerThread.Start();
        }
    
        public void Stop()
        {
            ThrowIfDisposedOrDisposing();
            _stopping.Set();
            _stopped.WaitOne();
        }
    
        public void WaitForExit()
        {
            ThrowIfDisposedOrDisposing();            
            _stopped.WaitOne();
        }
    
        #region IDisposable Members
    
        public void Dispose()
        {
            dispose(true);
        }
    
        #endregion
    
        public static void WaitAll(params WorkerThreadBase[] threads)
        { 
            WaitHandle.WaitAll(
                Array.ConvertAll<WorkerThreadBase, WaitHandle>(
                    threads,
                    delegate(WorkerThreadBase workerThread)
                    { return workerThread._stopped; }));
        }
    
        public static void WaitAny(params WorkerThreadBase[] threads)
        {
            WaitHandle.WaitAny(
                Array.ConvertAll<WorkerThreadBase, WaitHandle>(
                    threads,
                    delegate(WorkerThreadBase workerThread)
                    { return workerThread._stopped; }));
        }
    
        protected virtual void Dispose(bool disposing)
        {
            //stop the thread;
            Stop();
    
            //make sure the thread joins the main thread
            _workerThread.Join(1000);
    
            //dispose of the waithandles
            DisposeWaitHandle(_stopping);
            DisposeWaitHandle(_stopped);
        }
    
        protected void ThrowIfDisposedOrDisposing()
        {
            if (_disposing)
            {
                throw new InvalidOperationException(
                    Properties.Resources.ERROR_OBJECT_DISPOSING);
            }
    
            if (_disposed)
            {
                throw new ObjectDisposedException(
                    GetType().Name,
                    Properties.Resources.ERROR_OBJECT_DISPOSED);
            }
        }
    
        protected void DisposeWaitHandle(WaitHandle waitHandle)
        {
            if (waitHandle != null)
            {
                waitHandle.Close();
                waitHandle = null;
            }
        }
    
        protected abstract void Work();
    
        private void dispose(bool disposing)
        {
            //do nothing if disposed more than once
            if (_disposed)
            {
                return;
            }
    
            if (disposing)
            {
                _disposing = disposing;
    
                Dispose(disposing);
    
                _disposing = false;
                //mark as disposed
                _disposed = true;
            }
        }
    
        private void threadProc()
        {
            Work();
            _stopped.Set();
        }        
    }
    

    【讨论】:

      猜你喜欢
      • 2012-03-28
      • 1970-01-01
      • 1970-01-01
      • 2010-09-08
      • 1970-01-01
      • 2013-04-02
      • 1970-01-01
      • 2011-08-14
      • 1970-01-01
      相关资源
      最近更新 更多