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

C# 如何在C中隐式停止对象中的线程#,c#,dispose,C#,Dispose,我有一个包含工作线程的对象。我想在对象超出范围时终止线程 using System.IO; using System; using System.Threading; namespace tt { class Program { static void Main() { AnotherClass a = new AnotherClass(); a.Say(); } }

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

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");
        }
    }
}

由于没有调用另一个类的析构函数,程序将挂起。我知道我可以调用a.Dispose()来终止线程。但是有没有一种方法可以在对象超出范围时隐式终止线程?

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

否,这是不可能的。没有引用计数可以注意到对象没有更多的引用,因此发生这种情况时无法执行任何操作

IDisposable
接口用于当实例不再使用时需要执行某些操作的类,调用
Dispose
方法是表示已完成实例操作的方式

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

static void Main()
{
  using (AnotherClass a = new AnotherClass())
  {
    a.Say();
  }
}
static void Main()
{
  AnotherClass a = new AnotherClass();
  try
  {
    a.Say();
  }
  finally
  {
    if (a != null)
    {
      ((Idisposable)a).Dispose();
    }
  }
}
using
块是
try…finally
块的语法糖:

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

除了Guffa的回答之外,我认为重要的是要注意,即使你有办法检测到你的
另一个类的
实例超出范围的精确时刻,在这种情况下,它永远不会。这就是为什么你注意到你的析构函数从未被调用


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

如果你的线程是为了服务一个对象而存在的,以利于其他线程,您应该有一个面向公共的包装器对象,所有对服务“感兴趣”的线程都将持有强引用,而服务本身将持有一个长的弱引用。该包装器对象不应包含服务需要读取或操作的任何信息,而应将所有信息请求转发给包装器和服务都包含强引用的对象

当服务器线程唤醒时,它应该定期检查对包装器对象的弱引用是否仍然有效。如果不是,服务器线程应该有序地关闭。请注意,服务器从不使用对包装器对象的强引用(即使是暂时的),因此一旦没有人对包装器对象感兴趣,包装器对象就会消失

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


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

耶,上帝和小鱼,我喜欢使用
成员调用
Dispose
没有任何问题,但是
使用
在代码中看起来非常优雅。因此,如果另一个类是库,则无法确保用户正确关闭/处置另一个类?@MonsterHunter如果实现了IDisposable,则存在问题。如果您无法访问另一个类中的逻辑,并且它没有正确处理,那么您可能会查看另一个库。@MonsterHunter:是的,没错。无法确保程序员正确使用该类。这实际上是一个很常见的问题。
IDisposable
模式有一个终结器方法(析构函数)作为充分利用这种情况的备用方法。垃圾收集器将在删除对象之前调用终结器,从而避免大量程序泄漏。我见过一些示例,其中代码运行多年而没有正确处理对象,然后在等待收集的对象数量过大时突然失败。那么怎么办我确保在不接触程序类中的代码的情况下,另一个类不会导致程序挂起?根据可用信息,使用Guffa建议的
IDisposable
模式是最好的方法。但是如果调用方不调用
Dispose(),那是行不通的。有一些使用终结器的解决方案,但是这些选项很少是一个好主意,并且可能导致其他更严重的问题。如果我处在你的地位,我会考虑改变我的课堂设计。但是不知道你的商业需求是什么,很难提出更好的设计方案。我认为隐式停止线程的唯一方法是断开电源。