Reputation: 905
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
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