title author date CreateTime categories
WPF Main thread gets a deadlock when stylus input thread is waiting for the window to close
lindexi
2018-11-01 09:32:42 +0800
2018-10-30 16:57:37 +0800
WPF

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.

We have found two ways, the first way always happens, and the second way is probabilistic.

Before we tell you about it, we need to tell you something about the touch thread and why it can make the main thread wait forever.

Theory

The stylus input thread gets the input event when the user touches the screen.

There is a ThreadProc method running in the stylus input thread and this method has a loop inside which will never end until the application exists.

void ThreadProc()
{
// The loop that never ends.
while (!__disposed)
{ }
}

There are nested two loops in the ThreadProc method. In the outside one, it adds and removes PenContext and in the inside one, it will be blocked by the PENIMC and can be continued when the user touches the screen.

void ThreadProc()
{
while (!__disposed)
{
// The outside loop
// To remove or add the PenContext while (true)
{
// The inside loop
// Tt will be blocked by the PENIMC
if(!Penimc.UnsafeNativeMethods.GetPenEvent(/*the thread locker*/))
{
// 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*/);
}
}
}

When a window is closed, it calls HwndSource.DisposeStylusInputProvider and this causes the PenContext.Disable be calling with the calling stack trace showing below.

System.Windows.Input.PenThreadWorker.WorkerRemovePenContext(System.Windows.Input.PenContext penContext)
System.Windows.Input.PenContext.Disable(bool shutdownWorkerThread)
System.Windows.Input.PenContexts.Disable(bool shutdownWorkerThread)
System.Windows.Input.StylusWisp.WispLogic.UnRegisterHwndForInput(System.Windows.Interop.HwndSource hwndSource)
System.Windows.Interop.HwndStylusInputProvider.Dispose()

Let us see the PenThreadWorker.WorkerRemovePenContext that run in the main thread.

internal bool WorkerRemovePenContext(PenContext penContext)
{
var operationRemoveContext = new PenThreadWorker.WorkerOperationRemoveContext(penContext, this); _workerOperation.Add((PenThreadWorker.WorkerOperation) operationRemoveContext);
// Release the _pimcResetHandle lock
UnsafeNativeMethods.RaiseResetEvent(this._pimcResetHandle.Value);
// Wait for the operationRemoveContext to finish
operationRemoveContext.DoneEvent.WaitOne();
operationRemoveContext.DoneEvent.Close();
return operationRemoveContext.Result;
}

From the code above we can learn that the main thread releases the _pimcResetHandle and it makes the ThreadProc breaking the inside loop and go back to the outside one to remove the PenContext.

Normally we should run the code in the stylus input thread to remove the PenContext and keep the main thread waiting for the operationRemoveContext to finish. But if the stylus input thread never remove the PenContext and the main thread waits for it, the main thread will never continue.

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.

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.

public class FooStylusPlugIn : StylusPlugIn
{
public FooStylusPlugIn(FooWindow fooWindow)
{
FooWindow = fooWindow;
} public FooWindow FooWindow { get; } /// <inheritdoc />
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.

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

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.

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.

Thanks to walterlv for proofreading the English translation of this post.

感谢 吕毅 对本文的英文翻译进行校对。

2018-11-1-WPF-Main-thread-gets-a-deadlock-when-stylus-input-thread-is-waiting-for-the-window-to-clos...的更多相关文章

  1. OI生涯回忆录 2018.11.12~2019.4.15

    上一篇:OI生涯回忆录 2017.9.10~2018.11.11 一次逆风而行的成功,是什么都无法代替的 ………… 历经艰难 我还在走着 一 NOIP之后,全机房开始了省选知识的自学. 动态DP,LC ...

  2. China Intelligent Office Summit(2018.11.21)

    时间:2018.11.21地点:中关村软件园国际会议中心

  3. International Programming Retreat Day(2018.11.17)

    时间:2018.11.17地点:北京国华投资大厦

  4. Intel Artificial Intelligence Conference(2018.11.14)

    时间:2018.11.14地点:北京国贸大酒店

  5. Debian系统中当安装deb软件时出现:deb cdrom:[Debian GNU/Linux 9.3.0 _Stretch_ - Official amd64 DVD Binary-1 20171209-12:11]/ stretch contrib main

    vi /etc/apt/sources.list // 注释掉下面这句话# deb cdrom:[Debian GNU/Linux 9.3.0 _Stretch_ - Official amd64 D ...

  6. 2018.11.23 浪在ACM 集训队第六次测试赛

    2018.11.23 浪在ACM 集训队第六次测试赛 整理人:刘文胜 div 2: A: Jam的计数法 参考博客:[1] 万众 B:数列 参考博客: [1] C:摆花 参考博客: [1] D:文化之 ...

  7. 2018.11.16 浪在ACM 集训队第五次测试赛

    2018.11.16 浪在ACM 集训队第五次测试赛 整理人:李继朋 Problem A : 参考博客:[1]朱远迪 Problem B : 参考博客: Problem C : 参考博客:[1]马鸿儒 ...

  8. 2018.11.9浪在ACM集训队第四次测试赛

    2018.11.9浪在ACM集训队第四次测试赛 整理人:朱远迪 A 生活大爆炸版 石头剪刀布           参考博客:[1] 刘凯 B 联合权值            参考博客: [1]田玉康 ...

  9. 2018.11.2浪在ACM集训队第三次测试赛

    2018.11.2 浪在ACM 集训队第三次测试赛 整理人:孔晓霞 A 珠心算测试 参考博客:[1]李继朋  B 比例简化 参考博客: [1]李继朋 C 螺旋矩阵 参考博客:[1]朱远迪 D 子矩阵 ...

  10. Thread.Sleep(0) vs Sleep(1) vs Thread.Yeild()

    注:本文为个人学习摘录,原文地址:http://www.cnblogs.com/stg609/p/3857242.html 本文将要提到的线程及其相关内容,均是指 Windows 操作系统中的线程,不 ...

随机推荐

  1. python下使用ElasticSearch

    一 什么是 ElasticSearch Elasticsearch 是一个分布式可扩展的实时搜索和分析引擎,一个建立在全文搜索引擎 Apache Lucene(TM) 基础上的搜索引擎.当然 Elas ...

  2. 莫烦PyTorch学习笔记(三)——激励函数

    1. sigmod函数 函数公式和图表如下图     在sigmod函数中我们可以看到,其输出是在(0,1)这个开区间内,这点很有意思,可以联想到概率,但是严格意义上讲,不要当成概率.sigmod函数 ...

  3. webpack静态资源拷贝插件

    处理不需要使用webpack统一打包处理或webpack不支持的文件 安装 npm install copy-webpack-plugin --save-dev 配置 const copyWebpac ...

  4. XJOI夏令营501训练1——分配工作

    传送门:QAQQAQ 题意:某公司有工作人员x1,x2,…,xn ,他们去做工作y1,y2,…,ym(n<=m) ,每个人都能做其中的几项工作,并且对每一项工作都有一个固定的效率.问能否找到一种 ...

  5. 一次读懂mybatis中的缓存机制

    缓存功能针对于查询(没听说果UPDATE,INSERT语句要缓存什么,都是直接执行的) 默认情况下,mybatis会启用一级缓存. 如果使用同一个session对象调用了相同的SELECT语句,则直接 ...

  6. iOS 更新日志 - 持续更新中

    本文只是为了简单记录一下每个正式版本发布时间和更新内容,只有这个初衷,从2019年9月25日开始,将会持续更新. iOS 13.1 - 2019年9月25日 iOS 13.1 iOS 13.1 包括错 ...

  7. C语言处理字符串及内存操作

    字符串处理函数 1.字符串长度 strlen表示包含的字符的个数,size_t strlen(char cosnt *string), 返回的是size_t类型,它是无符号整数类型,在表达式中进行运算 ...

  8. c语言一个例子对比php学习随记

    今天开始学写一些简单的C例子,发现很多和PHP不一样的地方,做个笔记. #include <stdio.h> int main(void) { int a,b; printf(" ...

  9. windows下 将tomcat做成服务,并于oracle后启动

    一.将tomcat做成服务 1.下载解压版的tomcat 6.*, 设置java.tomcat的环境(这个就不说了). 2.运行->cmd->到tomcat安装目录的bin目录: 3.运行 ...

  10. Java虚拟机原理图解-- 1.1、class文件基本组织结构 [转]

    作为Java程序猿,我们知道,我们写好的.java 源代码,最后会被Java编译器编译成后缀为.class的文件,该类型的文件是由字节组成的文件,又叫字节码文件.那么,class字节码文件里面到底是有 ...