WPF Main thread gets a deadlock when stylus input thread is waiting for the window to close
- Dominant language
- C#
- Stars
- 7.7k
- Forks
- 1.3k
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 61
Description
* .NET Core Version: 3.0 Preview1
* Windows version: (`Windows 10 all versions / 7 SP1`)
* Does the bug reproduce also in WPF for .NET Framework 4.8?: Yes
**Problem description:**
We found two way that can make the main thread locked. And we can not write any code to solve it and it can only be circumvented.
The easiest way to reproduce this issue is to wait for the window in the main thread to close in the stylus input thread.
**Actual behavior:**
## The first way
The first way is to write a custom class implementing `StylusPlugIn` and wait for a window to close in the `OnStylusUp` method.
Let's create a new empty window named `FooWindow`.
```csharp
public class FooWindow : Window
{
}
```
Then we create a `FooStylusPlugIn` class to implement the `StylusPlugIn` with overriding the `OnStylusUp` method. We add some code to wait for the window to close by calling `Invoke` which will wait by pumping a new message loop.
```csharp
public class FooStylusPlugIn : StylusPlugIn
{
public FooStylusPlugIn(FooWindow fooWindow)
{
FooWindow = fooWindow;
}
public FooWindow FooWindow { get; }
///
protected override void OnStylusUp(RawStylusInput rawStylusInput)
{
FooWindow.Dispatcher.Invoke(() => FooWindow.Close());
base.OnStylusUp(rawStylusInput);
}
}
```
To combine both the critical codes above, we write some codes in the `MainWindow`. The `FooWindow` is instanced in the constructor and the `StylusPlugIn` is plugged in it. We also make a button in the XAML that can let us know whether the main thread is still running or not.
```csharp
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
_fooWindow = new FooWindow();
StylusPlugIns.Add(new FooStylusPlugIn(_fooWindow));
_fooWindow.Show();
}
private void Button_OnClick(object sender, RoutedEventArgs e)
{
}
private FooWindow _fooWindow;
}
```
Run the project, touch the main window, and you'll find that the main window never responds to your interaction. Try to click the button to view the responding and you'll soon verify what I'm talking.
The reason is that the `OnStylusUp` in `FooStylusPlugIn` is running in the stylus input thread which is also running the inside loop of the `ThreadProc` method. It needs to go back to the outside loop to remove the `PenContext` when a window is closed. The stylus input thread is waiting for the main thread to close a window and the main thread is also waiting for the stylus input thread remove PenContext. Thus, the deadlock occurred.
The demo in [github](https://github.com/dotnet-campus/wpf-issues/tree/master/MainThreadDeadlockWithStylusInputThread/MainThreadDeadlockWhenTouchThreadWaitForWindowClosed)
## The second way
If a touch happens exactly during a window closing, the main thread will enter a lock.
The difference between the first method and the second method is that the first one will lock both the main thread and the stylus input thread but the second one will only lock the main thread.
From the theory, we know that the `PenContext` should be removed correctly in the outside loop. But in the second way, the stylus input thread is firing the touch event exactly when we run the code to remove the `PenContext` in the stylus input thread. As you can see we need to run the code to remove `PenContext` in the outside loop but at this moment the code is firing the touch event in the second loop.
The firing of the touch event means the `_pimcResetHandle` is released. Although the main thread has also released the lock the code cannot run to the outside loop to remove the `PenContext` and the main thread can no longer wait for the moment when the `PenContext` removal is finished.
```csharp
void ThreadProc()
{
while (!__disposed)
{
// The outside loop
// To remove or add the PenContext
// The main thread is waiting for its finishing.
RemovePenContext();
while (true)
{
// The inside loop
// Tt will be blocked by the PENIMC
if(!Penimc.UnsafeNativeMethods.GetPenEvent(/*wait the lock*/))
{
// If the `_pimcResetHandle` is released, this if branch will enter so the inside loop will end with the `break` and the code runs back to the outside loop.
break;
}
FireEvent(/*fire the touch events*/); // the code is running in this line
// and the `_pimcResetHandle` is released.
// the main thread release the `_pimcResetHandle` but the code can not go to RemovePenContext for it will no longer break.
}
}
}
```
The main thread has released the lock but the stylus input thread doesn't need to wait for the lock. The stylus input thread cannot go back to the outside loop to remove the `PenContext` and main thread can no longer wait for the moment when the `PenContext` removal is finished.
**Expected behavior:**
**Minimal repro:**
See https://github.com/dotnet-campus/wpf-issues/tree/master/MainThreadDeadlockWithStylusInputThread/MainThreadDeadlockWhenTouchThreadWaitForWindowClosed
Contributor guide
Assessment
This issue has not been assessed yet.