Thread.Interrupt для остановки долгого сна при выключении приложения - есть ли лучший подход - PullRequest
5 голосов
/ 17 июля 2009

У меня небольшой фоновый поток, который работает в течение всего времени жизни приложения - однако, когда приложение закрывается, поток должен корректно завершиться.

Проблема в том, что поток запускает некоторый код с интервалом в 15 минут - это означает, что он спит ALOT.

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

Вот суть моего кода (несколько псевдо):

public class BackgroundUpdater : IDisposable
{
    private Thread myThread;
    private const int intervalTime = 900000; // 15 minutes
    public void Dispose()
    {
        myThread.Interrupt();
    }

    public void Start()
    {
        myThread = new Thread(ThreadedWork);
        myThread.IsBackground = true; // To ensure against app waiting for thread to exit
        myThread.Priority = ThreadPriority.BelowNormal;
        myThread.Start();
    }

    private void ThreadedWork()
    {
        try
        {
            while (true)
            {
                Thread.Sleep(900000); // 15 minutes
                DoWork();
            }
        }
        catch (ThreadInterruptedException)
        {
        }
    }
}

Ответы [ 5 ]

14 голосов
/ 17 июля 2009

Существует абсолютно лучший способ - либо использовать Monitor.Wait / Pulse вместо Sleep / Interrupt, либо использовать Auto / ManualResetEvent. (Вы, вероятно, захотите ManualResetEvent в этом случае.)

Лично я фанат Wait / Pulse, вероятно, из-за того, что он похож на Java-механизм wait () / notify (). Тем не менее, определенно есть моменты, когда события сброса более полезны.

Ваш код будет выглядеть примерно так:

private readonly object padlock = new object();
private volatile bool stopping = false;

public void Stop() // Could make this Dispose if you want
{
    stopping = true;
    lock (padlock)
    {
        Monitor.Pulse(padlock);
    }
}

private void ThreadedWork()
{
    while (!stopping)
    {
        DoWork();
        lock (padlock)
        {
            Monitor.Wait(padlock, TimeSpan.FromMinutes(15));
        }
    }
}

Для получения дополнительной информации см. Мое учебное пособие по потокам , в частности страницы о взаимоблокировках, ожидании и пульсации , странице о ручках ожидания . Джо Албахари также имеет учебное пособие , которое охватывает те же темы и сравнивает их.

Я еще не смотрел в деталях, но я не удивлюсь, если бы у Parallel Extensions была некоторая функциональность, чтобы сделать это проще.

2 голосов
/ 17 июля 2009

Вы можете использовать событие, чтобы проверить, должен ли процесс завершиться следующим образом:

var eventX = new AutoResetEvent(false);
while (true)
{
    if(eventX.WaitOne(900000, false))
    {
        break;
    }
    DoWork();
}
1 голос
/ 14 октября 2013

В .NET 4 и более поздних версиях есть класс CancellationTokenSource, который немного упрощает эту задачу.

private readonly CancellationTokenSource cancellationTokenSource = 
    new CancellationTokenSource();

private void Run()
{
    while (!cancellationTokenSource.IsCancellationRequested)
    {
        DoWork();
        cancellationTokenSource.Token.WaitHandle.WaitOne(
            TimeSpan.FromMinutes(15));
    }
}

public void Stop()
{
    cancellationTokenSource.Cancel();
}

Не забывайте, что CancellationTokenSource одноразовый, поэтому убедитесь, что вы утилизируете его правильно.

0 голосов
/ 07 марта 2013

Мне очень нравится ответ Джона Скитса. Однако, это может быть немного легче для понимания и также должно работать:

public class BackgroundTask : IDisposable
{
    private readonly CancellationTokenSource cancellationTokenSource;
    private bool stop;

    public BackgroundTask()
    {
        this.cancellationTokenSource = new CancellationTokenSource();
        this.stop = false;
    }

    public void Stop()
    {
        this.stop = true;
        this.cancellationTokenSource.Cancel();
    }

    public void Dispose()
    {
        this.cancellationTokenSource.Dispose();
    }

    private void ThreadedWork(object state)
    {
        using (var syncHandle = new ManualResetEventSlim())
        {
            while (!this.stop)
            {
                syncHandle.Wait(TimeSpan.FromMinutes(15), this.cancellationTokenSource.Token);
                if (!this.cancellationTokenSource.IsCancellationRequested)
                {
                    // DoWork();
                }
            }
        }
    }
}

Или, включая ожидание фактической остановки фоновой задачи (в этом случае Dispose должен вызываться другим потоком, а не тем, на котором запущен фоновый поток, и, конечно, это не идеальный код, для него требуется рабочий поток, который фактически начался):

using System;
using System.Threading;

public class BackgroundTask : IDisposable
{
    private readonly ManualResetEventSlim threadedWorkEndSyncHandle;
    private readonly CancellationTokenSource cancellationTokenSource;
    private bool stop;

    public BackgroundTask()
    {
        this.threadedWorkEndSyncHandle = new ManualResetEventSlim();
        this.cancellationTokenSource = new CancellationTokenSource();
        this.stop = false;
    }

    public void Dispose()
    {
        this.stop = true;
        this.cancellationTokenSource.Cancel();
        this.threadedWorkEndSyncHandle.Wait();
        this.cancellationTokenSource.Dispose();
        this.threadedWorkEndSyncHandle.Dispose();
    }

    private void ThreadedWork(object state)
    {
        try
        {
            using (var syncHandle = new ManualResetEventSlim())
            {
                while (!this.stop)
                {
                    syncHandle.Wait(TimeSpan.FromMinutes(15), this.cancellationTokenSource.Token);
                    if (!this.cancellationTokenSource.IsCancellationRequested)
                    {
                        // DoWork();
                    }
                }
            }
        }
        finally
        {
            this.threadedWorkEndSyncHandle.Set();
        }
    }
}

Если вы видите какие-либо недостатки и недостатки по сравнению с решением Jon Skeets, я бы хотел их услышать, так как мне всегда нравится учиться ;-) Я предполагаю, что это медленнее и использует больше памяти и поэтому не должно использоваться в больших масштабах и в короткие сроки. Любой другой?

0 голосов
/ 17 июля 2009

Одним из методов может быть добавление события отмены или делегирование, на которое будет подписан поток. Когда вызывается событие отмены, поток может остановиться сам.

...