Корректное ожидание завершения потока
От: Аноним  
Дата: 20.04.12 11:33
Оценка:
Добрый день!
Есть приложение, которое опрашивает некий прибор по COM порту в потоке и выводит прочитанные данные в GUI.
Опрос делается в бесконечном цикле с проверкой "флажка" ManualResetEvent для сигнализации завершения и поднятия другого "флажка" для сигнализации завершения, как-то так:
while(true)
{
...                
  if(threadStop.WaitOne(0, true) == true)
    break;
...
}
threadEnd.Set();

В приложении есть чекбокс по клику на который я лолжен остановить поток чтения — т.е. поднимаю флажок остановки опроса и жду флажка завершения:
threadStop.Set();
threadEnd.WaitOne(2000, true);

Проблема собственно в том, что threadEnd устанавливается не всегда. И я не могу понять, почему так происходит. Время в ожидании 2 секунды можно поменять на любое другое — ничего не поменяется. Просто после threadStop.Set() я буду находится в функции клика чекбокса, а функции опроса не будет передаваться управление. Может кто-нибудь даст мне совет?
Re: Корректное ожидание завершения потока
От: Аноним  
Дата: 20.04.12 12:05
Оценка:
А>
А>threadStop.Set();
А>threadEnd.WaitOne(2000, true);
А>


Для ожидания завершения потока есть Thread.Join.
А вообще в GUI-потоке нельзя ничего "ждать". Почему не воспользоваться делегатом + Invoke?

А>Просто после threadStop.Set() я буду находится в функции клика чекбокса, а функции опроса не будет передаваться управление.


Как ей может не передаваться управление, если она работает в другом потоке? Покажите больше кода.
Re: Корректное ожидание завершения потока
От: rumatavz  
Дата: 20.04.12 12:17
Оценка:
threadStop.WaitOne(0, true)


Второй аргумент true на всякий случай или есть основания? Если есть то этого куска кода явно недостаточно.

Если запустить бесконечный цикл ровно в том виде как на форме проблема продолжает наблюдаться?
Re[2]: Корректное ожидание завершения потока
От: Аноним  
Дата: 20.04.12 12:27
Оценка:
Здравствуйте, Аноним, Вы писали:

А>>
А>>threadStop.Set();
А>>threadEnd.WaitOne(2000, true);
А>>


А>Для ожидания завершения потока есть Thread.Join.

А>А вообще в GUI-потоке нельзя ничего "ждать". Почему не воспользоваться делегатом + Invoke?

А>>Просто после threadStop.Set() я буду находится в функции клика чекбокса, а функции опроса не будет передаваться управление.


А>Как ей может не передаваться управление, если она работает в другом потоке? Покажите больше кода.


Спасибо, что откликнулись!
Прибил свой второй "флажок", теперь завершение такое:
threadStop.Set();
th.Join();

Но иногда программа зависает на th.Join() — т.е. аналогичная ситуация. Я не могу понять, почему когда выставил "флажок" threadStop, до функции опроса, где этот флажок анализируется, чтобы выйти из вечного цикла, дело не доходит (проверял точками останова и отладочной печатью). Проблема в том, что я делаю Join() в GUI-потоке? Но все равно не могу осмыслить отчего блокируюсь на th.Join.
Re[2]: Корректное ожидание завершения потока
От: Аноним  
Дата: 20.04.12 12:34
Оценка:
Здравствуйте, rumatavz, Вы писали:

R>
R>threadStop.WaitOne(0, true)
R>


R>Второй аргумент true на всякий случай или есть основания? Если есть то этого куска кода явно недостаточно.


R>Если запустить бесконечный цикл ровно в том виде как на форме проблема продолжает наблюдаться?


Второй аргумент на всякий случай — пробовал сначала без него, но без изменений.

Закомментил лишнее в цикле — проблема исчезла...
Но там не так много, из таблицы делегатов по очереди выбираются функции, которые читают из com порта определенные параметры:
while(true)
{                
  try
  {              
    if(threadStop.WaitOne(0, true) == true)
      break;

      objDevice.Open();
      MasReadFuncDlg[funcIdx++]();
      Thread.Sleep(200);
      objDevice.Close();

      if(funcIdx >= MasReadFuncDlg.Length)
        funcIdx = 0;
  }
  catch{}            
}
Re: Корректное ожидание завершения потока
От: rumatavz  
Дата: 20.04.12 13:22
Оценка:
А>а функции опроса не будет передаваться управление.

