Симптомы:
Приложение зависает при вызове Control.Invoke() для фреймворка 2.0, под 1.1 все работает изумительно.
Вскрытие показало:
Не приходит эвент об окончании выполнения функции которую хотели вызвать через Control.Invoke(), более того сама функция, которую нужно вызвать в другой нитке, не вызывается вобще, однако запрос поставлен в очередь контрола. Дальнейшее исследование вопроса выявило, что выполнение очереди происходит по получении контролом специального зарегистренного сообщения "WindowsForms12_ThreadCallbackMessage". Вот тут то и выяснилось, что это сообщение вобще не посылается. Ниже приведен дизасмблеж кода с ошибкой.
private object MarshaledInvoke(Control caller, Delegate method, object[] args, bool synchronous)
{
int lpdwProcessId;
if (!this.IsHandleCreated)
throw new InvalidOperationException(SR.GetString("ErrorNoMarshalingThread"));
if (((ActiveXImpl) this.Properties.GetObject(PropActiveXImpl)) != null)
IntSecurity.UnmanagedCode.Demand();
bool flag = false;
if ((SafeNativeMethods.GetWindowThreadProcessId(new HandleRef(this, this.Handle), out lpdwProcessId) == SafeNativeMethods.GetCurrentThreadId()) && synchronous)
flag = true;
ExecutionContext executionContext = null;
if (!flag)
executionContext = ExecutionContext.Capture();
ThreadMethodEntry entry = new ThreadMethodEntry(caller, method, args, synchronous, executionContext);
lock (this)
{
if (this.threadCallbackList == null)
this.threadCallbackList = new Queue();
}
lock (this.threadCallbackList)
{
if (threadCallbackMessage == 0x0)
threadCallbackMessage = SafeNativeMethods.RegisterWindowMessage(Application.WindowMessagesVersion + "_ThreadCallbackMessage");
this.threadCallbackList.Enqueue(entry); // Ставим запрос в очередь вызываемого контрола
}
if (flag)
this.InvokeMarshaledCallbacks(); // Если можно вызвать функцию синфронно, то вызываем синхронноelse
{
UnsafeNativeMethods.PostMessage(new HandleRef(this, this.Handle), threadCallbackMessage, IntPtr.Zero, IntPtr.Zero); // Вызываем функцию асинхронно
// БАГА ТУТ: нет проверки на невозможность поставить сообщение в очередь
}
if (!synchronous)
return entry;
if (!entry.IsCompleted)
this.WaitForWaitHandle(entry.AsyncWaitHandle); // Ждем окончания выполнения для асинхронного вызоваif (entry.exception != null)
throw entry.exception;
return entry.retVal;
}
Причина смерти:
Горячо любимый Microsoft забыл выдать эксепшен в случае невозможности отсылки сообщения с помощью PostMessage. Оказалось, что на момент Control.Invoke() очередь сообщений заполнена на 100%!!!
Лечение:
— по таймеру посылать "WindowsForms12_ThreadCallbackMessage" контолу (не кошерно, но когда нет другого выхода, то сойдет);
— сделать Thread.Sleep() на пару секунд для очистки очереди в вызывающем треде (не наш метод, мы боремся за скорость);
— устранить причину заполнености очереди сообщений.
Re: Зависание при вызове Control.Invoke (с решением)
Здравствуйте, WW898, Вы писали:
WW>Симптомы: WW>Приложение зависает при вызове Control.Invoke() для фреймворка 2.0, под 1.1 все работает изумительно.
WW>Вскрытие показало: WW>Не приходит эвент об окончании выполнения функции которую хотели вызвать через Control.Invoke(), более того сама функция, которую нужно вызвать в другой нитке, не вызывается вобще, однако запрос поставлен в очередь контрола. Дальнейшее исследование вопроса выявило, что выполнение очереди происходит по получении контролом специального зарегистренного сообщения "WindowsForms12_ThreadCallbackMessage". Вот тут то и выяснилось, что это сообщение вобще не посылается. Ниже приведен дизасмблеж кода с ошибкой.
Вопрос не по теме: Каким образом отлаживали системные функции??? Или просто изучали код с помощью какого-нибудь рефлектора и анализировали его?
... << RSDN@Home 1.1.4 stable SR1 rev. 568>>
Re[2]: Зависание при вызове Control.Invoke (с решением)
Здравствуйте, tyger, Вы писали:
T>Вопрос не по теме: Каким образом отлаживали системные функции??? Или просто изучали код с помощью какого-нибудь рефлектора и анализировали его?
Отлаживал с ассемблере кусочек и анализ с помощью рефлектора.
Дополнительно скажу в чем была загвоздка конкретно в моем приложении:
Есть 2 нитки: главная и рабочая. Рабочая передает в главную с помошью BeginInvoke информацию о прогрессе операции для окна №1. В конце рабочая вызывает Invoke для окна №2 и зависает. Рабочая нитка не успевала разгребать поставленые в очередь сообщения для окна №1, поэтому сообщение для окна №2 было ТИХО откинуто. Все зависло. После кардинального уменьшения количества BeginInvoke для окна №1 все заработало.
Re: Зависание при вызове Control.Invoke (с решением)
Здравствуйте, vdimas, Вы писали:
V>Попробуй вместо Invoke использовать Dispatcher, он специально для подобных нужд создавался.
Сильно извиняюсь, но я не очень понял что именно вы предлагаете. Не могли бы разъяснить, желательно с примерами. Заранее благодарен.
Re[3]: Зависание при вызове Control.Invoke (с решением)
Здравствуйте, WW898, Вы писали:
WW>Сильно извиняюсь, но я не очень понял что именно вы предлагаете. Не могли бы разъяснить, желательно с примерами. Заранее благодарен.
Здравствуйте, vdimas, Вы писали:
V>А нафига под 1.0? Нет вообще ни одной причины продолжать его поддержку.
У вас нет, а у нас есть. "Претензии на устройство мира не принимаются." (С)
V>Имелся ввиду объект Dispatcher из .Net 3.0, который недавно вышел.
А что меняется? Загляните в Dispatcher.RequestForegroundProcessing(). Там вы увидите все тот же PostMessage, НО:
— повторная постановка сообщения в очередь не производиться;
— корректно обрабатываются ситуация, когда невозможно поставить сообщение в очередь.
Ну что тут сказать: умеет Микрософт делать работу над ошибками когда хочет. Вот если бы тоже самое сделали бы и с Control.Invoke(), но видно не судьба...
WW>Ну что тут сказать: умеет Микрософт делать работу над ошибками когда хочет. Вот если бы тоже самое сделали бы и с Control.Invoke(), но видно не судьба...
Contorl.Invoke() — это приблуда растет из-за заточенности GUI-домена на STA, из-за ActiveX, ну и плюс оконный мессейджинг привязан к очереди сообщений потока, в котором создавалось окно. Т.е. нативное окошко к треду привязано намертво.
Ты лучше скажи, зачем тебе BeginInvoke()? Почему не сразу Invoke()? И как можно было умудриться забить очередь до предела?
Зачем посылать статус окну постоянно? Можно просто запомнить время последней отсылки и посылай только через нек. интервал.
Re[8]: Зависание при вызове Control.Invoke (с решением)
Здравствуйте, vdimas, Вы писали:
V>Contorl.Invoke() — это приблуда растет из-за заточенности GUI-домена на STA, из-за ActiveX, ну и плюс оконный мессейджинг привязан к очереди сообщений потока, в котором создавалось окно. Т.е. нативное окошко к треду привязано намертво.
А что поменялось-то в диспатчере? Как было все привязано к очереди сообщений так и осталось. Просто корректнее сделали и вокруг разного удобства понавесили. А так — один в один.
V>Ты лучше скажи, зачем тебе BeginInvoke()? Почему не сразу Invoke()? И как можно было умудриться забить очередь до предела?
Invoke дольше работает за счет ожидания результата, а результат-то выполнения как раз нам и не нужен.
Умудриться? На то талант нужен! И он есть! Жаль правда, что не у меня.
V>Зачем посылать статус окну постоянно? Можно просто запомнить время последней отсылки и посылай только через нек. интервал.
Все уже с неделю как починено по методу "что НЕ делается — все к лучшему" (С) .