title author date CreateTime categories
WPF 在触摸线程等待主线程窗口关闭会让主线程和触摸线程相互等待
lindexi
2018-10-31 9:30:9 +0800
2018-10-30 14:43:17 +0800
WPF

本文是记录一个线程相互等待导致主线程无法响应的问题,这个问题是属于一定可以复现的问题,是 WPF 的已知问题。如果遇到这个问题,属于暂时没有方法解决,只能规避。
这个问题的最简单复现步骤是在触摸线程,也就是 StylusInput 线程,等待一个主线程的窗口关闭,此时就会出现主线程卡住的问题

这个问题有两个复现方法,第一个方法属于必现的方法,第二个方法属于概率的方法

在开始说明问题之前需要大概讲一下 WPF 的触摸原理和这个问题的原理

原理

在 WPF 触摸下,是存在 Stylus Input 线程用于处理触摸相关的事情,在这个线程会调用 ThreadProc 进入循环

这个线程会调用 ThreadProc 进入循环,直到软件退出

void ThreadProc()
{ }

在 ThreadProc 里面有两次循环,第一层循环是处理添加或移除 PenContext 等,第二层循环是进入 PENIMC 这个库卡住,直到释放线程锁 _pimcResetHandle 或用户触摸才继续

void ThreadProc()
{
while (!__disposed)
{
// 第一层循环
// 处理 PenContext 的添加或移除等的代码 while (true)
{
// 第二层循环,用于处理用户的触摸
if(!Penimc.UnsafeNativeMethods.GetPenEvent(/*等待 _pimcResetHandle 释放,或用户触摸*/))
{
// 如果是 _pimcResetHandle 被释放,则跳出第二层循环
break;
} FireEvent(/*触发触摸消息*/);
}
}
}

在窗口关闭的时候,需要调用 HwndSource.DisposeStylusInputProvider 关闭窗口的触摸,这时的调用堆栈是从消息到 PenContext.Disable 方法

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

先来看一下 PenThreadWorker.WorkerRemovePenContext 的代码

    internal bool WorkerRemovePenContext(PenContext penContext)
{ var operationRemoveContext = new PenThreadWorker.WorkerOperationRemoveContext(penContext, this); _workerOperation.Add((PenThreadWorker.WorkerOperation) operationRemoveContext);
// 释放 _pimcResetHandle 锁
UnsafeNativeMethods.RaiseResetEvent(this._pimcResetHandle.Value);
// 等待任务完成
operationRemoveContext.DoneEvent.WaitOne();
operationRemoveContext.DoneEvent.Close();
return operationRemoveContext.Result;
}

从上面的代码可以看到,主线程需要等待 WorkerOperationRemoveContext 运行完成,而 WorkerOperationRemoveContext 需要在 Stylus Input 线程运行

这就是关闭窗口可能出现的主线程卡住问题,只要主线程等待没有完成,主线程就会一直等待

方法一

添加一个 StylusPlugIn 同时在 StylusPlugIn 的 Up 方法等待一个窗口的关闭

在代码添加一个窗口类,这个窗口类是一个空白的窗口

    public class FooWindow : Window
{ }

然后创建一个类 FooStylusPlugIn 继承 StylusPlugIn 类,重写 OnStylusUp 方法,在这个方法等待传入的 FooWindow 关闭

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

在主窗口创建 FooWindow 和 FooStylusPlugIn 同时在前台放一个按钮,放一个按钮可以知道当前的主线程是否无法点击

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

这时运行代码触摸一下屏幕就会发现主窗口的按钮无法点击

因为在 FooStylusPlugIn 的 OnStylusUp 属于 Stylus Input 线程,执行的方法在 ThreadProc 的 FireEvent 里,而处理窗口关闭的时候需要调用 WorkerOperationRemoveContext 也需要在 Stylus Input 线程运行。

在主线程需要等待触摸线程运行移除 PenContext 代码,触摸线程需要等待主线程关闭窗口,这时两个线程就无响应

所有的代码在 github

方法二

在触摸触发的过程中,出现了窗口的关闭,会让主线程卡住

和方法一不同的是,方法一会让触摸线程和主线程同时卡住,方法二只会让主线程卡住

从原理上可以知道,窗口关闭需要移除 PenContext 需要在触摸线程的第一层循环运行。但是在触摸的过程,触摸线程运行到第二层循环里。

void ThreadProc()
{
while (!__disposed)
{
// 第一层循环
// 处理 PenContext 的添加或移除等的代码
// 主线程需要等待这里的代码运行完成
RemovePenContext(); while (true)
{
// 第二层循环,用于处理用户的触摸
if(!Penimc.UnsafeNativeMethods.GetPenEvent(/*等待 _pimcResetHandle 释放,或用户触摸*/))
{
// 如果是 _pimcResetHandle 被释放,则跳出第二层循环
break;
} FireEvent(/*触发触摸消息*/); // 当前触摸线程运行到这里
}
}
}

在没有出现触摸的时候,触摸线程会在 Penimc.UnsafeNativeMethods.GetPenEvent 卡住

主线程通过释放 _pimcResetHandle 锁运行 RemovePenContext 代码

