Закрытие многопоточного приложения

Я пытаюсь записать ThreadManager для своего приложения C#. Я создаю несколько потоков:
Один поток для моего класса для записи текста.
Один поток, который контролирует некоторую статистику.
Несколько потоков для выполнения большой последовательности вычислений (до 4 потоков на ядро и я запускаю свое приложение на 2x четырехъядерный сервер).

Мое приложение обычно работает в течение максимум 24 часов за один раз, таким образом, все потоки создаются в начале, и они сохраняют в течение всего времени выполнение приложения.

Я хочу иметь единственное место, где я "регистрирую" все свои шаги и когда приложение закрывается, я просто вызываю метод, и оно проходит все зарегистрированные потоки и завершает работу их.

С этой целью я создал следующий класс:

public class ThreadManager
{
    private static Object _sync = new Object();
    private static ThreadManager _instance = null;
    private static List<Thread> _threads;
    private ThreadManager()
    {
        _threads = new List<Thread>();
    }

    public static ThreadManager Instance
    {
        get
        {
            lock (_sync)
            {
                if (_instance == null)
                {
                    _instance = new ThreadManager();
                }
            }
            return _instance;
        }
    }

    public void AddThread(Thread t)
    {
        lock (_sync)
        {
            _threads.Add(t);
        }
    }

    public void Shutdown()
    {
        lock (_sync)
        {
            foreach (Thread t in _threads)
            {
                t.Abort(); // does this also abort threads that are currently blocking?
            }
        }
    }
}

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

8
задан Kiril 22 February 2010 в 12:44
поделиться

6 ответов

Если вы установите потоки как фоновые, они будут убиты при закрытии приложения.

myThread.IsBackground = true;

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

16
ответ дан 3 November 2019 в 12:49
поделиться

Что, если AddThread вызывается во время завершения работы?

Когда завершение работы завершается, поток, ожидающий в AddThread, добавит новый поток в коллекцию. Это могло привести к зависанию вашего приложения.

Для защиты от этого добавьте флаг bool, который вы когда-либо устанавливали только в Shutdown.

bool shouldGoAway = false;
public void AddThread(Thread t)
{
    lock (_sync)
    {
        if( ! shouldGoAway )
            _threads.Add(t);
    }
}

public void Shutdown()
{
    lock (_sync)
    {
        shouldGoAway = true;
        foreach (Thread t in _threads)
        {
            t.Abort(); // does this also abort threads that are currently blocking?
        }
    }

Также вам не следует использовать статические члены - для этого нет причин, поскольку у вас есть экземпляр Singleton.

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

3
ответ дан 3 November 2019 в 12:49
поделиться

Прерывание потоков - это то, что вы делаете , когда все остальное терпит неудачу . Это опасный поступок, к которому следует прибегать только в крайнем случае. Правильный способ сделать это - сделать логику потоковой передачи так, чтобы каждый рабочий поток реагировал быстро и правильно, когда основной поток дает ему команду на завершение работы.

По совпадению, это тема моего блога на этой неделе.

http://blogs.msdn.com/ericlippert/archive/2010/02/22/should-i-specify-a-timeout.aspx

14
ответ дан 3 November 2019 в 12:49
поделиться

Единственный конкретный вопрос, о котором я знаю, это этот: http://www.bluebytesoftware.com/blog/2007/01/30/MonitorEnterThreadAbortsAndOrphanedLocks.aspx

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

2
ответ дан 3 November 2019 в 12:49
поделиться

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

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

0
ответ дан 3 November 2019 в 12:49
поделиться

Что делать, если метод AddThread вызывается во время завершения работы?

После завершения завершения работы поток, ожидающий в AddThread, добавит новый поток в коллекцию. Это может привести к зависанию в приложении.

Добавьте флаг bool, который вы когда-либо устанавливали только в Shutdown, чтобы защитить от этого.

bool shouldGoAway = false;
public void AddThread(Thread t)
{
    lock (_sync)
    {
        if( ! shouldGoAway )
            _threads.Add(t);
    }
}

public void Shutdown()
{
    lock (_sync)
    {
        shouldGoAway = true;
        foreach (Thread t in _threads)
        {
            t.Abort(); // does this also abort threads that are currently blocking?
        }
    }

Также не следует использовать статические члены - для этого нет причин, поскольку у вас есть экземпляр Singleton.

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

-121--3438925-

Короткий ответ: да, следующий метод безопасен для потока:

public static int CalcuateSomething( int number ) {
  int i = number * 10;
  return i;
}

Все локальные переменные безопасны, если они не указывают на общий объект в куче.

Как и в случае приложений с одним потоком, следующие вызовы этого метода вернут различные значения.

CalculateSomething( 10 ) => 100
CalculateSomething( 20 ) => 200

Почему это так? Потому что каждый вызов номера метода принимает разные значения, и поэтому я тоже буду. Значение i не запоминается после завершения функции, так как i выделяется в стеке. Почти на всех языках функции моделируются в стеке. Каждый раз при вызове другого метода текущий метод приостанавливается. Новый метод вставляется в стек вызовов и вызывается. Когда этот метод завершен, программа отменяет вызов метода и возобновляет его в том месте, где он был остановлен (т.е. в returnAddress). Любая локальная переменная, определенная в этом методе, отделена от стекового кадра этого метода. Стэкфрейм для нашего метода можно было бы представить так:

public Class StackFrameForCalculateSomething implements StackFrame {
  public int ReturnAddress;
  public int i = 0;
  public int number;
}

Стек можно было бы рассматривать как совокупность объектов StackFrame.

Stack callStack

Каждый раз при вызове нового метода программа может выполнять следующее:

StackFrameForCalculcateSomething s = new StackFrameForCalculateSomething();
s.returnAddress = instructionPointer;
s.number = 10;
callStack.push( s );
s.invoke();

StackFrameForCalculcateSomething s2 = new StackFrameForCalculateSomething();
s2.returnAddress = instructionPointer;
s2.number = 20;
callStack.push( s2 );
s2.invoke();

Что это означает для многопоточности? В случае потоков у вас будет несколько независимых callStacks с их собственной коллекцией. Причина, по которой доступ к локальным переменным безопасен, заключается в том, что для callStack потока 1 нет пути получить доступ к callStack потока 2, поскольку они являются отдельными объектами. Так же, как в случае одиночной резьбы s и s2, это различные объекты с различными значениями числа. И поэтому они независимы друг от друга. Рассмотрим, что s было в потоке 1, а s2 является потоком 2. Потоки 1 и 2 не имеют общей памяти, поэтому они безопасны.

Потоки не используют общие кадры стека. Они делят кучу.

-121--3238833-

Если вас не волнует состояние рабочего потока, вы можете выполнить _thread и прервать:

void DieDieDie()
{
  foreach (Thread thread in _thread)
    {
      thread.Abort();
      thread.Join(); // if you need to wait for the thread to die
    }
}

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

1
ответ дан 3 November 2019 в 12:49
поделиться
Другие вопросы по тегам:

Похожие вопросы: