Invoke или BeginInvoke не может быть вызван для элемента управления, пока не будет создан дескриптор окна


у меня есть метод расширения SafeInvoke Control, похожий на один Грег D обсуждает здесь (минус проверка IsHandleCreated).

Я вызываю его из System.Windows.Forms.Form следующим образом:

public void Show(string text) {
    label.SafeInvoke(()=>label.Text = text);
    this.Show();
    this.Refresh();
}

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

System.InvalidOperationException произошел

Message= "Invoke или BeginInvoke не может быть призван на контроль до тех пор, пока оконная ручка была создан."

Source= система".Окна.Формы"

StackTrace:
at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
at System.Windows.Forms.Control.Invoke(Delegate method)
at DriverInterface2.UI.WinForms.Dialogs.FormExtensions.SafeInvoke[T](T control, Action`1 action) 
in C:codeDriverInterface2DriverInterface2.UI.WinFormsDialogsFormExtensions.cs:line 16

что происходит и как это исправить? Я знаю столько, сколько это не проблема создания формы, так как иногда она будет работать один раз и потерпит неудачу в следующий раз, так в чем же может быть проблема?

PS. Я действительно действительно ужасен в WinForms, кто-нибудь знает хорошую серию статей, которая объясняет всю модель и как с ней работать?

8 66

8 ответов:

возможно, что вы создаете свои элементы управления в неправильном потоке. Рассмотрим следующее документация от MSDN:

это означает, что InvokeRequired может возвращает false, если вызов не требуется (вызов происходит в том же потоке), или если элемент управления был создан на другой поток, но элемент управления дескриптор еще не создан.

в случае, когда ручка управления еще не было создан, вы должны не просто вызывать свойства, методы, или события на элементе управления. Это может быть причина дескриптор элемента управления, чтобы быть созданный на фоне потока, изоляция элемента управления в потоке без насоса сообщения и делать приложение нестабильно.

вы можете защитить от этого случая путем также проверка значения IsHandleCreated когда InvokeRequired возвращает false в фоновом потоке. Если дескриптор управления еще не был создан, вы должны дождаться был создан перед вызовом Invoke или Метод BeginInvoke. Как правило, это происходит только если создается фоновый поток в конструкторе основной формы для применения (как в Приложение.Выполнить(новая основная форма()), до формы или Приложение.Бег был вызван.

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

Предположим ваш реализация идентична указанной, за исключением проверки против IsHandleCreated давайте следовать логике:

public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous)
{
    if (uiElement == null)
    {
        throw new ArgumentNullException("uiElement");
    }

    if (uiElement.InvokeRequired)
    {
        if (forceSynchronous)
        {
            uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
        }
        else
        {
            uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); });
        }
    }
    else
    {    
        if (uiElement.IsDisposed)
        {
            throw new ObjectDisposedException("Control is already disposed.");
        }

        updater();
    }
}

рассмотрим случай, когда мы называем SafeInvoke из потока без графического интерфейса для элемента управления, дескриптор которого не был создан.

uiElement не null, поэтому мы проверяем uiElement.InvokeRequired. В документах MSDN (выделено жирным шрифтом)InvokeRequired вернутся false, потому что, хотя он был создан в другом потоке, то ручка не была создана! Это посылает нас к else состояние, в котором мы проверяем IsDisposed или немедленно приступить к вызову, представленных действий... из фонового потока!

На данный момент все ставки выключены re: этот элемент управления, потому что его дескриптор был создан на потоке, который не имеет насоса сообщений для него, как упоминалось во втором абзаце. Возможно, это тот случай, с которым вы сталкиваетесь?

нашел InvokeRequired не надежный, поэтому я просто использую

if (!this.IsHandleCreated)
{
    this.CreateHandle();
}

вот мой ответ на аналогичную вопрос:

Я думаю (пока не совсем уверен), что это потому, что InvokeRequired будет всегда возвращает false, если элемент управления имеет еще не был загружен / показан. Я сделал обходной путь, который, кажется, работает для момент, который является простым ссылка на дескриптор связанного управление в своем создателе, вроде так:

var x = this.Handle; 

(см. http://ikriv.com/en/prog/info/dotnet/MysteriousHang.html)

метод в сообщении, которое вы связываете с вызовами Invoke / BeginInvoke, прежде чем проверять, был ли дескриптор элемента управления создан в случае, когда он вызывается из потока, который не создал элемент управления.

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

EDIT

Если вы проверяете InvokeRequired и HandleCreated перед вызовом invoke вы не должны получить это исключение.

если вы собираетесь использовать Control из другого потока, прежде чем показывать или делать другие вещи с Control, рассмотрим принудительное создание его дескриптора в конструкторе. Это делается с помощью CreateHandle

ссылка на дескриптор связанного элемента управления в его создателе, например:

Примечание: будьте осторожны с этим решением.Если элемент управления имеет дескриптор, он намного медленнее выполняет такие действия, как установка его размера и местоположения. Это делает InitializeComponent намного медленнее. Лучшим решением является не фоновое ничего, прежде чем элемент управления имеет дескриптор.

Я испытывал ту же ошибку. Я вызывал invoke из конструктора Form ().

Я решил эту проблему, вызвав вызов из события Form_Load вместо этого.

Я не вижу никаких исключений после того, как я сделал это изменение.

у меня была эта проблема с такой простой формой:

public partial class MyForm : Form
{
    public MyForm()
    {
        Load += new EventHandler(Form1_Load);
    }

    private void Form1_Load(Object sender, EventArgs e)
    {
        InitializeComponent();
    }

    internal void UpdateLabel(string s)
    {
        Invoke(new Action(() => { label1.Text = s; }));
    }
}

затем на n другие асинхронные потоки, которые я использовал new MyForm().UpdateLabel(text) чтобы попытаться вызвать поток пользовательского интерфейса, но конструктор не дает дескриптора экземпляру потока пользовательского интерфейса, поэтому другие потоки получают другие дескрипторы экземпляра, которые либо Object reference not set to an instance of an object или Invoke or BeginInvoke cannot be called on a control until the window handle has been created. Чтобы решить эту проблему, я использовал статический объект для хранения дескриптора пользовательского интерфейса:

public partial class MyForm : Form
{
    private static MyForm _mf;        

    public MyForm()
    {
        Load += new EventHandler(Form1_Load);
    }

    private void Form1_Load(Object sender, EventArgs e)
    {
        InitializeComponent();
        _mf = this;
    }

    internal void UpdateLabel(string s)
    {
        _mf.Invoke((MethodInvoker) delegate { _mf.label1.Text = s; });
    }
}

Я думаю, что это работает нормально, до сих пор...