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

У меня есть метод расширения SafeInvoke Control, аналогичный тому, который описан здесь, здесь обсуждается Greg 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= "System.Windows.Forms"

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:\code\DriverInterface2\DriverInterface2.UI.WinForms\Dialogs\FormExtensions.cs:line 16

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

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

Ответ 1

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

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

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

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

Посмотрим, что это значит для вас. (Это было бы легче рассуждать, если бы мы увидели вашу реализацию 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 из потока non-gui для элемента управления, чей дескриптор не был создан.

uiElement не является нулевым, поэтому мы проверяем uiElement.InvokeRequired. В документах MSDN (bolded) InvokeRequired вернется false, потому что, хотя он был создан в другом потоке, дескриптор hasn ' был создан! Это отправляет нас в условие else, где мы проверяем IsDisposed или немедленно переходим к вызову представленного действия... из background thread!

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

Ответ 2

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

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

Ответ 3

Вот мой ответ на аналогичный question:

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

var x = this.Handle; 

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

Ответ 4

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

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

ИЗМЕНИТЬ

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

Ответ 5

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

В многопоточном проекте, где логика "контроллера" находится не в WinForm, эта функция играет роль конструкторов Control, чтобы избежать этой ошибки.

Ответ 6

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

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

Ответ 7

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

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

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

Ответ 8

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

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) чтобы попытаться вызвать поток пользовательского интерфейса, но конструктор не дает дескриптора экземпляру потока UI, поэтому другие потоки получают другие экземпляры дескрипторов, которые являются либо 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; });
    }
}

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