如何在 C# 中隐式停止对象中的线程

How to implicitly stop a thread in an object in C#

我有一个包含工作线程的对象。当对象超出范围时,我想终止线程。

using System.IO;
using System;
using System.Threading;

namespace tt {
    class Program
    {
        static void Main()
        {
            AnotherClass a = new AnotherClass();
            a.Say();
        }

    }

    class AnotherClass:IDisposable {
        private bool m_Disposed;
        private readonly AutoResetEvent m_ResetEvent = new AutoResetEvent(false);

        public AnotherClass() {
            Thread t = new Thread(wait);
            t.Start();
        }

        public void Dispose() {
            Dispose(true);
        }
        private void Dispose(bool disposing) {
            if (m_Disposed) {
                return;
            }

            if (disposing) {
                Console.WriteLine("inner disposing");
            }
            m_ResetEvent.Set();
            Console.WriteLine("Outer disposing");
            m_Disposed = true;
        }

        private void wait() {
            m_ResetEvent.WaitOne();
        }

        ~AnotherClass() {
            Dispose(false);
        }

        public void Say() {
            Console.WriteLine("HellO");
        }
    }
}

程序将挂在那里,因为没有调用 AnotherClass 的析构函数。我知道我可以调用 a.Dispose() 来终止线程。但是有没有办法在对象超出范围时隐式终止线程?

您可以使用异常处理并使用在 catch 块中显示为异常的错误

不,那不可能。没有引用计数可以注意到对象没有更多的引用,所以当这种情况发生时没有办法做任何事情。

IDisposable接口用于类当一个实例不再被使用时需要做一些事情,调用Dispose方法就是你发出信号的方式您已完成实例。

确保在离开范围时释放对象的常用方法是将代码包装在 using 块中:

static void Main()
{
  using (AnotherClass a = new AnotherClass())
  {
    a.Say();
  }
}

using 块是 try...finally 块的语法糖:

static void Main()
{
  AnotherClass a = new AnotherClass();
  try
  {
    a.Say();
  }
  finally
  {
    if (a != null)
    {
      ((Idisposable)a).Dispose();
    }
  }
}

添加到 Guffa 的回答中,我认为同样重要的是要注意 即使 你有办法检测到您的 AnotherClass 实例超出范围,在这种情况下,它永远不会。这就是为什么您注意到您的析构函数从未被调用过。

这样做的原因是,当您创建并启动工作线程时,该线程会传递一个指向实例方法 wait() 的委托引用,该方法带有对 this 的隐式引用( AnotherClass 实例)。因此,只要您的线程本身没有超出范围,它就会持有对 AnotherClass 实例的强引用并防止它被垃圾收集。

如果您的线程存在的目的是为其他线程的利益服务一个对象,您应该有一个面向 public 的包装器对象,该服务中的所有线程 "interested"将持有强引用,服务本身将持有长弱引用。该包装器对象不应保存服务需要读取或操作的任何信息,而应将所有信息请求转发给包装器和服务都持有强引用的对象。

当服务器线程被唤醒时,它应该定期检查对包装对象的弱引用是否仍然存在。如果不是,服务器线程应该以有序的方式关闭。请注意,服务器从不使用对包装对象的强引用——即使是暂时的——所以包装对象应该在没有人对它感兴趣时立即消失。

如果服务器线程可能会无限期地阻塞等待各种事情的发生,那么包装器应该在宇宙中的任何地方持有对一个可终结对象的唯一引用,该对象持有对服务器对象的强引用;当终结器触发时,它应该检查服务器对象持有的长弱引用是否仍然有效。如果不是,它应该尝试微调服务器线程(例如使用 Monitor.TryEnter/Monitor.Pulse。如果包装器对象仍然存在(终结器在某些复活场景中偶尔会错误触发)或者终结器无法要推动服务器线程,可终结对象应重新注册以进行终结。

使用终结器对象会使事情复杂化;如果服务器线程可以在不需要被推动的情况下周期性地唤醒自己,那将简化设计。但是,即使在涉及非自愿复活的情况下,如所述使用的终结器也应该相对健壮。