我在C#中有一个对象,我需要定期执行一个方法。我希望这个方法只有在其他人使用我的对象时执行,一旦人们停止使用我的对象,我希望这个后台操作停止。
所以这是一个简单的例子(这是破碎的):
class Fish
{
public Fish()
{
Thread t = new Thread(new ThreadStart(BackgroundWork));
t.IsBackground = true;
t.Start();
}
public void BackgroundWork()
{
while(true)
{
this.Swim();
Thread.Sleep(1000);
}
}
public void Swim()
{
Console.WriteLine("The fish is Swimming");
}
}
问题在于,如果我在任何地方新建一个Fish对象,它永远不会被垃圾收集,因为有一个后台线程引用它。这是一个破损代码的插图版本。
public void DoStuff()
{
Fish f = new Fish();
}
// after existing from this method my Fish object keeps on swimming.
我知道Fish对象应该是一次性的,我应该在处理时清理线程,但是我无法控制我的调用者并且无法确保调用dispose。
如何解决此问题并确保即使未明确调用Dispose也会自动处理后台线程?
答案 0 :(得分:4)
以下是我提出的解决此问题的方法:
class Fish : IDisposable
{
class Swimmer
{
Thread t;
WeakReference fishRef;
public ManualResetEvent terminate = new ManualResetEvent(false);
public Swimmer(Fish3 fish)
{
this.fishRef = new WeakReference(fish);
t = new Thread(new ThreadStart(BackgroundWork));
t.IsBackground = true;
t.Start();
}
public void BackgroundWork()
{
bool done = false;
while(!done)
{
done = Swim();
if (!done)
{
done = terminate.WaitOne(1000, false);
}
}
}
// this is pulled out into a helper method to ensure
// the Fish object is referenced for the minimal amount of time
private bool Swim()
{
bool done;
Fish fish = Fish;
if (fish != null)
{
fish.Swim();
done = false;
}
else
{
done = true;
}
return done;
}
public Fish Fish
{
get { return fishRef.Target as Fish3; }
}
}
Swimmer swimmer;
public Fish()
{
swimmer = new Swimmer(this);
}
public void Swim()
{
Console.WriteLine("The third fish is Swimming");
}
volatile bool disposed = false;
public void Dispose()
{
if (!disposed)
{
swimmer.terminate.Set();
disposed = true;
GC.SuppressFinalize(this);
}
}
~Fish()
{
if(!disposed)
{
Dispose();
}
}
}
答案 1 :(得分:2)
我认为IDisposable解决方案是正确的。
如果您的类的用户不遵循使用实现IDisposable的类的指导原则,那就是他们的错 - 而且您可以确保文档明确提到应该如何使用该类。
另一个更麻烦的选项是“KeepAlive”DateTime字段,客户端调用的每个方法都会更新。然后,工作线程定期检查该字段,如果在一段时间内没有更新,则退出。当方法设置字段时,如果线程已退出,则线程将重新启动。
答案 2 :(得分:2)
我就是这样做的:
class Fish3 : IDisposable
{
Thread t;
private ManualResetEvent terminate = new ManualResetEvent(false);
private volatile int disposed = 0;
public Fish3()
{
t = new Thread(new ThreadStart(BackgroundWork));
t.IsBackground = true;
t.Start();
}
public void BackgroundWork()
{
while(!terminate.WaitOne(1000, false))
{
Swim();
}
}
public void Swim()
{
Console.WriteLine("The third fish is Swimming");
}
public void Dispose()
{
if(Interlocked.Exchange(ref disposed, 1) == 0)
{
terminate.Set();
t.Join();
GC.SuppressFinalize(this);
}
}
~Fish3()
{
if(Interlocked.Exchange(ref disposed, 1) == 0)
{
Dispose();
}
}
}