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 进入循环,直到软件退出

  1. void ThreadProc()
  2. {
  3.  
  4. }

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

  1. void ThreadProc()
  2. {
  3. while (!__disposed)
  4. {
  5. // 第一层循环
  6. // 处理 PenContext 的添加或移除等的代码
  7.  
  8. while (true)
  9. {
  10. // 第二层循环,用于处理用户的触摸
  11. if(!Penimc.UnsafeNativeMethods.GetPenEvent(/*等待 _pimcResetHandle 释放,或用户触摸*/))
  12. {
  13. // 如果是 _pimcResetHandle 被释放,则跳出第二层循环
  14. break;
  15. }
  16.  
  17. FireEvent(/*触发触摸消息*/);
  18. }
  19. }
  20. }

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

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

先来看一下 PenThreadWorker.WorkerRemovePenContext 的代码

  1. internal bool WorkerRemovePenContext(PenContext penContext)
  2. {
  3.  
  4. var operationRemoveContext = new PenThreadWorker.WorkerOperationRemoveContext(penContext, this);
  5.  
  6. _workerOperation.Add((PenThreadWorker.WorkerOperation) operationRemoveContext);
  7. // 释放 _pimcResetHandle 锁
  8. UnsafeNativeMethods.RaiseResetEvent(this._pimcResetHandle.Value);
  9. // 等待任务完成
  10. operationRemoveContext.DoneEvent.WaitOne();
  11. operationRemoveContext.DoneEvent.Close();
  12. return operationRemoveContext.Result;
  13. }

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

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

方法一

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

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

  1. public class FooWindow : Window
  2. {
  3.  
  4. }

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

  1. public class FooStylusPlugIn : StylusPlugIn
  2. {
  3. public FooStylusPlugIn(FooWindow fooWindow)
  4. {
  5. FooWindow = fooWindow;
  6. }
  7.  
  8. public FooWindow FooWindow { get; }
  9.  
  10. /// <inheritdoc />
  11. protected override void OnStylusUp(RawStylusInput rawStylusInput)
  12. {
  13. FooWindow.Dispatcher.Invoke(() => FooWindow.Close());
  14. base.OnStylusUp(rawStylusInput);
  15. }
  16. }

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

  1. public partial class MainWindow : Window
  2. {
  3. public MainWindow()
  4. {
  5. InitializeComponent();
  6. _fooWindow = new FooWindow();
  7. StylusPlugIns.Add(new FooStylusPlugIn(_fooWindow));
  8. _fooWindow.Show();
  9. }
  10.  
  11. private void Button_OnClick(object sender, RoutedEventArgs e)
  12. {
  13. }
  14.  
  15. private FooWindow _fooWindow;
  16. }

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

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

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

所有的代码在 github

方法二

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

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

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

  1. void ThreadProc()
  2. {
  3. while (!__disposed)
  4. {
  5. // 第一层循环
  6. // 处理 PenContext 的添加或移除等的代码
  7. // 主线程需要等待这里的代码运行完成
  8. RemovePenContext();
  9.  
  10. while (true)
  11. {
  12. // 第二层循环,用于处理用户的触摸
  13. if(!Penimc.UnsafeNativeMethods.GetPenEvent(/*等待 _pimcResetHandle 释放,或用户触摸*/))
  14. {
  15. // 如果是 _pimcResetHandle 被释放,则跳出第二层循环
  16. break;
  17. }
  18.  
  19. FireEvent(/*触发触摸消息*/); // 当前触摸线程运行到这里
  20. }
  21. }
  22. }

在没有出现触摸的时候,触摸线程会在 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. 关于pandas

    axis轴,axis=0为逐行,axis=1为逐列

  2. 数据结构与算法简记--redis有序集合实现-跳跃表

    跳表 定义 为一个值有序的链表建立多级索引,比如每2个节点提取一个节点到上一级,我们把抽出来的那一级叫做索引或索引层.如下图所示,其中down表示down指针,指向下一级节点.以此类推,对于节点数为n ...

  3. uvloop官网翻译

    魔术堆栈 uvloop:快速的Python网络连接 作者Yury Selivanov @ 1st1 2016年5月3日 TL; DR asyncio是Python标准库附带的异步I / O框架.在此博 ...

  4. TreeMap源码解析笔记

    常见的数据结构有数组.链表,还有一种结构也很常见,那就是树.前面介绍的集合类有基于数组的ArrayList,有基于链表的LinkedList,还有链表和数组结合的HashMap,今天介绍基于树的Tre ...

  5. Codesforces 485D Maximum Value

                                                      D. Maximum Value   You are given a sequence a cons ...

  6. application/json和application/x-www-form-urlencoded参数接收

    application/json ajax请求中content-type:application/json代表参数以json字符串传递给后台,controller接收需要@RequestBody 接收 ...

  7. PHP 工厂模式浅析

    //抽象出一个人的接口interface Person{ public function showInfo();}//继承于人的学生类class Student implements Person{ ...

  8. soapui基础知识

    一.基础知识 1.测试步骤(teststep)为最小单位,一个完整的测试用例由多个测试步骤组成: 2.一个测试用例(testcase)代表一个完整的操作 3.测试集(testsuite)主要是为了区分 ...

  9. sql中char,varchar,nvarchar的区别

    char[n] 是定长的,也就是当存储字符小于n时,他会自动补齐(补空值).优点:效率较varchar高. varchar[n]是变长且非unicode字符数据类型,n的取值在1到8000之间,该类型 ...

  10. 使用ReadStream方法读取文件事件传递过程

    const fs = require('fs'); let file = fs.createReadStream("filename.js"); file.on("ope ...