InvalideOperationException/вызов

следующий код используется для запуска вызова (код сокращен, я не учел обработку ошибок в этом примере, чтобы сделать его более понятным)

public static void InvokeIfNecessary(this Control control, MethodInvoker methodInvoker)
{
    if (control != null && !control.IsDisposed && !control.Disposing)
    {

        if (control.InvokeRequired)
        {
            control.Invoke(methodInvoker);
        }
        else
        {
            methodInvoker();
        }
    }
}

Обычно это работает нормально, но иногда, если я вызываю метод формы, выдается InvalidOperationException. Схематический метод, который будет называться

// in a Frm2:
internal void UpdateSomething()
{
    List<NO> myObjects = frmMain.NO.GetNOs();

    if (null != myObjects)
    {
        this.InvokeIfNecessary(() =>
        {
            layoutControlGroup.BeginUpdate(); // DevExpress Layoutcontrolgroup

            foreach (NO aObject in myObjects)
            {
                if(...) // if already a control for the object exist update it.
                {
                    // update

                }
                else
                {
                    // add item
                    LayoutControlItem layoutControlItem = new LayoutControlItem();
                    // create new control
                    Control control = CreateNewControl(aObject);
                    layoutControlItem.Control = control;
                    // do some stuff with visibility and size of control
                    ...
                    layoutControlGroup.AddItem(layoutControlItem); // <-- And here the InvalidOperationException occurs.
                    /// The message is (translated
                    /// InvalidOperationException was not handled by usercode
                    /// The acces on the Control FrmMain was done from another Thrad then the thread which created it.
                    ...;
                }
            }


            ...;

            layoutControlGroupCA.EndUpdate();
        });
    }
}

Что ж... Должен признать, что у меня тут концептуальная проблема.

Почему здесь выбрасывается исключение?

Метод Frm2 создает новый элемент (в НЕТ есть только строка и структура со строками и бул). Доступ к элементу возможен только внутри метода UpdateSomething(). Группа layOutControlGroup является членом Frm2.

Поэтому, на мой взгляд, только новый элемент управления, который должен быть создан в потоке Frm2, должен быть прикреплен к конкретному элементу управления Frm2.

Так почему же он настаивает на FrmMain? (основная форма, которая вызывает метод формы для информирования об обновлении элементов)

P.S. this.InvokeIfRequired ‹- на самом деле это Frm2...


person Offler    schedule 06.06.2013    source источник
comment
@KenKin это схема того, что делает программа. Я удалил конкретные названия компаний и исключения, чтобы они не мешали, а только показывали, что делает эта штука.   -  person Offler    schedule 06.06.2013
comment
Поскольку вы обновили код, я удаляю предыдущий комментарий.   -  person Ken Kin    schedule 06.06.2013
comment
По большому счету вы не должны проверять, нужно ли вам вызывать. Во время компиляции вы должны знать, будет ли данный метод выполняться из потока пользовательского интерфейса или нет. Если вы не участвуете в потоке пользовательского интерфейса, вызовите Invoke, если да, то не делайте этого. Если вы постоянно проверяете, даже когда знаете, что вам нужно будет вызывать, это просто расточительно и иногда доставляет вам боль в тех немногих случаях, когда InvokeRequired ложно, но вам действительно нужно Invoke.   -  person Servy    schedule 06.06.2013


Ответы (2)


Итак, как мы видим, всегда возникают проблемы с вызовом (begin). Вместо этого используйте BeginInvoke. мой намек. Обычно это говорит о том, что начало вызова элементов управления выполняет метод в том же потоке, в котором был создан дескриптор.

Но похоже, что дескриптор формы2 не создан в том же потоке, ИЛИ, возможно, его еще нет. Попробуйте проверить это и проверить это, пожалуйста.

Ах. кстати, помечайте исключения, clr, в visual studio, когда они выбрасываются. Это помогает обнаружить ошибку.