Тот поток, в котором крутится бесконечный цикл виснет на какой нибудь строке или продолжает крутиться?
Re[2]: Корректное ожидание завершения потока
От: Аноним  
Дата: 20.04.12 13:36
Оценка:
Здравствуйте, rumatavz, Вы писали:

А>>а функции опроса не будет передаваться управление.


R>Тот поток, в котором крутится бесконечный цикл виснет на какой нибудь строке или продолжает крутиться?

Поток с бесконечным циклом не виснет, виснет в функции клика по чекбоксу, где я останавливаю бесконечный цикл.
Переписал событие клика через Invoke — не помогло:

delegate void TestInvoker();
private void chbxStatePID_MouseClick(object sender, MouseEventArgs e)
{
  Invoke(new TestInvoker(testFunc));    
}

void testFunc()
{
  UInt16 tempState = 0;

  threadStop.Set();
  th.Join();          // !!!Висим тут, не всегда, но не понятно.
}
Re[2]: Корректное ожидание завершения потока
От: Аноним  
Дата: 20.04.12 13:43
Оценка:
Здравствуйте, rumatavz, Вы писали:

А>>а функции опроса не будет передаваться управление.


R>Тот поток, в котором крутится бесконечный цикл виснет на какой нибудь строке или продолжает крутиться?

Я выше не правильно выразился. Поток состоит из функции опроса с бесконечным циклом — так вот при выставлении из другого места (нажатие чекбокса) флажка остановки, судя по отладочным сообщениям, я больше в ту функцию опроса никогда не попадаю. И это мне совсем не понятно.
Re[3]: Корректное ожидание завершения потока
От: rumatavz  
Дата: 20.04.12 13:57
Оценка:
А>Я выше не правильно выразился. Поток состоит из функции опроса с бесконечным циклом — так вот при выставлении из другого места (нажатие чекбокса) флажка остановки, судя по отладочным сообщениям, я больше в ту функцию опроса никогда не попадаю. И это мне совсем не понятно.

Так вот мой вопрос и состоит в том, что делает этот самый поток.
До нажатия на чекбокс поставьте брейк в цикле и в окне Debug->Windows->Threads посмотрите ИД потока.
После нажатия на чекбокс найдите этот же поток в том окне и посмотрите его Call Stack (Debug->Windows->Call Stack)
Колл стэк приведите плз.
Re[4]: Корректное ожидание завершения потока
От: Аноним  
Дата: 20.04.12 14:13
Оценка:
Здравствуйте, rumatavz, Вы писали:

А>>Я выше не правильно выразился. Поток состоит из функции опроса с бесконечным циклом — так вот при выставлении из другого места (нажатие чекбокса) флажка остановки, судя по отладочным сообщениям, я больше в ту функцию опроса никогда не попадаю. И это мне совсем не понятно.


R>Так вот мой вопрос и состоит в том, что делает этот самый поток.

R>До нажатия на чекбокс поставьте брейк в цикле и в окне Debug->Windows->Threads посмотрите ИД потока.
R>После нажатия на чекбокс найдите этот же поток в том окне и посмотрите его Call Stack (Debug->Windows->Call Stack)
R>Колл стэк приведите плз.

Спасибо за советы и желание помочь! Я в отладки многопоточности совсем не силен. Когда приложение подвисает на Join поток сидит в функции SetText.
Эта функция служит для "безопасного" отображения в GUI принятых данных из COM порта, выглядит так:
delegate void SetTextInvoker(object box, object text);
public void SetText(object box, object text)
{
    if (InvokeRequired)
    {
        Invoke(new SetTextInvoker(SetText), box, text);
        return;
    }
    (box as TextBox).Text = (string)text;
}


Колл стэк выглядит так:

[In a sleep, wait, or join]
[External Code]
> AutoCalib.exe!AutoCalib.ModBusPolling.PidPollingForm.test() Line 305 + 0x10 bytes C#
[External Code]
AutoCalib.exe!AutoCalib.ModBusPolling.PidPollingForm.chbxStatePID_MouseClick(object sender = {Text = Cannot evaluate expression because the current thread is in a sleep, wait, or join CheckState = Checked}, System.Windows.Forms.MouseEventArgs e = {X = 0x00000006 Y = 0x0000000b Button = Left}) Line 297 + 0x29 bytes C#
[External Code]
AutoCalib.exe!AutoCalib.frmMain.регистрыПИДToolStripMenuItem_Click(object sender = {System.Windows.Forms.ToolStripMenuItem}, System.EventArgs e = {System.EventArgs}) Line 238 + 0xa bytes C#
[External Code]
AutoCalib.exe!AutoCalib.Program.Main() Line 17 + 0x1d bytes C#
[External Code]

