Правильный способ иметь бесконечную рабочую нить?

У меня есть объект, который требует много инициализации (1-2 секунды на мускулистой машине). Хотя после его инициализации требуется всего около 20 миллисекунд, чтобы выполнить типичную "работу"

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

Вот что я до сих пор, любая критика приветствуется

    private void DoWork()
    {
        while (true)
        {
            if (JobQue.Count > 0)
            {
                // do work on JobQue.Dequeue()
            }
            else
            {
                System.Threading.Thread.Sleep(50);
            }
        }
    }

После размышления: я думал, что мне, возможно, придется убить эту нить изящно, чтобы позволить ей работать вечно, поэтому я думаю, что добавлю тип Job, который сообщает, что поток заканчивается. Любые мысли о том, как закончить такой поток, тоже оценили.

Ответ 1

В любом случае вам нужно до lock, поэтому вы можете Wait и Pulse:

while(true) {
    SomeType item;
    lock(queue) {
        while(queue.Count == 0) {
            Monitor.Wait(queue); // releases lock, waits for a Pulse,
                                 // and re-acquires the lock
        }
        item = queue.Dequeue(); // we have the lock, and there data
    }
    // process item **outside** of the lock
}

с добавлением:

lock(queue) {
    queue.Enqueue(item);
    // if the queue was empty, the worker may be waiting - wake it up
    if(queue.Count == 1) { Monitor.PulseAll(queue); }
}

Вы также можете посмотреть этот вопрос, который ограничивает размер очереди (блокировка, если она слишком заполнена).

Ответ 2

Вам нужен примитив синхронизации, например WaitHandle (посмотрите на статические методы). Таким образом, вы можете "сигнализировать" рабочий поток, что есть работа. Он проверяет очередь и продолжает работать до тех пор, пока очередь не будет пуста, и в это время она ожидает, что мьютекс снова сигнализирует об этом.

Сделать одним из элементов задания также команду quit, чтобы вы могли сигнализировать рабочий поток, когда он время для выхода из потока

Ответ 3

В большинстве случаев я делал это очень похоже на то, как вы настроили, но не на том же языке. У меня было преимущество работы с структурой данных (в Python), которая блокирует поток до тех пор, пока элемент не будет помещен в очередь, что отрицает необходимость вызова сна.

Если .NET предоставляет такой класс, я бы изучил его. Блокировка потока намного лучше, чем поток, вращающийся при вызовах сна.

Задание, которое вы можете передать, может быть таким же простым, как "null"; если код получает нуль, он знает, что пора вырваться из него и вернуться домой.

Ответ 4

Возьмите Parallel Framework. Он имеет BlockingCollection <T> , который вы можете использовать в качестве очереди заданий. Как вы его используете:

  • Создайте BlockingCollection <T> , который будет содержать ваши задачи/задания.
  • Создайте несколько потоков, которые имеют бесконечный цикл (while (true) {//получаем задание от очереди)
  • Установить потоки, идущие
  • Добавить задания в коллекцию, когда они появятся.

Потоки будут заблокированы до тех пор, пока в коллекции не появится элемент. Тот, кто его перевернет, получит его (зависит от процессора). Я использую это сейчас, и он отлично работает.

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

Ответ 5

Если вам действительно не нужно отключать поток (и просто хотите, чтобы он не поддерживал ваше приложение), вы можете установить Thread.IsBackground до true, и это закончится, когда закончится все нефонические потоки. У Уилла и Марка есть хорошие решения для обработки очереди.

Ответ 6

Я реализовал очередь фоновых задач без использования каких-либо циклов while или пульсирования, или ожидания, или, действительно, касания объектов Thread вообще. И это работает. (Под этим я подразумеваю, что это было в производственных средах, обрабатывающих тысячи задач в день в течение последних 18 месяцев без какого-либо неожиданного поведения.) Это класс с двумя значимыми свойствами: Queue<Task> и a BackgroundWorker. Существует три важных метода, сокращенных здесь:

private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
   if (TaskQueue.Count > 0)
   {
      TaskQueue[0].Execute();
   }
}

private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    Task t = TaskQueue[0];

    lock (TaskQueue)
    {
        TaskQueue.Remove(t);
    }
    if (TaskQueue.Count > 0 && !BackgroundWorker.IsBusy)
    {
        BackgroundWorker.RunWorkerAsync();
    }
}

public void Enqueue(Task t)
{
   lock (TaskQueue)
   {
      TaskQueue.Add(t);
   }
   if (!BackgroundWorker.IsBusy)
   {
      BackgroundWorker.RunWorkerAsync();
   }
}

Это не то, что там нет ожидания и пульсирования. Но все происходит внутри BackgroundWorker. Это просто просыпается всякий раз, когда задача отбрасывается в очередь, выполняется до тех пор, пока очередь не будет пуста, а затем снова заснет.

Я далек от эксперта по нарезке. Есть ли причина, связанная с System.Threading для такой проблемы, если использовать BackgroundWorker?