IEnumerable против IList и странное CrossThreadMessagingException во время отладки - PullRequest
1 голос
/ 01 сентября 2011

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

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

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

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


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

Сообщение: возникла исключительная ситуация 'Microsoft.VisualStudio.Debugger.Runtime.CrossThreadMessagingException'

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

StackTrace:

в Microsoft.VisualStudio.Debugger.Runtime.Main.ThrowCrossThreadMessageException (String formatString)

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

в System.Diagnostics.Process.get_MainWindowTitle ()

1 Ответ

3 голосов
/ 01 сентября 2011

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

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

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

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

При тестировании вы можете фактически перечислять другие экземпляры 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.

Это все слепое предположение, если честно!

...