Симптомы:
Приложение зависает при вызове 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() на пару секунд для очистки очереди в вызывающем треде (не наш метод, мы боремся за скорость);
— устранить причину заполнености очереди сообщений. |