Re[4]: Корректное ожидание завершения потока
От: Аноним  
Дата: 20.04.12 14:34
Оценка:
Добавлю к вышесказанному, что в функции SetText в Invoke() входит, но от туда уже не выходит. Поэтому потоку и не передается управление. Но почему так происходит, мне не хватает опыта понять.
Re: Корректное ожидание завершения потока
От: Flem1234  
Дата: 20.04.12 14:39
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Добрый день!

А>Есть приложение, которое опрашивает некий прибор по COM порту в потоке и выводит прочитанные данные в GUI.
А почему не BackgroundWorker? У него довольно удобные события для таких сценариев.
Re[2]: Корректное ожидание завершения потока
От: Аноним  
Дата: 20.04.12 14:44
Оценка:
Здравствуйте, Flem1234, Вы писали:

F>Здравствуйте, Аноним, Вы писали:


А>>Добрый день!

А>>Есть приложение, которое опрашивает некий прибор по COM порту в потоке и выводит прочитанные данные в GUI.
F>А почему не BackgroundWorker? У него довольно удобные события для таких сценариев.
Видимо придется смотреть в сторону BackgroundWorker, просто считал, что я реализую аналогичный ему функционал. Теперь уже из-за спортивного интереса хочется понять, что не так...
Re[3]: Корректное ожидание завершения потока
От: Flem1234  
Дата: 20.04.12 14:53
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Видимо придется смотреть в сторону BackgroundWorker, просто считал, что я реализую аналогичный ему функционал. Теперь уже из-за спортивного интереса хочется понять, что не так...


Он простой как валенок, этот воркер, немного громоздкий только.
Лучше выложите полный пример, а то по таким данным оч.тяжело догадаться, что происходит.
Re[4]: Корректное ожидание завершения потока
От: Аноним  
Дата: 20.04.12 14:58
Оценка:
Здравствуйте, Flem1234, Вы писали:

F>Здравствуйте, Аноним, Вы писали:


А>>Видимо придется смотреть в сторону BackgroundWorker, просто считал, что я реализую аналогичный ему функционал. Теперь уже из-за спортивного интереса хочется понять, что не так...


F>Он простой как валенок, этот воркер, немного громоздкий только.

F>Лучше выложите полный пример, а то по таким данным оч.тяжело догадаться, что происходит.
Полный пример еще более сложен для понимания, я пытался как можно яснее и без перегруза информации изложить суть... Благодаря помощи выше вроде выяснилось, что Invoke, который вызывается в функции установки текста в текстбокс, иногда улетает в астрал. И в этом случаи я навсегда зависаю при ожидании завершения потока. Осталось понять, отчего так ведет себя Invoke...
Re[3]: Корректное ожидание завершения потока
От: rumatavz  
Дата: 20.04.12 14:59
Оценка:
Как я понимаю MasReadFuncDlg вызывает SetText

while(true)
{                
  try
  {              
    if(threadStop.WaitOne(0, true) == true)
      break;

      objDevice.Open();
      MasReadFuncDlg[funcIdx++]();
      Thread.Sleep(200);
      objDevice.Close();

      if(funcIdx >= MasReadFuncDlg.Length)
         funcIdx = 0;
  }
  catch{}            
}


