Автоматическое завершение несущественных потоков в C # - PullRequest
3 голосов
/ 10 декабря 2008

У меня есть объект в 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 не вызывается явно?

Ответы [ 3 ]

4 голосов
/ 25 августа 2009

Вот мое предлагаемое решение этой проблемы:

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();
        }
    }
}
2 голосов
/ 10 декабря 2008

Вот как бы я это сделал:

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();
        }
    }
}
2 голосов
/ 10 декабря 2008

Я думаю, IDisposable решение является правильным.

Если пользователи вашего класса не следуют рекомендациям по использованию классов, реализующих IDisposable, это их вина - и вы можете убедиться, что в документации явно указано, как должен использоваться класс.

Другой, гораздо более сложный вариант - поле DateTime «KeepAlive», которое будет обновлять каждый метод, вызываемый вашим клиентом. Затем рабочий поток периодически проверяет поле и закрывается, если оно не обновлялось в течение определенного периода времени. Когда метод устанавливает поле, поток будет перезапущен, если он вышел.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...