触摸线程在运行到 FireEvent 不需要等待_pimcResetHandle就无法到第一层循环,主线程无法等到触摸线程移除 PenContext 主线程卡住

2018-10-31-WPF-在触摸线程等待主线程窗口关闭会让主线程和触摸线程相互等待的更多相关文章

  1. Unity进阶----AssetBundle_02(加载依赖关系及网络资源)(2018/10/31)

    网络资源加载: string path ="file://"+ Application.streamingAssetsPath + "\\windows\\123&quo ...

  2. [文章汇总]ASP.NET Core框架揭秘[最近更新:2018/10/31]

    之前一段时间都在个人公众号账号“大内老A”发布关于ASP.NET Core的系列文章,很多人留言希望能够同步到这里,所以在这里 对这些文章做一个汇总,以便于PC端阅读.如果说微软官方文档主要关于ASP ...

  3. 2018.10.31 NOIP模拟 几串字符(数位dp+组合数学)

    传送门 如果观察到性质其实也不是很难想. 然而考试的时候慌得一批只有心思写暴力233. 下面是几个很有用的性质: c0,1+1≥c1,0≥c0,1c_{0,1 }+1 ≥ c_{1,0} ≥ c_{0 ...

  4. it's time to change myself now (2018.10.31)

    自16年从新屋熊职校毕业,入职深圳某厂从事云存储两年半了.两年半的时间很快,快的感觉一生都会飞快,两年多一直很忙,忙的几乎忘了自己是否正向改变过. 正向改变,or 积极改变,今年十一回家,与几个好友小 ...

  5. 2018.10.31 NOIP训练 锻造(方程式期望入门题)(期望dp)

    传送门 根据题目列出方程: fi=pi∗(fi−1+fi−2)+(1−pi)∗(fi+1+fi)f_i=p_i*(f_{i-1}+f_{i-2})+(1-p_i)*(f_{i+1}+f_i)fi​=p ...

  6. 2018.10.31 bzoj4737: 组合数问题(lucas定理+容斥原理+数位dp)

    传送门 这是一道让我重新认识lucaslucaslucas的题. 考虑到lucaslucaslucas定理: (nm)≡(n%pm%p)∗(npmp)\binom n m \equiv \binom ...

  7. 2018.10.31 bzoj3339&&3585mex(主席树)

    传送门 双倍经验 直接上主席树,每个叶节点维护这个值出现的最右区间,非叶子节点维护当前值域内所有最右区间的最小值. 查询的时候只用在以root[qr]root[qr]root[qr]为根的树上面二分. ...

  8. 2018.10.31 vijos1052贾老二算算术(高斯消元)

    传送门 高斯消元模板题. 写的时候反了sbsbsb错误消元的时候除数和被除数反了. 所以把板子贴上来压压惊. 代码: #include<bits/stdc++.h> using names ...

  9. 2018.10.31 NOIP模拟 一些情报(倍增)

    传送门 题目并不难(想) 其实就是用倍增维护几个树上信息. 也就这么几个: 子树内最长链及其后继点. 子树内次长链及其后继点. 子树内第三场链(也就是dzyodzyodzyo口中鬼畜的次次长链) 点i ...

随机推荐

  1. 备份一下alias喽

    # 每次grep都显示出行号示出行号 alias grep="grep -n" # grep反向选择并显示行号显示行号 alias vgrep="grep -n -v&q ...

  2. pytest-参数化2

    import pytesttest_user_data=['linda','sai','tom']@pytest.fixture(scope='module')def login(request): ...

  3. 循序渐进学.Net Core Web Api开发系列【17】:.Net core自动作业之Hangfire

    nuget搜索:Hangfire 安装即可,这里我选择的是 1.7.0-beta1 版本 我是用这个集成到了 mvc api里 这里需要在 Startup 文件里进行如下配置 在配置方法 Config ...

  4. Codeforces New Year and Arbitrary Arrangement

    New Year and Arbitrary Arrangement time limit per test2 seconds You are given three integers k, pa a ...

  5. 隐式激活Activity

  6. HTML + CSS (下)【更新中】

    弹性盒子: 定义:弹性盒子模型是css3中新提出的一种布局方案.是一种为了应对针对不同屏幕宽度不同设备的一整套新的布局方案. 主要是对一个容器中的子元素进行排列.对齐和分配空白空间的方案的调整. 新旧 ...

  7. Stm32CubeMX5 配置使用ADC

    试验,通过ADC按键 按下时的不同电压,然后把相应的数字量通过串口发出, 1. 使用 ADC 检测按键, 原理图分析 2. Stm32CubeMX5配置 ADC功能,先配置PA0为ADC模式 配置 A ...

  8. 【LeetCode】Heap

    [215] Kth Largest Element in an Array [Medium] 给个无序数组,返回第K大的数字. 方法1. 直接使用优先队列 priority_queue class S ...

  9. org.springframework.web.servlet.DispatcherServlet异常

    org.springframework.web.servlet.DispatcherServlet: No mapping found for HTTP request with URI [/] in ...

  10. shell脚本每五分钟执行一次可执行程序(nohup)

    两种解决方案:个人推荐第二种,使用crontab来定时执行任务   1.shell代码如下: nohup command &. 解释: 后台永久运行command命令. (nohup表示后台永 ...