public void SetText(object box, object text)
{
if (InvokeRequired)
{
Invoke(new SetTextInvoker(SetText), box, text);
return;
}
(box as TextBox).Text = (string)text;
}
[/c#]

threadStop.Set();
threadEnd.WaitOne(2000, true);


Теперь смотрим
В потоке ГУИ выполняется строка threadEnd.WaitOne(2000, true) теперь поток ГУИ не может ничего сделать пока не выполнится эта строка.
У потока с циклом 2 варианта: SetText уже выполнилась в данной итерации. Тогда всё ок — поток вывалится на брейка.
А вот если после то выполнится SetText. Эта функция в свою очередь вызовет Invoke. Invoke начнет ждать пока поток ГУИ будет свободен.
А поток ГУИ будет ждать threadEnd.Set(). Дедлок на лицо.
Re: Корректное ожидание завершения потока
От: rumatavz  
Дата: 20.04.12 15:13
Оценка:
Технически решить это можно вызвав BeginInvoke в потоке цикла.
Но на мой взгляд тут архитектурная ошибка. Вы в ГУИ ждете, пока другой поток что то сделает — в данном случае завершится.
Вряд ли вам нужно именно подвивание интерефейса до завершения потока.
По этому я бы сделал бы так:

class ClassWhereLoopRuns : IDisposable
{
   private volatile bool _needShutdown;

   public void ShutDown()
   {
       _needShutdown = true;
   }

   ? void Loop()
   {
      while(_needShutdown)
      }
       ...
       RaiseEventTextChanged();
       ...
      }
    }
    RaiseEventShutDownDone();
}

class SomeControl
{

   void OnClick(...)
   {
      _classWhereLoopRuns.ShutDown(); 
   }

   void OnTextChanged(...) //OnShutDownDone работает аналогично
   {
       Invoke(DoOnTextChanged) //в DoOnTextChanged работа с ГУИ
   }
}


ClassWhereLoopRuns имеет два события на которые подписывается контрол. Синхронизацию осуществляет сам контрол, остальные ничего не знают о ГУИшных потоках. Когда ClassWhereLoopRuns завершает работу он говорит об этом ГУИ, а не ГУИ ждет завершения.
Re[2]: Корректное ожидание завершения потока
От: Аноним  
Дата: 20.04.12 21:25
Оценка:
Здравствуйте, rumatavz, Вы писали:

R>Технически решить это можно вызвав BeginInvoke в потоке цикла.

R>Но на мой взгляд тут архитектурная ошибка. Вы в ГУИ ждете, пока другой поток что то сделает — в данном случае завершится.
R>Вряд ли вам нужно именно подвивание интерефейса до завершения потока.
R>По этому я бы сделал бы так:

R>
R>class ClassWhereLoopRuns : IDisposable
R>{
R>   private volatile bool _needShutdown;

R>   public void ShutDown()
R>   {
R>       _needShutdown = true;
R>   }

R>   ? void Loop()
R>   {
R>      while(_needShutdown)
R>      }
R>       ...
R>       RaiseEventTextChanged();
R>       ...
R>      }
R>    }
R>    RaiseEventShutDownDone();
R>}

R>class SomeControl
R>{

R>   void OnClick(...)
R>   {
R>      _classWhereLoopRuns.ShutDown(); 
R>   }

R>   void OnTextChanged(...) //OnShutDownDone работает аналогично
R>   {
R>       Invoke(DoOnTextChanged) //в DoOnTextChanged работа с ГУИ
R>   }
R>}
R>


R>ClassWhereLoopRuns имеет два события на которые подписывается контрол. Синхронизацию осуществляет сам контрол, остальные ничего не знают о ГУИшных потоках. Когда ClassWhereLoopRuns завершает работу он говорит об этом ГУИ, а не ГУИ ждет завершения.


Спасибо за предложенное решение моей проблемы! Обязательно в понедельник пересмотрю свою архитектуру — надеюсь поможет.
Re[5]: Корректное ожидание завершения потока
От: Sinclair Россия https://github.com/evilguest/
Дата: 23.04.12 03:00
Оценка:
Здравствуйте, Аноним, Вы писали:

F>>Он простой как валенок, этот воркер, немного громоздкий только.

F>>Лучше выложите полный пример, а то по таким данным оч.тяжело догадаться, что происходит.
А>Полный пример еще более сложен для понимания, я пытался как можно яснее и без перегруза информации изложить суть... Благодаря помощи выше вроде выяснилось, что Invoke, который вызывается в функции установки текста в текстбокс, иногда улетает в астрал. И в этом случаи я навсегда зависаю при ожидании завершения потока. Осталось понять, отчего так ведет себя Invoke...
By Design.
Invoke отправляет сообщение в UI-поток и пытается дождаться ответа на него. Deadlock.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[2]: Корректное ожидание завершения потока
От: Аноним  
Дата: 23.04.12 07:22
Оценка:
Большое человеческое спасибо rumatavz! Благодаря вашим разъяснением все работает, как и хотелось.
И я теперь имею понимание, почему не работало, что не менее важно.

PS: Проглядываю свои прошлые программки в связи с открывшимися знаниями на предмет исправления.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.