IEnumerable против IList и странное исключение CrossThreadMessagingException во время отладки

Исходный код такой:

var processes = Process.GetProcesses().Where(p => p.MainWindowTitle.ToUpperInvariant().Contains("FOO"));

Во время отладки, если я попытаюсь вызвать Count() на processes в непосредственной области окна или проверю «Просмотр результатов» на панели локальных пользователей, я получу ошибку CrossThreadMessagingException. Если я не отлаживаю, а просто запускаю код, все в порядке. Также хорошо, если я преобразовываю коллекцию в список перед назначением ее processes и использую свойство Count во время отладки.

Что такое CrossThreadMessagingException и почему подход IEnumerable вызывает такое исключение?


изменить: Предоставление немного больше информации об исключении.

Сообщение: Произошло исключение «Microsoft.VisualStudio.Debugger.Runtime.CrossThreadMessagingException».

Источник: Microsoft.VisualStudio.Debugger.Runtime.

Трассировки стека:

в Microsoft.VisualStudio.Debugger.Runtime.Main.ThrowCrossThreadMessageException (формат строки)

в Microsoft.Win32.NativeMethods.GetWindowTextLength(HandleRef hWnd)

в System.Diagnostics.Process.get_MainWindowTitle()


person Moss    schedule 01.09.2011    source источник
comment
Что именно говорит CrossThreadMessagingException?   -  person Jon Skeet    schedule 01.09.2011
comment
Хм, необычно. Отладчик перехватывает собственный вызов Windows. Какая версия Visual Studio делает это? Какой проект? 64-битная операционная система? Удаленный отладчик?   -  person Hans Passant    schedule 01.09.2011


Ответы (1)


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

У меня такое ощущение, что непосредственное окно пытается перечислить ваш результат, оно делает это в другом потоке (что вызывает CrossThreadMessagingException).

Этого не происходит, когда вы вызываете ToList, потому что ToList вызывает немедленное выполнение перечисления и объединение результатов в списке. Это делается до того, как вы попытаетесь использовать метод Count в непосредственном окне.

Когда вы используете Count() без вызова ToList, он заставляет WhereArrayIterator (которое является возвращаемым значением вашего вызова метода Where) перечислять, что затем пытается получить доступ к вашему лямда-делегату из другого потока.

При тестировании вы можете фактически перечислить другие экземпляры WhereArrayIterator через немедленный, поэтому я думаю, что это ваш конкретный вариант использования, когда вы пытаетесь перечислить тип Process, который, как я думаю, выполняет внутренние вызовы с использованием Win32 API.

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

[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[MonitoringDescription("ProcessMainWindowTitle")]
public string MainWindowTitle
{
  get
  {
    if (this.mainWindowTitle == null)
    {
      IntPtr mainWindowHandle = this.MainWindowHandle;
      if (mainWindowHandle == (IntPtr) 0)
      {
        this.mainWindowTitle = string.Empty;
      }
      else
      {
        StringBuilder lpString = new StringBuilder(Microsoft.Win32.NativeMethods.GetWindowTextLength(new HandleRef((object) this, mainWindowHandle)) * 2);
        Microsoft.Win32.NativeMethods.GetWindowText(new HandleRef((object) this, mainWindowHandle), lpString, lpString.Capacity);
        this.mainWindowTitle = ((object) lpString).ToString();
      }
    }
    return this.mainWindowTitle;
  }
}

При первом доступе к свойству выполняется вызов Win32 для захвата текста окна. Я считаю, что это то место, где он, кажется, падает. Но кажется, что это происходит только при использовании отложенного перечисления с вашим экземпляром WhereArrayIterator.

Это все слепые догадки, если честно!

person Matthew Abbott    schedule 01.09.2011
comment
Спасибо за подробную информацию. - person Moss; 11.09.2011