Фоновые рекомендации по управлению временем жизни потоков потребителей - PullRequest
2 голосов
/ 24 августа 2010

У меня есть библиотека классов C #, которая запускает фоновый потребительский поток (лениво), который прослушивает выполнение задач из очереди производителя / потребителя.Эта библиотека классов может использоваться из любого типа приложения .NET и в настоящее время используется на веб-сайте ASP.NET MVC.Поток потребителя блокируется большую часть времени, пока запрос не поступит в очередь.Для каждого данного домена приложения должен быть только один потребительский поток.

Я хочу иметь возможность корректно завершить работу потребительского потока при выходе из приложения независимо от того, какое приложение оно представляет, например Windows Forms, Consoleили приложение WPF.Сам код приложения должен игнорировать тот факт, что этот поток задерживается в ожидании завершения.

Как я могу решить эту проблему времени жизни потока с точки зрения библиотеки классов?Есть ли какое-нибудь глобальное событие закрытия приложения, в которое я могу ввязаться, чтобы потом аккуратно разорвать поток?

Прямо сейчас, поскольку оно находится в домене приложений ASP.NET MVC, это действительно не имеет значения, так как они никогда не порвутсявсе равно изящно внизНо теперь, когда я начинаю использовать его в запланированных задачах консольного приложения, предназначенных для завершения, я хочу решить эту проблему раз и навсегда.Консольное приложение не завершает свою работу, поскольку потребительский поток все еще активен и заблокирован в ожидании запросов.Я выставил открытое статическое свойство Thread в классе для вызова Abort() при выходе из консольного приложения, но это, откровенно говоря, отвратительно.

Любые указатели приветствуются!Опять же, я не хочу писать Windows Forms или WPF или специальный консольный код для решения этой проблемы.Лучшее общее решение, которое может использовать каждый потребитель библиотеки классов, было бы лучше.

Ответы [ 3 ]

2 голосов
/ 25 августа 2010

Это немного сложно.Вы на правильном пути, чтобы избежать прерывания, поскольку это может привести к прерыванию потока в середине важной операции (запись в файл и т. Д.).Установка свойства IsBackground будет иметь тот же эффект.

Что вам нужно сделать, это сделать вашу очередь блокировки отменяемой.Жаль, что вы еще не используете .NET 4.0, в противном случае вы могли бы использовать класс BlockingCollection.Не беспокойся, хотя.Вам просто нужно изменить свою пользовательскую очередь, чтобы она периодически опрашивала сигнал остановки.Вот быстрая и грязная реализация, которую я только что описал, используя каноническую реализацию очереди блокировки в качестве отправной точки.

public class CancellableBlockingCollection<T>
{
    private Queue<T> m_Queue = new Queue<T>();

    public T Take(WaitHandle cancel)
    {
        lock (m_Queue)
        {
            while (m_Queue.Count <= 0)
            {
                if (!Monitor.Wait(m_Queue, 1000))
                {
                    if (cancel.WaitOne(0))
                    {
                        throw new ThreadInterruptedException();
                    }
                }
            }
            return m_Queue.Dequeue();
        }
    }

    public void Add(T data)
    {
        lock (m_Queue)
        {
            m_Queue.Enqueue(data);
            Monitor.Pulse(m_Queue);
        }
    }
}

Обратите внимание, как метод Take принимает WaitHandle, который можно проверитьдля сигнала отмены.Это может быть тот же дескриптор ожидания, который используется в других частях вашего кода.Идея состоит в том, что дескриптор ожидания опрашивается на определенном интервале внутри Take, и если он сигнализируется, то весь метод выбрасывает.Предполагается, что бросать внутрь Take можно, поскольку это безопасная точка, потому что потребитель не мог быть в середине важной операции.Вы можете просто позволить потоку распадаться на исключение в этой точке.Вот как может выглядеть ваш потребительский поток.

ManualResetEvent m_Cancel = new ManualResetEvent(false);
CancellableBlockingCollection<object> m_Queue = new CancellableBlockingCollection<object>();

private void ConsumerThread()
{
  while (true)
  {
    object item = m_Queue.Take(m_Cancel); // This will throw if the event is signalled.
  }
}

Существуют и другие способы отменить метод Take.Я решил использовать WaitHandle, но это легко сделать, например, с помощью простого флага bool.

Обновление:

Как указал Стивен вкомментарии Thread.Interrupt делают это по существу уже.Это приведет к тому, что поток сгенерирует исключение при блокирующем вызове ... в значительной степени то, что я делал в этом примере, но с гораздо большим количеством кода.Единственное предостережение в отношении Thread.Interrupt заключается в том, что он работает только во время стандартных вызовов блокировки в .NET BCL (например, WaitOne и т. Д.).Таким образом, вы не сможете отменить длительные вычисления в процессе, как если бы вы использовали более ручной подход, как в этом ответе.Это, безусловно, отличный инструмент, чтобы держать его в заднем кармане и может быть просто полезен в вашем конкретном сценарии.

2 голосов
/ 24 августа 2010

Установите для свойства потока IsBackground значение true.Тогда это не помешает завершению процесса.

http://msdn.microsoft.com/en-us/library/system.threading.thread.isbackground.aspx

Альтернативно, вместо использования Abort используйте Interrupt.Гораздо менее отвратительно.

1 голос
/ 24 августа 2010

Если вы используете .net 4.0, a CancelationToken может использоваться для оповещения нити о том, что пора грациозно завершить работу. Это очень полезно, так как большинство команд ожидания позволяют передать ему токен, и если поток находится в ожидании, когда поток отменяется, он автоматически выйдет из ожидания.

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