Moss
Moss

Reputation: 905

IEnumerable vs IList and weird CrossThreadMessagingException during debugging

The initial code is this:

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

During debugging, if I try to call Count() on processes in the immediate window pane or inspect the "Results View" in the locals pane, I get a CrossThreadMessagingException. If I don't debug but just run the code, everything is fine. It is also fine if I convert the collection to a list before assigning it to processes and use the Count property during debugging.

What exactly is a CrossThreadMessagingException and why is the IEnumerable approach causing such an exception?


edit: Providing a bit more information about the exception.

Message: An exception 'Microsoft.VisualStudio.Debugger.Runtime.CrossThreadMessagingException' occurred

Source: Microsoft.VisualStudio.Debugger.Runtime

StackTrace:

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

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

at System.Diagnostics.Process.get_MainWindowTitle()

Upvotes: 0

Views: 547

Answers (1)

Matthew Abbott
Matthew Abbott

Reputation: 61589

This might totally be wrong, but I gather it's a mixture of deferred enumeration with WhereArrayIterator, and the debugger attempting to enumerate it?

I get the feeling, where the immediate window is trying to enumerate your result, it is doing so on another thread (which is causing the CrossThreadMessagingException).

It's not doing it when you call ToList, because ToList causes the enumeration to run immediately and concatenate the results in the list. This is done before you attempt to use the Count method in the immediate window.

When you use Count() without the ToList call, it forces the WhereArrayIterator (which is the return value of your Where method call) to enumerate, which is then trying to access the your lamda delegate from another thread.

On testing, you can actually enumerate other instances of WhereArrayIterator through immediate, so I think this is your particular use case, where you are trying to enumerate over the Process type, which I think internally makes calls using the Win32 API.

Internally, the Process.MainWindowTitle property uses lazy loading for its value. It doesn't actually make the call to grab the information, until the property is accessed for the first time (and also, it does so without locking, so if there where multiple threads accessing that area of the code, it is not atomic, so there is an inherit risk of race conditions - It shouldn't matter to much anyway, as it is a read only property, who's value should always be the same).

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

When it is first accessed, the property makes a Win32 call to grab the window text. I believe this is where it seems to be falling over. But it only seems to fall over when using deferred enumeration with your WhereArrayIterator instance.

This is all a blind guess to be honest!

Upvotes: 3

Related Questions