Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cross thread Sync context stealing #276

Open
TomKuhn opened this issue Jun 7, 2023 · 2 comments
Open

Cross thread Sync context stealing #276

TomKuhn opened this issue Jun 7, 2023 · 2 comments

Comments

@TomKuhn
Copy link

TomKuhn commented Jun 7, 2023

I'm getting some crazy UI thread deadlocks after using AsyncEx.Context. WinForms app.

Here's the UI thread, doing UI things:
Application.Run(form);
Here's the sync context on this thread:
System.Windows.Forms.WindowsFormsSynchronizationContext, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 as expected.

However check this crazy callstack out:

System.dll!System.Collections.Concurrent.BlockingCollection<System.__Canon>.CheckDisposed() Line 1808 C#
System.dll!System.Collections.Concurrent.BlockingCollection<System.Tuple<System.Threading.Tasks.Task, bool>>.TryAddWithNoTimeValidation(System.Tuple<System.Threading.Tasks.Task, bool> item, int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) Line 414 C#
Nito.AsyncEx.Context.dll!Nito.AsyncEx.AsyncContext.TaskQueue.TryAdd(System.Threading.Tasks.Task item, bool propagateExceptions) Line 57 C#
Nito.AsyncEx.Context.dll!Nito.AsyncEx.AsyncContext.Enqueue(System.Threading.Tasks.Task task, bool propagateExceptions) Line 91 C#
mscorlib.dll!System.Threading.Tasks.Task.ScheduleAndStart(bool needsProtection) Line 1946 C#
mscorlib.dll!System.Threading.Tasks.Task.InternalStartNew(System.Threading.Tasks.Task creatingTask, System.Delegate action, object state, System.Threading.CancellationToken cancellationToken, System.Threading.Tasks.TaskScheduler scheduler, System.Threading.Tasks.TaskCreationOptions options, System.Threading.Tasks.InternalTaskOptions internalOptions, ref System.Threading.StackCrawlMark stackMark) Line 1294 C#
mscorlib.dll!System.Threading.Tasks.TaskFactory.StartNew(System.Action action, System.Threading.CancellationToken cancellationToken, System.Threading.Tasks.TaskCreationOptions creationOptions, System.Threading.Tasks.TaskScheduler scheduler) Line 401 C#
Nito.AsyncEx.Tasks.dll!Nito.AsyncEx.TaskFactoryExtensions.Run(System.Threading.Tasks.TaskFactory this, System.Action action) Unknown
Nito.AsyncEx.Context.dll!Nito.AsyncEx.AsyncContext.AsyncContextSynchronizationContext.Send(System.Threading.SendOrPostCallback d, object state) Line 62 C#
System.dll!Microsoft.Win32.SystemEvents.SystemEventInvokeInfo.Invoke(bool checkFinalization, object[] args) Line 1634 C#
System.dll!Microsoft.Win32.SystemEvents.RaiseEvent(bool checkFinalization, object key, object[] args) Line 1314 C#
System.dll!Microsoft.Win32.SystemEvents.OnUserPreferenceChanged(int msg, System.IntPtr wParam, System.IntPtr lParam) Line 1006 C#
System.dll!Microsoft.Win32.SystemEvents**.WindowProc**(System.IntPtr hWnd, int msg, System.IntPtr wParam, System.IntPtr lParam) Line 1491 C#

The UI thread has somehow issued a post/send to an AsyncEx context which I use on other worker threads, but NEVER the UI thread! That Sync Context getting posted to has long been disposed, because this callstack ends in an exception thrown by the _queue in TaskQueue.cs:

System.ObjectDisposedException
HResult=0x80131622
Message=The collection has been disposed.
Object name: 'BlockingCollection'.
Source=System
StackTrace:
at System.Collections.Concurrent.BlockingCollection`1.CheckDisposed() in f:\dd\NDP\fx\src\sys\system\collections\concurrent\BlockingCollection.cs:line 1810

It's very similar to this issue:
https://ikriv.com/dev/dotnet/MysteriousHang#WhatToDo
Which is also related to OnUserPreferenceChanged.

What I think is happening is that somehow a Control handle is being accidentally created on a non-UI thread, in which the Sync Context is AsyncEx's context. The control is auto-registering itself with SystemEvents to be notified when OnUserPreferenceChanged is raised. This registration also involves the current Sync Context, so it can post to it later.

The question is: How can I detect an erroneous handle creation on the non UI thread!

@TomKuhn
Copy link
Author

TomKuhn commented Jun 7, 2023

This isn't some old version of framework, it's v4.7.2

@TomKuhn
Copy link
Author

TomKuhn commented Jun 7, 2023

I think I've figured it out, was me being an idiot. I was on the UI thread doing this unintentionally

AsyncContext.Run(() =>
{
// Create UI control here!
});

This meant that when some UI controls registered for OnUserPreferenceChanged callbacks, they passed the AsyncContext to it, saying "call me back on this context" - which was long since disposed. I've fixed my code so that I don't use AsyncContext.Run if I'm on the UI thread (doh)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant