如何在WPF中停止线程

如何在WPF中停止线程,第1张

1. Abort的非及时性

使用多线程经常会遇到一个问题,如何停止这个Thread?在WPF中提供了Abort方法,但MSDN却告诉我们:

线程不一定会立即中止,或者根本不中止。 如果线程在作为中止过程的一部分被调用的 finally 块中做非常大量的计算,从而无限期延迟中止 *** 作,则会发生这种情况。

先看下面一段代码:

public partial class MainWindow : Window

{

Thread thread

public MainWindow()

{

InitializeComponent()

Test t = new Test()

thread = new Thread(new ThreadStart(t.Run))

thread.Start()

}

private void Button_Click(object sender, RoutedEventArgs e)

{

Console.WriteLine("abort")

thread.Abort()

}

}

public class Test

{

public void Run()

{

while (true)

{

Console.WriteLine("Running...")

}

}

}

在运行中点击Button,则可能得到的输出如下:

图1

可以看到Abort后线程仍在运行,然后才停止,输出的结果是多变的,有可宏桥能Abort之后再无输出,也有可能输出多行,但都能说明一个问题: 使用abort不能立即终止一个thread.

那么到这里,需要先认识一下究竟Abort是什么?

简单来说,Abort是一个标识或者信号,调用它会引发ThreadAbortException,Abort会去请求终止thread,但这不是立即的。

改动一下代码再看看结果:

private void Button_Click(object sender, RoutedEventArgs e)

{

Console.WriteLine("abort")

thread.Abort()

Console.WriteLine(thread.IsAlive)

Console.WriteLine(thread.ThreadState)

}

private void Button_Click_1(object sender, RoutedEventArgs e)

{

Console.WriteLine(thread.IsAlive)

Console.WriteLine(thread.ThreadState)

}

图2

点击Button执行Abort后立即输出thread的状态,发现 IsAlive=True , ThreadState=Running ,当然也有可能是 IsAlive=False , ThreadState=Aborted ,因为Abort是不稳定的。然后停一段时间确保thread被终止再点击Button1输出thread的状态,此时 IsAlive=False , ThreadState=Aborted .

2. Abort和Join同时使用的意义

由于abort之后获取到的thread的信息樱渗是不稳定的,所以可能会想到用Join,它的作用是:

使用此方法确保线程已终止。 如果线程不终止,则调用方将无限期阻塞。 如果调用 Join 时该线程已终止,此方法将立即返回。

也就是说如果我们使用Abort之后再加上Join,那么Join之后的thread肯定是蔽颂猛Aborted.

修改一下Button的代码:

private void Button_Click(object sender, RoutedEventArgs e)

{

Console.WriteLine("abort")

thread.Abort()

thread.Join()

Console.WriteLine(thread.IsAlive)

Console.WriteLine(thread.ThreadState)

}

没有加入Join之前,输出的IsAlive和ThreadState可能是多种情况的,加入之后,输出的一定是 IsAlive=False , ThreadState=Aborted .

需要注意的是Join的作用并不是终止thread,它只是等待thread直到thread执行完成。

3. 如何停止线程

如果考虑让一个thread先停止,然后需要的时候再执行,那么使用abort是很不明智的。一般来说只有当应用程序退出,我们才需要把thread彻底关闭,也就是这时候才用到abort。对于停止线程的需求,可以通过信号来解决:

public partial class MainWindow : Window

{

Thread thread

Test t

public MainWindow()

{

InitializeComponent()

t = new Test()

thread = new Thread(new ThreadStart(t.Run))

thread.Start()

}

private void Button_Click(object sender, RoutedEventArgs e)

{

t.Flag = !t.Flag

}

}

public class Test

{

private bool _flag

public bool Flag

{

set

{

_flag = value

if (_flag == true)

{

Console.WriteLine("flag=true")

}

else

{

Console.WriteLine("flag=false")

}

}

get

{

return _flag

}

}

public void Run()

{

while (true)

{

if (Flag)

{

Console.WriteLine("Running...")

}

}

}

}

上面的例子通过信号量来控制thread中的内容是否执行,在实际应用中,如果一个thread中的内容可能再也不会用到,那么对于这一类情况,使用abort和join;如果这个thread只是临时停止,还会再用到,那么使用信号控制就可以了。

WPF 自诞生以来就带着微软先生的傲慢。微软说 WPF 支持氏誉触摸,于是 WPF 就真的支持触摸了。对,我说的是“支持触摸”,那种摸上去能点能动的;偶尔还能带点儿多指的炫酷效果。但是,WPF 推出那会儿,绝大部分开发者都还没有触摸屏呢,开发个程序要怎么验证支不支持触摸呢?微软先生无奈地决定——你写鼠标的代码就好了,我帮你转换!于是……一大波 BUG 袭来……

WPF 触摸失效的分类

我将 WPF 的触歼局段摸失效总结成三种不同的类型。

