WedX - журнал о программировании и компьютерных науках

Неожиданное состояние блока с Control.Invoke

Следующий код является упрощенным доказательством концепции. В приложении Windows Forms есть обработчик событий FormClosing для выполнения некоторой очистки. Приложение запускает несколько потоков, которые пишут в форму с помощью Control.Invoke. Я знаю, что мог бы использовать Control.BeginInvoke, но я хотел бы лучше понять, что происходит, и найти другое решение.

List<Thread> activeThreadlist;
volatile bool goOnPolling = true;
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        goOnPolling = false;
        Thread.Sleep(1000);
        foreach (Thread element in activeThreadlist)
            if (element.IsAlive)
                element.Abort();

    }

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

Вот код, выполняемый другими потоками:

while (goOnPolling)
{
    //some long elaboration here
    if (!goOnPolling) continue;
    aControl.Invoke(new Action(() =>
    {
        //display some result about the elaboration
    }));
    if (!goOnPolling) continue;
    //some long elaboration here    

}

Примерно в 50% случаев, когда форма закрывается, потоки блокируются в Control.Invoke, поэтому они не завершаются во время сна и вызывается Abort. Я думал, что проверки флага goOnPolling перед вызовом Invoke будет достаточно в 99,999% случаев, но я ошибался. Как указано в коде, потоки выполняют длительные обработки (не менее 100 мс), поэтому я ожидал, что goOnPolling может измениться во время них. Почему я ошибаюсь? Есть ли другое простое решение без повторения BeginInvoke, которое создает дополнительный поток?

Обновлять

Прочитав комментарии, я понял, что название было неправильным. Сначала я написал deadlock, но это всего лишь неожиданное (для меня) состояние блокировки. Вначале я не мог понять, почему мои потоки все еще живы после ожидания их завершения в течение 1000 мс, поэтому я поставил Abort, чтобы выяснить это. Я совершенно неправильно понял разницу между Invoke и BeginInvoke, спасибо за пояснение.

@Джон Б

Я согласен, что break подходит лучше, чем continue, но этот код находится внутри блока while (goOnPolling), поэтому я мог сэкономить только несколько циклов процессора, ничего более.

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


  • BeginInvoke не должен создавать дополнительные потоки, он просто ставит в очередь работу над существующим потоком пользовательского интерфейса. Создание нового потока полностью противоречит тому, для чего предназначен BeginInvoke. 06.07.2017
  • Вы уверены в своем последнем предложении? Насколько я знаю, и Invoke, и BeginInvoke выполняются в потоке пользовательского интерфейса, только Invoke блокируется перед возвратом. то есть Invoke = BeginInvoke + дождаться выполнения. Вас может спутать с delegate Invoke/BeginInvoke, см. stackoverflow.com/questions/229554/ . Единственная причина использовать Control.Invoke — это когда вам нужно завершить вызванное действие, прежде чем продолжить работу с вызывающим потоком. 06.07.2017
  • Я на 100% положительный. Invoke ставит в очередь выполнение в цикле обработки сообщений, а затем ждет его завершения. BeginInvoke делает то же самое, но не блокируется до завершения. Вероятно, отсюда и ваш тупик. Ваш поток пользовательского интерфейса ожидает дочернего потока, а дочерний поток ожидает завершения пользовательского интерфейса. 06.07.2017
  • @BradleyUffner «Вы уверены в ...» был нацелен на ОП. 06.07.2017
  • @Rotem Ааа, извини, не заметил, что ты не ОП, 06.07.2017
  • @Filippo также, вам, вероятно, не следует никогда звонить Thread.Abort. В 99% (или даже больше) случаев, когда вы видите Thread.Abort, вы делаете что-то очень неправильное. Это может оставить приложение в таком непредсказуемом состоянии, что вы можете столкнуться со всеми видами сумасшедших ошибок. См. stackoverflow.com/questions/1559255/< /а> 06.07.2017
  • Помимо других комментариев относительно BeginInvoke и Abort, я думаю, что вместо if (!goOnPolling) continue; вы хотите if (!goOnPolling) break;. Или, скорее, просто придерживайтесь while (goOnPolling). 06.07.2017

Ответы:


1

Мы знаем, что эти другие потоки хотят попасть в поток пользовательского интерфейса через Invoke, и мы знаем, что вы связываете поток UI с потоком Sleep.

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

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    goOnPolling = false;
    for(int i=0;i<10;i++)
    {
        Application.DoEvents();
        Thread.Sleep(50);
    }
    //foreach (Thread element in activeThreadlist)
    //    if (element.IsAlive)
    //        element.Abort();
}

А также измените потоки, чтобы справляться с выбрасываемыми исключениями, если они пытаются Invoke после того, как форма уже была закрыта, вместо того, чтобы злобно пытаться разорвать их с помощью Abort.


Что касается почему так много потоков находятся в состоянии, когда они хотят Invoke, это неясно из опубликованного кода, но имейте в виду, что Invoke сериализует доступ к потоку пользовательского интерфейса. Возможно, что все потоки запросили Invoke задолго до того, как поток пользовательского интерфейса начал обрабатывать сообщение Windows, которое преобразуется в событие "закрытие формы", если доступ к потоку сильно загружен.

06.07.2017
Новые материалы

Объяснение документов 02: BERT
BERT представил двухступенчатую структуру обучения: предварительное обучение и тонкая настройка. Во время предварительного обучения модель обучается на неразмеченных данных с помощью..

Как проанализировать работу вашего классификатора?
Не всегда просто знать, какие показатели использовать С развитием глубокого обучения все больше и больше людей учатся обучать свой первый классификатор. Но как только вы закончите..

Работа с цепями Маркова, часть 4 (Машинное обучение)
Нелинейные цепи Маркова с агрегатором и их приложения (arXiv) Автор : Бар Лайт Аннотация: Изучаются свойства подкласса случайных процессов, называемых дискретными нелинейными цепями Маркова..

Crazy Laravel Livewire упростил мне создание электронной коммерции (панель администратора и API) [Часть 3]
Как вы сегодня, ребята? В этой части мы создадим CRUD для данных о продукте. Думаю, в этой части я не буду слишком много делиться теорией, но чаще буду делиться своим кодом. Потому что..

Использование машинного обучения и Python для классификации 1000 сезонов новичков MLB Hitter
Чему может научиться машина, глядя на сезоны новичков 1000 игроков MLB? Это то, что исследует это приложение. В этом процессе мы будем использовать неконтролируемое обучение, чтобы..

Учебные заметки: создание моего первого пакета Node.js
Это мои обучающие заметки, когда я научился создавать свой самый первый пакет Node.js, распространяемый через npm. Оглавление Глоссарий I. Новый пакет 1.1 советы по инициализации..

Забудьте о Matplotlib: улучшите визуализацию данных с помощью умопомрачительных функций Seaborn!
Примечание. Эта запись в блоге предполагает базовое знакомство с Python и концепциями анализа данных. Привет, энтузиасты данных! Добро пожаловать в мой блог, где я расскажу о невероятных..


Для любых предложений по сайту: [email protected]