【c#线程学习笔记二:线程的信号发送和工作线程更新UI】



一、前台线程和后台线程

一般情况下,显式创建的线程成为前台线程,线程池中创建的线程默认是后台线程。只要有一个前台线程还在运行,应用程序就仍然保持运行状态,而后台线程则不然。当所有前台线程结束时,应用程序就会停止,且所有的后台线程也随之终止。
可以通过线程的IsBackground属性来查询或修改线程的前后台状态
当前台线程都执行结束时,后台线程随之终止,且后台线程执行栈上的finally语句块都无法执行,如果finally里执行的是清理逻辑等,那么可以在应用程序结束时等待后台线程汇合(Join)来避免前面的问题,不过需要注意的是需要指定一个超时时间,来舍弃那些无法按时完成的后台任务,防止应用程序无法正常关闭。

二、设置线程的优先级

当多个线程同时运行,优先级就会变得很重要,因为有的线程可能需要更低的延迟(能够快速响应),比如一些UI进程,那么可以使用System.Diagnostics命名空间下的Process类来提高进程的优先级:

using(Process p  = Process.GetCurrentProcess())
    p.PriorityClass = ProcessPriorityClass.High;

该方法适用于一些工作量较少的线程,在计算密集,特别是带有用户界面的应用程序中,提高进程的优先级可能会抢占其他进程的执行时间,从而影响整个计算机的运行速度。

三、信号发送

线程间的消息通知即所谓的信号发送。

var signal = new ManualResetEvent(false);
new Thread(() =>
{
    Console.WriteLine("等待信号");
    signal.WaitOne();
    signal.Dispose();
    Console.WriteLine("线程完成");
}).Start();
Thread.Sleep(2000);
Console.WriteLine("等待2s后设置信号");
signal.Set();
//输出结果:
//等待信号
//等待2s后设置信号
//线程完成

以上代码中,线程会阻塞在signal.WaitOne(),直到其他线程调用了signal.Set"打开"了信号后,线程才继续执行

四、工作线程更新UI(封送)

1.Invoke和BeginInvoke

在WPF、WinFrom应用程序中,在主线程上执行长时间的操作将导致应用失去响应。这是因为主线程同时也是处理消息循环的线程,它会根据键盘和鼠标事件来执行相应的渲染工作。
一个常用的方法是创建一个工作线程来执行耗时操作,并在完成之时更新UI线程。然后UI元素和空间只能够由创建他们的线程访问(通常是主UI线程)。因此,如果想要在工作线程上更新UI,就必须将请求发送给UI线程,这种技术就是封送。
我们可以用BeginInvoke/Invoke方法实现该功能。这两种方法都可以接受一个委托来引用执行的方法,BeginInvoke会将这个委托假如到UI线程的消息队列上(这个消息队列也处理键盘、鼠标和定时器事件)。Invoke也会执行相同的操作,但是他会阻塞,直到UI线程接受了这些信息。因此,使用Invoke可以从方法中直接获得返回值,而如果不需要返回值,则可以使用BeginInvoke,它不会阻塞调用者。
示例:假如窗口中包含一个文本框txtMessage,我们希望一个工作线程能够在一个耗时的任务执行完毕后更新文本框的内容

partial class MyWindow : Window
{
    public MyWindow()
    {
        InitializeComponent();
        new Thread(Work).Start();
    }
    void Work()
    {
        Thread.Sleep(3000);  //模拟耗时任务
        UpdateMessage("The answer");
    }
    void UpdateMessage(string message)
    {
        Action action = () => {txtMessage.Text = message; };
        this.BeginInvoke(action);   //WPF则是 Dispatcher.BeginInvoke(action);
    }
}

2.同步上下文

同步上下文的目的是让公共语言运行时(CLR)的内部异步/同步操作能够在不同的同步模型中正确地工作。 这个模型也简化了托管应用程序在不同的同步环境中工作时必须遵循的一些要求。
例如,在Windows Forms应用程序中,只有一个主UI线程,它负责创建和更新窗体和控件。如果在其他线程上直接访问UI元素,就会导致异常或不一致的行为。因此,Windows Forms会在创建首个窗体的线程上安装一个WindowsFormsSynchronizationContext,它会将委托放到UI线程上执行。 这样,就可以在其他线程上异步地执行一些耗时或阻塞的操作,然后通过Post方法将结果传递给UI线程同步执行更新界面的操作。
System.ComponetModel命名空间下由一个SynchronizationContext类,这个类实现了一个线程封送的功能。

partial class MyWindow : Window
{
    SynchronizationContext _uiSyncContext;
    public MyWindow()
    {
        InitializeComponent();
        _uiSyncContext = SynchronizationContext.Current;
        new Thread(Work).Start();
    }
    void Work()
    {
        Thread.Sleep(3000);  //模拟耗时任务
        UpdateMessage("The answer");
    }
    void UpdateMessage(string message)
    {
        //Action action = () => {txtMessage.Text = message; };
        //this.BeginInvoke(action);   //WPF则是 Dispatcher.BeginInvoke(action);
        _uiSyncContext.Post(_ => txtMessage.Text = message, null);
    }
}

该方法效果和BeginInvoke相同。另外Send方法和Invoke方法的效果也是相同。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值