触摸下 Stylus/Touch 事件正常触发,但不提升为 Mouse 事件;导致仅使用 Mouse 事件的控件无法使用

触摸下 Stylus/Touch 有触发,但触发点位置在 (0, 0) 处或上一个触摸点处;导致即使触发了,当前控件也收不到

触摸下无 Stylus/Touch 事件,也不提升为 Mouse 事件,但鼠标下有 Mouse 事件;导致整个界面完全无法触摸使用

第一种情况

使用触摸或者触笔 *** 作时,如果Up事件中发生了任何异常,会导致StylusLogic.PostProcessInput的后续逻辑不会正确执行,这就包括了用于清理触控资源的 StylusTouchDevice.OnDeactivate 方法。需要注意的是:Up事件不止是TouchUp或者StylusUp,MouseUp也会引发这样的触摸失效。

而在StylusTouchDevice.OnDeactivate方法中,会重置StylusLogic.CurrentMousePromotionStylusDevice属性为null或NoMousePromotionStylusDevice。此方法不执行会直接导致StylusLogic.ShouldPromoteToMouse方法对当前触控设备的判断出现错误,持续返回false,即不会再执行触控转鼠标的逻辑,出现触摸无效的现象。

第二种情况

如果 WPF 的 StylusUp 事件被阻断(例如e.Handled = true,或者在 StylusUp 事件中d出一个模态窗口),则下一次触摸时获取到的点坐标将是上一次被阻断时的点坐标。于是,阻断后的第一次点击必将点中之前点的那个点,而不管现在点中了什么。如果阻断时点在新窗口外,则几乎相当于触摸失效。需要注意的是,这种情况下MouseUp的e.Handled = true是可以使用而不会导致触摸失效的。

第腊逗三种情况

WPF 程序在启动期间,如果触摸组件发生了异常,极有可能会使得触摸根本就没有初始化成功!

比如,System.Windows.Input.StylusLogic.RegisterStylusDeviceCore(StylusDevice stylusDevice)方法在启动时抛出System.InvalidOperationException,虽然内部有catch,但实际获取到的TabletDevice个数是 0 个,根本无法获取触摸设备,于是触摸无效。

或者,在WorkerOperationGetTabletsInfo.OnDoWork方法中,获取到了错误的触摸设备个数:

IPimcManager pimcManager = UnsafeNativeMethods.PimcManageruint countpimcManager.GetTabletCount(outcount)

解决之道

目前为止,这三种问题都没有根本的解决办法,但是我们可以规避。

第一种情况

我们没有办法阻止每一处的 Up 事件,所以我的做法是在禁止那些可能会在Up中引发异常的 *** 作监听Up事件,而是统一由我封装好的Down/Move/Up中进行分发。在我的Up中catch所有异常,随后延迟引发。

try{// 分发真正业务上的 Up 事件。DeliverUpEvent(e)}catch(Exceptionex){// 使用触摸或者触笔 *** 作时,如果 Up 事件中发生了任何异常,会导致 StylusLogic.PostProcessInput 的后续逻辑不会正确执行,// 这就包括了用于清理触控资源的 StylusTouchDevice.OnDeactivate 方法。//// 而在 StylusTouchDevice.OnDeactivate 方法中,会重置 StylusLogic.CurrentMousePromotionStylusDevice 属性// 为 null 或 NoMousePromotionStylusDevice。此方法不执行会直接导致 StylusLogic.ShouldPromoteToMouse 方法// 对当前触控设备的判断出现错误,持续返回 false,即不会再执行触控转鼠标的逻辑,出现触摸无效的现象。//// 这里通过 InvokeAsync 的方式再次抛出异常是为了在保证 Stylus 逻辑不出错的情况下,将异常暴露。Dispatcher.CurrentDispatcher.InvokeAsync(() =>{ ExceptionDispatchInfo.Capture(ex).Throw()})}

第二种情况

一样的,我们没有办法阻止每一处的 Up 事件。于是我们只能要求多人开发项目中的每一位开发人员都注意不要在StylusUp中e.Handled = true。

然而,要求每一个人都这么做是不现实的,尤其是团队成员不稳定的情况下。目前我还没有找到具体可实施的自动化的解决办法,不过我最近正在尝试的 Roslyn 扩展可能可以解决这样的问题。有关 Roslyn 扩展的开发,可以阅读我的另一篇文章:Roslyn 入门:使用 Roslyn 静态分析现有项目中的代码。

第三种情况

启动时触摸设备获取错误的问题我还没有一个彻底的解决方案,目前是检测第一次机会异常,并在发现错误堆栈是以上情况的时候重新启动应用程序。能够采取这样的策略是因为此异常发生在我们的App类初始化之后MainWindow显示出来之前。

close的时候旅纳子线程还处于拆敏没active状态吧。。完全退出用拿指

System.Environment.Exit(System.Environment.ExitCode)


欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/yw/12370035.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-05-23
下一篇2023-05-23

发表评论

登录后才能评论

评论列表(0条)

    保存