首页 技术 正文
技术 2022年11月7日
0 收藏 875 点赞 1,114 浏览 3604 个字
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 主线程卡住

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,489
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,904
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,737
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,490
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:8,128
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:5,290