Невозможно (как я ненавижу использовать это слово) ошибка перекрестного потока? - PullRequest
6 голосов
/ 09 февраля 2009

Кто-нибудь может объяснить, как это могло произойти?

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

alt text

Во-первых, если InvokeRequired = true, это должно означать выполнение BeginInvoke (), метод [помещается в очередь] для повторного вызова и InvokeRequired должен быть равен false?

Я не должен получать это исключение, так как оно должно обслуживаться?

Надеюсь услышать от некоторых многопоточных гуру :)

Graham

Ответы [ 5 ]

9 голосов
/ 09 февраля 2009

Я подозреваю, что InvokedRevoked может лгать вам. Элемент управления WinForm откладывает создание базового элемента управления Win32 HWND до тех пор, пока вызов метода не понадобится. InvokeRequired вернет false, если HWND еще не был создан.

Для подробного объяснения см .: "Таинственный Зависание или Великий Обман InvokeRequired"

Если ваш фоновый поток запрашивает InvokeRequired до , поток пользовательского интерфейса заставил Control лениво создать свой HWND, InvokeRequired (неправильно) сообщит вашему фоновому потоку, что ему не нужно использовать Invoke () для передачи контроль обратно в поток пользовательского интерфейса. Когда фоновый поток получает доступ к Control, BAM! «InvalidOperationException: операция с несколькими потоками недопустима!»

Поток пользовательского интерфейса может вручную заставить Control создать свой дескриптор HWND, чтобы Control.InvokeRequired знал, что поток пользовательского интерфейса является владельцем элемента управления:

Control control = new Control();
IntPtr handle = control.Handle; // if you ask for HWND, it will be created
5 голосов
/ 09 февраля 2009

Большинство людей видят эту ошибку и видят одну вещь: «вы не обращаетесь к этому элементу управления из основного потока пользовательского интерфейса». В действительности вы можете иметь 100 потоков пользовательского интерфейса, если хотите (поведение для этого не определено, но поддерживается). Скорее всего, PanelMain создается в другом потоке (this); Я не вижу код - но похоже, что вы создаете его в своем рабочем / потоке.

Чтобы подтвердить поведение, попробуйте это:

Action addAction = new Action (
   new delegate { panelMain.Controls.Add(UCLoadingScreen); } )
if(panelMain.InvokeRequired)
{
   panelMain.Invoke(addAction); // Breakpoint here.
}
else
{
   addAction();
}

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

Фабрика исправит это однако:

public void CreateControl<T>() where T : new()
{
    if(InvokeRequired)
    {
        return (T)Invoke(new Func<T>(CreateControl<T>()));
    }
    return new T();
}

РЕДАКТИРОВАТЬ: panelMain может не быть нарушителем потока, как я уже говорил, родительские элементы управления из разных потоков приводят к крайне неопределенному поведению. Убедитесь, что все ваши элементы управления созданы в контексте потока вашей основной формы.

0 голосов
/ 20 апреля 2009

Я надеюсь, что это работает для вас. Я полагаю, что ваш графический интерфейс на одной нити. Просто инициализируйте этот синглтон и полагайтесь на него всякий раз, когда вам захочется вызвать свойство Control.InvokeRequired.

Приветствия

* 1005 Грег *

public sealed class UiThread
{
  #region Singleton
  // Singleton pattern implementation taken from Jon Skeet's C# and .NET article www.yoda.arachsys.com/csharp/singleton.html

  UiThread() { }

  public static UiThread Instance { get { return Nested.instance; } }

  class Nested
  {
     // Explicit static constructor to tell C# compiler
     // not to mark type as beforefieldinit
     static Nested() { }

     internal static readonly UiThread instance = new UiThread();
  }
  #endregion

  int _uiThreadId = 0;

  public void SetUiThread(Thread thread)
  {
     if (_uiThreadId != 0)
        throw new ApplicationException("UI Thread has already been established!");

     if (thread.ManagedThreadId == 0)
        throw new ArgumentException("Unexpected thread id value of 0!");

     if (thread.IsBackground)
        throw new ArgumentException("Supplied thread should not be a background thread!");

     if (thread.IsThreadPoolThread)
        throw new ArgumentException("Supplied thread should not be a thread pool thread!");

     _uiThreadId = thread.ManagedThreadId;
  }

  /// <summary>
  /// It's possible for InvokeRequired to return false when running in background thread.
  /// This happens when unmanaged control handle has not yet been created.
  /// We second-guess Microsoft's implementation in this case, checking against foreground thread's Id.
  /// </summary>
  /// <param name="control">Control to check against.</param>
  public bool InvokeRequired(Control control)
  {
     if (control.InvokeRequired)
        return true;

     IntPtr unmanagedHandle = control.Handle;
     bool newResult = control.InvokeRequired;

     if (unmanagedHandle == IntPtr.Zero)
     {
        // Trace.WriteLine() call here forces unmanagedHandle's initialization, 
        //  even with optimizing compiler.
        Trace.WriteLine(string.Format("Control handle could not be established! Control was {0}.", control.ToString()));
     }

     bool retVal = InvokeRequired();

     // Break whenever the result of our check does not match theirs.
     Debug.Assert(retVal == newResult);

     // Return our InvokeRequired result over theirs 
     //   to keep with the tradition of updating controls from foreground only.
     return retVal;
  }

  /// <summary>
  /// Prefer to use overload with Control argument if at all possible.
  /// It's possible for InvokeRequired to return false when running in background thread.
  /// This happens when unmanaged control handle has not yet been created.
  /// We second-guess Microsoft's implementation in this case, checking against foreground thread's Id.
  /// </summary>
  public bool InvokeRequired()
  {
     if (_uiThreadId == 0)
        throw new ApplicationException("UI Thread has not been established!");

     return (Thread.CurrentThread.ManagedThreadId != _uiThreadId);
  }

}

0 голосов
/ 09 февраля 2009

Нет такой вещи как невозможная ошибка перекрестного потока!

0 голосов
/ 09 февраля 2009

Использование

if (InvokeRequired)
{
  //invoke
}
else
{
  //do
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...