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


82

У мене є метод розширення SafeInvoke Control, подібний до того, який тут обговорює Грег Д. (мінус перевірка 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
Щось дивне відбувається із посиланням ... розмітка та попередній перегляд правильні ... дивно.
Джордж Мауер,

До якого контексту покликане Шоу? Це коли-небудь викликається із конструктора форми, наприклад? Можливо, буде корисно реєструвати повідомлення для викликів, які відображатимуться проти повідомлень, ініційованих подією HandleCreated, щоб перевірити, що ви викликаєте show лише для об'єктів, у яких вже були створені дескриптори.
Greg D

Для чого призначена програма / як вона розроблена? Що робить this.Show ()? (Я припускаю, що це робить щось більше, ніж просто це. Visis = true;) Чи є ваше посилання на веб-форми друкарською помилкою?
Greg D

this.Show () - це базова форма Form.Show (), що б це не робило. Діалог ніколи не виводиться з конструктора. Це викликається реалізацією служби INotifier, яка має простий метод Notify (string)
Джордж Мауер,

4
Переглядаючи це ще раз, через рік, здається, що ви стаєте помилкою саме з тієї причини, що IsHandleCreatedперевірка існує. Ви намагаєтесь змінити властивість (Надіслати повідомлення) елементу керування, який ще не створений. Одне, що ви можете зробити в цій ситуації, - поставити в чергу представників, які надсилаються до створення елемента керування, а потім запустити їх у HandleCreatedвипадку.
Greg D

Відповіді:


77

Можливо, ви створюєте елементи керування на неправильній нитці. Розгляньте наступну документацію від MSDN :

Це означає, що InvokeRequired може повернути false, якщо Invoke не потрібно (виклик відбувається в тому ж потоці), або якщо елемент керування був створений в іншому потоці, але дескриптор елемента керування ще не створений.

У випадку, коли дескриптор елемента управління ще не створений, не слід просто викликати властивості, методи або події на елементі управління. Це може спричинити створення дескриптора елемента керування у фоновому потоці, ізолюючи елемент керування для потоку без насоса повідомлення та роблячи програму нестабільною.

Ви можете захиститися від цього випадку, також перевіривши значення IsHandleCreated, коли InvokeRequired повертає false у фоновому потоці. Якщо дескриптор управління ще не створений, перед викликом Invoke або BeginInvoke потрібно почекати, поки він буде створений. Як правило, це відбувається лише в тому випадку, якщо фоновий потік створений у конструкторі первинної форми для програми (як у Application.Run (new 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 з потоку, не пов’язаного з графічним інтерфейсом, для елемента керування, дескриптор якого не створений.

uiElementне є нульовим, тому ми перевіряємо uiElement.InvokeRequired. Відповідно, MSDN-документи (напівжирним шрифтом) InvokeRequiredповертаються, falseоскільки, хоча вони були створені в іншому потоці, дескриптор не створений! Це спрямовує нас до elseумови, коли ми перевіряємо IsDisposedабо негайно приступаємо до виклику надісланої дії ... з фонового потоку !

На цьому етапі всі ставки відключені: цей контроль, оскільки його дескриптор створений на потоці, який не має насоса для повідомлення, як зазначено у другому абзаці. Можливо, це випадок, з яким ви стикаєтесь?


Чи слід включати EndInvokeпісля BeginInvoke?
Одіс

@odyodyodys: Коротка відповідь: Ні. Це магічний, надто конкретний випадок, коли вам не потрібно. Довша відповідь: Прочитайте коментарі до цієї відповіді: stackoverflow.com/a/714680/6932
Greg D

1
Ця відповідь та стаття MSDN стосуються InvokeRequired, що повертає false, оскільки Handle не створено. Але OP отримує виняток, коли Beginvoke / Invoke викликається після того, як InvokeRequired повертає true. Як InvokeRequired може повернути true, коли дескриптор ще не створений?
thewpfguy

Існує також умова перегони, одна з яких я вже пробіг, на wrt IsDisposed. IsDisposed може бути помилковим при тестуванні, але стати істинним до того, як надіслана дія буде повністю виконана. Здається, ці два варіанти (а) ігнорують InvalidOperationException та (б) використовують блокування для створення критичних розділів із поданої дії та методу управління розпорядженням. Перший відчуває себе як хак, а другий - біль.
blearyeye

37

Я знайшов InvokeRequiredненадійний, тому просто використовую

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

5
Чи не може це спричинити створення двох ручок у різних потоках? Дескриптор повинен бути створений, вам просто потрібно покращити свій час / порядок подій ..
Деніз Скідмор

Приємно - я віддаю перевагу цьому, а не просто отримувати доступ до цього. Зверніться до того, як (а) у вас немає невикористовуваної змінної, і (б) очевидно, що відбувається
Данк,

5
MSDN: "У випадку, коли дескриптор елемента управління ще не створений, не слід просто викликати властивості, методи або події на елементі управління. Це може призвести до того, що дескриптор елемента керування буде створений у фоновому потоці, ізолюючи елемент керування на потік без накачування повідомлень і робить додаток нестабільним. " Вся суть вправи полягає у тому, щоб уникнути створення ручки на неправильній нитці. Якщо цей дзвінок відбувається із потоку, який не є потоком gui, чуть - ти мертвий.
Greg D

25

Ось моя відповідь на подібне запитання :

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

var x = this.Handle; 

(Див. Http://ikriv.com/en/prog/info/dotnet/MysteriousHang.html )


Дуже цікава стаття до речі. Дякую.
Янн Тревін,

Дякую, це спрацювало для мене, тому що у мене була прихована форма, яку потрібно було анімувати у фоновому потоці та поза ним. Посилання на Handle мені
Джон Мак,

Це все ще є проблемою в останніх версіях .net, хоча це менше помилки, ніж "функція". Варто зазначити, що надягання "годинника" на об'єкт та перегляд його властивостей також робить те саме, що дивиться на ручку. Ви дивитесь на якісь квантові налагодження безглуздя, де це працює, коли ви дивитесь на це.
Тоні Чітем

5

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

Отже, ви отримаєте виняток, коли ваш метод викликається з потоку, відмінного від того, який створив елемент керування. Це може статися через видалення подій або робочих елементів, що стоять у черзі ...

РЕДАГУВАТИ

Якщо ви перевіряєте InvokeRequiredі HandleCreatedперед викликом викликати, ви не повинні отримувати цей виняток.


Якщо я правильно розумію, ви говорите, що це відбуватиметься щоразу, коли виклик потоку відрізняється від того, на якому створено елемент керування. Я не можу гарантувати, з якого потоку буде викликана подія. Це може бути той, хто його створив (схоже) - це зовсім інша нитка. Як я можу це вирішити?
Джордж Мауер,

так, це правильно. Я відредагував публікацію з умовою, яка повинна вирішити проблему.
Ши

Я не переконаний, що це так. Я оновив своє запитання на основі вашого коментаря, Arnshea.
Greg D

Я не розумію. Мені потрібно це вікно, щоб показати, мені незрозуміло, чому IsHandleCreated є хибним, але відсутність вікна не відображається, це не варіант, моє питання про те, чому у світі це було б помилковим
Джордж Мауер

Я вважаю, що IsHandleCreated поверне значення false, якщо ручка була закрита / елемент керування утилізований. Ви впевнені, що вас не кусає асинхронний виклик елемента керування, який існував раніше, але більше не?
Greg D

3

Якщо ви збираєтеся використовувати a Controlз іншого потоку перед тим, як показувати або робити інші дії з Control, розгляньте примусове створення його дескриптора в конструкторі. Це робиться за допомогоюCreateHandle функції.

У багатопотоковому проекті, де логіка "контролера" відсутня в WinForm, ця функція допомагає Controlконструкторам уникнути цієї помилки.


3

Додайте це перед тим, як викликати метод виклику:

while (!this.IsHandleCreated) 
   System.Threading.Thread.Sleep(100)

1

Зверніться до дескриптора пов'язаного елемента керування у його творці приблизно так:

Примітка . Будьте обережні з цим рішенням. Якщо елемент керування має дескриптор, набагато повільніше виконувати такі дії, як встановлення розміру та розташування. Це робить InitializeComponent набагато повільнішим. Краще рішення - нічого не фонувати, поки елемент управління не має дескриптора.


0

У мене була така проблема з такою простою формою:

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; });
    }
}

Я думаю, це працює нормально, поки ...


0
var that = this; // this is a form
(new Thread(()=> {

    var action= new Action(() => {
       something
    }));

    if(!that.IsDisposed)
    {
        if(that.IsHandleCreated)
        {
            //if (that.InvokeRequired)
                that.BeginInvoke(action);
            //else
            // action.Invoke();
        }
        else
            that.HandleCreated+=(sender,event) => {
                action.Invoke();
            };
    }


})).Start();

це c # - thisне змінюється залежно від виклику, що техніка стилю javascript повинна бути непотрібною.
Джордж Мауер,

впевнений, намагався чітко вказати, на що посилатися. - що завгодно
Шимон Дудкін

0

Як що до цього :


    public static bool SafeInvoke( this Control control, MethodInvoker method )
    {
        if( control != null && ! control.IsDisposed && control.IsHandleCreated && control.FindForm().IsHandleCreated )
        {
            if( control.InvokeRequired )
            {
                control.Invoke( method );
            }
            else
            {
                method();
            }
            return true;
        }
        else return false;
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.