Что не так с моим многопоточным вызовом в Windows Forms? - PullRequest
9 голосов
/ 15 июня 2010

Возникла проблема с приложением Windows Forms.

Форма должна отображаться из другого потока.Итак, в классе формы у меня есть следующий код:

private delegate void DisplayDialogCallback();

public void DisplayDialog()
{
    if (this.InvokeRequired)
    {
        this.Invoke(new DisplayDialogCallback(DisplayDialog));
    }
    else
    {
        this.ShowDialog();
    }
}

Теперь, каждый раз, когда я запускаю это, на строку this.ShowDialog(); выдается InvalidOperationException:

«Операция с несколькими потоками недопустима: элемент управления 'SampleForm' доступен из потока, отличного от потока, в котором он был создан."

Что не так с этим фрагментом кода?Разве это не правильный способ совершать вызовы между потоками?Есть что-то особенное с ShowDialog()?

Ответы [ 7 ]

8 голосов
/ 15 июня 2010

Возможно, вы выполняете этот код до того, как форма будет показана.
Следовательно, InvokeRequired возвращает false.

5 голосов
/ 15 июня 2010

Я считаю, что здесь происходит то, что этот код запускается до того, как Form когда-либо появится.

Когда в .Net создается Form, он не сразу получает сходство для определенного потока. Только когда выполняются определенные операции, такие как показ или захват ручки, он приобретает сходство. До того, как это произойдет, InvokeRequired будет трудно функционировать правильно.

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

Способ исправить это - установить сходство с вашим элементом управления, когда он создается в потоке пользовательского интерфейса. Лучший способ сделать это - просто запросить у элемента управления его свойство handle.

var notUsed = control.Handle;
4 голосов
/ 15 июня 2010

Попробуйте это:

private delegate void DisplayDialogCallback();

public void DisplayDialog()
{
    if (this.InvokeRequired)
    {
        this.Invoke(new DisplayDialogCallback(DisplayDialog));
    }
    else
    {
        if (this.Handle != (IntPtr)0) // you can also use: this.IsHandleCreated
        {
            this.ShowDialog();

            if (this.CanFocus)
            {
                this.Focus();
            }
        }
        else
        {
            // Handle the error
        }
    }
}

Обратите внимание, что InvokeRequired возвращает

true, если дескриптор элемента управления был создан в потоке, отличном от вызывающего потока (указывая, чтовы должны делать вызовы к элементу управления через метод invoke);в противном случае - false.

и, следовательно, если элемент управления не был создан, возвращаемое значение будет false!

1 голос
/ 15 июня 2010

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

Вы можете добавить этот код до того, как ваш код, и все должно быть хорошо:

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

Редактировать: Есть еще одна проблема с вашим кодом.После отображения формы вы не можете снова вызвать ShowDialog ().Вы получите недопустимое исключение операции.Возможно, вы захотите изменить этот метод, как предложили другие.

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

0 голосов
/ 15 июня 2010

Я тоже считаю SLaks правильным.Из msdn (http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired.aspx):

Если подходящего дескриптора не найдено, метод InvokeRequired возвращает false.

Если это возможно в вашем случае, я бы попытался объединитьсоздание и отображение элемента управления в одном методе, а именно:

public DisplayDialog static Show()
{
  var result = new DisplayDialog; //possibly cache instance of the dialog if needed, but this could be tricky
  result.ShowDialog(); 
  return result;
}

, который можно вызвать из другого потока.

0 голосов
/ 15 июня 2010

Скорее всего, дескриптор элемента управления еще не создан, и в этом случае Control.InvokeRequired возвращает false.

Проверьте свойство Control.IsHandleCreated, чтобы убедиться, что это так.

0 голосов
/ 15 июня 2010

Вы всегда можете попробовать протестировать другой элемент управления.

Например, вы можете получить доступ к коллекциям Application.Forms

public Control GetControlToInvokeAgainst()
{
    if(Application.Forms.Count > 0)
    {
        return Application.Forms[0];
    }
    return null;
}

Затем в вашем методе DisplayDialog () вызовите GetControlToInvokeAgainst () и проверьте на нулевое значение, прежде чем пытаться выполнить вызов invokerequired.

...