Thread.Interrupt, чтобы остановить длительный сон при завершении работы приложения – есть ли лучший подход

StackOverflow https://stackoverflow.com/questions/1141617

  •  16-09-2019
  •  | 
  •  

Вопрос

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

Проблема в том, что поток выполняет некоторый код с интервалом в 15 минут, а это значит, что он МНОГО спит.

Теперь, чтобы вывести его из спящего режима, я бросаю ему прерывание - однако у меня вопрос, есть ли лучший подход к этому, поскольку прерывания генерируют 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)
        {
        }
    }
}
Это было полезно?

Решение

Есть абсолютно лучший способ - либо использовать Monitor.Wait/Пульс вместо 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 также будет какая-то функциональность, упрощающая эту задачу.

Другие советы

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

var eventX = new AutoResetEvent(false);
while (true)
{
    if(eventX.WaitOne(900000, false))
    {
        break;
    }
    DoWork();
}

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

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 является одноразовым, поэтому убедитесь, что вы утилизируете его правильно.

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top