person icbytes    schedule 06.06.2013
comment
Если я позволяю отображать ((Control) this).IsHandleCreated в точке, где он ломается, он показывает true. Поскольку это Frm2, не должен ли InvokeIfNecessary вызываться в правильном контексте, где был создан дескриптор? Как я мог увидеть (VisualStudio 2012), в каком потоке был создан объект? - person Offler; 06.06.2013
comment
Проблема в Вашем первом фрагменте, как он выглядит у меня. И нет, мне пришлось копаться в настройках, чтобы узнать, какой поток создал дескриптор. Но есть, конечно, методы, чтобы получить его. - person icbytes; 06.06.2013
comment
Привет, хорошее объяснение, но я нашел проблему. Это был совершенно другой класс, на котором остановился отладчик. (к объекту обращались в другом классе, чтобы зарегистрировать, что что-то не удалось (должно быть только имя класса)). Был найден теперь по списку активных тем. Но мне все еще любопытно, почему это показывает, что строка AddItem вызовет проблему. - person Offler; 06.06.2013
comment
это связано с тем, что исключение столкнулось с обработчиком NO. И это там, где Твоя линия. Я полагаю. Но подскажите, пожалуйста, как получить ручки с их резьбой? - person icbytes; 06.06.2013
comment
Я не могу сказать. Я просмотрел список активных потоков (Ctrl+D,T) и то, что они делают в момент исключения. В этот момент регистратор попытался получить доступ к элементу управления, данному InvokeIfNecessary, и попытался зарегистрировать, что вызов не может быть выполнен, поскольку дескриптор не был создан. На этой машине кажется, что это происходит в то же время, когда к элементу управления обращаются во второй раз (потому что теперь создается дескриптор, вызывается InvokeIfNecessary, и, как ни странно, он останавливается во время регистрации, потому что журнал не может получить доступ к управлению.) - person Offler; 07.06.2013
comment
Ах, это. Я знаю это. Иногда Вам нужен хендл немного раньше. Это также может быть достигнуто. CreateHandle должен быть вызван явным. - person icbytes; 07.06.2013

Я думаю, что вы проверили InvokeRequired с неправильным контролем.

То, что вам нужно было проверить с помощью InvokeRequired, это layoutControlGroup, но ваш метод расширения проверяет формы с кодом this.InvokeIfNecessary, где this — это Frm2.

Кроме того, вы вызвали layoutControlGroup.BeginUpdate(), но layoutControlGroupCA.EndUpdate() кажется несимметричным использованию.

Исправление кода может быть:

internal void UpdateSomething() {
    var myObjects=frmMain.NO.GetNOs();

    if(null!=myObjects) {
        MethodInvoker beginUpdate=() => layoutControlGroup.BeginUpdate();
        MethodInvoker endUpdate=() => layoutControlGroup.EndUpdate();

        layoutControlGroup.InvokeIfNecessary(beginUpdate);

        foreach(NO aObject in myObjects)
            if(SomeCondition) {
                // update
            }
            else {
                LayoutControlItem layoutControlItem=new LayoutControlItem();
                Control control=CreateNewControl(aObject);
                layoutControlItem.Control=control;

                MethodInvoker addItem=
                    () => {
                        layoutControlGroup.AddItem(layoutControlItem);
                    };

                layoutControlGroup.InvokeIfNecessary(addItem);
            }

        layoutControlGroup.InvokeIfNecessary(endUpdate);
    }
}
person Ken Kin    schedule 06.06.2013
comment
Красиво, но.. это не решение. Также Visual Studio всегда указывала на строку (останавливаться при возникновении исключения), она останавливалась на другом методе. Кстати: layoutControlGroup — это элемент DevExpress, который не наследуется от управления. Поэтому layoutControlGroup.InvokeIfNecessary не будет работать. Мне также немного любопытны детали в вашем объяснении. Поскольку элементы управления взяты из Form2, как может быть, что они не в контексте Form2? Как написано в исходном сообщении, layoutControlGroup является переменной-членом Frm2. - person Offler; 06.06.2013
comment
@Offler: InvokeRequired используется для проверки того, находится ли вызов желаемого элемента управления в текущем контексте. Если он возвращает true, то это не так. Возвращаемое значение InvokeRequired контейнера может не совпадать с элементом управления таргетингом. - person Ken Kin; 06.06.2013
comment
можете ли вы объяснить, в каком случае контейнер (в котором элементы управления могут быть добавлены только из его контекста?) может иметь другой контекст, чем его элементы управления? - person Offler; 06.06.2013
comment
Да. Создайте в форме Button с именем button1 и протестируйте следующий код: Debug.Print("{0}", button1.InvokeRequired); (new Thread(() => { Debug.Print("{0}", button1.InvokeRequired); })).Start(); Вы получите результат, отличный от InvokeRequired. - person Ken Kin; 06.06.2013
comment
@Offler: Поскольку это не решает вашу проблему, я готов удалить ее. Извините за это, я не обновил свою лицензию DevExpress .. - person Ken Kin; 06.06.2013
comment
@Servy: Зачем восстанавливать содержимое моего удаленного ответа ..? - person Ken Kin; 06.06.2013
comment
@KenKin Зачем это удалять? В основном потому, что иногда полезно посмотреть, что такое удаленный ответ. Возможно, это было правильно или достаточно близко к правильному, и его можно было исправить достаточно, чтобы он работал, возможно, он оскорбителен / неуместен и должен быть помечен, возможно, это ответ, который я хотел бы написать, но это неправильно по причинам, которые я не указал. еще видел; может быть полезно увидеть, что кто-то другой опубликовал ответ и удалил его, когда было показано, что он неправильный. - person Servy; 06.06.2013