03 April, 2007

Зависание при вызове Control.Invoke

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

  • устранить причину заполнености очереди сообщений.