知识需要不断积累、总结和沉淀,思考和写作是成长的催化剂,输出倒逼输入

内容目录

一、线程统一取消模型1、取消令牌2、可以中断的线程1、设计一个中断函数2、创建CancellationTokenSource对象3、启动线程4、取消线程执行二、跨线程访问UI基本方法1、Control.Invoke和BeginInvoke2、桌面退出3、编写线程安全的控件三、BackgroundWorker组件1、干活的代码2、启动任务3、结果取回4、取消任务5、进度报告四、等等

一、线程统一取消模型

线程取消在多线程开发中非常普遍,鉴于此微软在.NET4.0基础类库中引入线程统一取消模型对线程取消功能的支持。

1、取消令牌

线程统一取消模型中两个重要的类型就是CancellationToken和CancellationTokenSource
每个CancellationTokenSource对象都包容着一个“取消令牌(CancellationToken)”,并通过它的Token属性向外界展露,用法也很简单通过调用CancellationTokenSource的Cancel()方法通知取消令牌。下面有个小栗子

2、可以中断的线程

下面介绍一个可以中断的线程模型流程

1、设计一个中断函数

一个可以中断的线程函数模板看起来像下面这样

public class ThreadFuncObject
{
    //通过构造函数从外界传入取消令牌
    private CancellationToken _token;
    public ThreadFuncObject(CancellationToken token)
    {
        _token = token;
    }
    public void DoWork()
    {
        //各种功能代码...
        if (_token.IsCancellationRequested)
        {
            //完成清理工作...
            //简单的处理直接return即可
            //return;
            //建议抛出个取消异常
            throw new OperationCanceledException(_token);
        }
        //各种功能代码...
    }
}
2、创建CancellationTokenSource对象
CancellationTokenSource tokenSource = new CancellationTokenSource();
3、启动线程
ThreadFuncObject obj = new ThreadFuncObject(tokenSource.Token);
Thread th = new Thread(obj.DoWork);
th.Start();
4、取消线程执行
tokenSource.Cancel();

二、跨线程访问UI基本方法

刚开始桌面程序开发时,在做进度条等通知UI更新的功能时,经常会遇到跨线程访问UI的问题,抛的错就是控件不能从不是创建它的线程去更改。

在.NET Framework中,所有的可视化控件都从System.Windows.Forms.Control类派生而来,考虑到跨线程访问控件的需要,Control类提供了相应的方法完成跨线程更新界面。

1、Control.Invoke和BeginInvoke

//
// 摘要:
//     在拥有此控件的基础窗口句柄的线程上执行指定的委托。
//
// 参数:
//   method:
//     包含要在控件的线程上下文中调用的方法的委托。
//
// 返回结果:
//     正在被调用的委托的返回值,或者如果委托没有返回值,则为 null。
public object Invoke(Delegate method);

Invoke方法的参数是一个委托,代表在创建控件的线程中要执行的方法。实际场景中是要向UI传值的,可以使用下面的重载

//
// 摘要:
//     在拥有控件的基础窗口句柄的线程上,用指定的自变量列表执行指定委托。
//
// 参数:
//   method:
//     一个方法委托,它采用的参数的数量和类型与 args 参数中所包含的相同。
//
//   args:
//     作为指定方法的参数传递的对象数组。 如果此方法没有参数,该参数可以是 null。
//
// 返回结果:
//     System.Object,它包含正被调用的委托返回值;如果该委托没有返回值,则为 null。
public object Invoke(Delegate method, params object[] args);

使用像下面这样

private void ThreadMethod(Object info)
{
    Action<string> del = (msg) => lblInfo.Text = msg;
    lblInfo.Invoke(del,info);
}

Control.Invoke是同步方法,就是当工作线程调用此方法将一个方法委托给UI线程执行以后,它必须等待UI线程执行完此方法后才能继续执行,如果UI线程很忙,工作线程可能要等待较长的时间不能工作。这可能不太合理

Control.BeginInvoke是异步方法,就是工作线程可以将一个方法传送给UI线程执行之后,继续执行下一步的任务而无需等待。

UI线程是单一的,就是来更新用户界面和接受用户响应的。它从消息队列中提取消息处理,如果某个消息执行时间较长,将会导致界面失去响应,感觉死机了。所以长时间任务要交给独立的工作线程去执行,UI线程只管向用户展示执行的结果。UI线程不应兼职过多

2、桌面退出

桌面(Windows窗体)程序是事件驱动的,应该确保用户不能频繁的点击启动访问控件的线程。因为多线程同时访问同一个控件,可能会造成程序不稳定,出现意想不到的的结果。可以通过控制按钮状态或线程状态,控制线程同步。

如果关闭主窗体导致主线程退出,工作线程还没结束。因为主窗体销毁了,其上的控件都被销毁,而工作线程还包含着访问控件的代码,所以会抛出ObjectDisposedException异常。

前几篇线程中讲到,最简单的方法就是将工作线程设置为后台线程,帮随着主线程的退出而退出,另一个方法就是在窗体FormClosing关闭事件中,判断工作线程的状态,手动Abort终止它。

3、编写线程安全的控件

我们可以将多线程访问功能封装进控件里,从而简化编写跨线程访问时的代码。比如Lable标签控件的text属性不能跨线程直接访问,可以派生出一个新的ThreadSafeLable类,向下面这样,这样不管跨不跨线程,使用相同的代码访问线程安全的控件。

public class ThreadSafeLable : Label
{
    //覆盖基类的Text属性
    public override string Text
    {
        get
        {
            return base.Text;
        }
        set
        {
            if (InvokeRequired)//跨线程调用
            {
                Action<string> del = (msg) => base.Text = msg;
                Invoke(del, value);
            }
            else//普通调用
            {
                base.Text = value;
            }
        }
    }
}

项目开发中注意封装一些这样的控件,可以简化多线程调用代码,提高项目的开发效率

三、BackgroundWorker组件

其实微软提供了一个现成的组件用于跨线程访问UI的,那就是BackgroundWorker组件,大大简化了此类程序的开发。基于事件的异步调用模式,就是通过事件告知什么时候干什么事。支持报告进度,支持取消。

1、干活的代码

在DoWork事件中,真正干活的代码放在这里

2、启动任务

调用BackgroundWorker组件的RunWorkerAsync方法,此方法会激发DoWork事件。此方法有个重载可以传Object对象,在DoWork中通过DoWorkEventArgs.Argument来接收

3、结果取回

BackgroundWorker组件有一个RunWorkerCompleted事件
其中的RunWorkerCompletedEventArgs参数包含以下重要信息:

参数属性 描述
e.Result 工作任务执行的结果
e.Error 这是一个Exception对象,如果工作任务执行过程中没有发生异常,则此属性为null,如果发生了异常,此属性引用被抛出的异常对象
e.Cancelled 如果在工作任务完成前用户取消了操作,则此属性为True,否则此属性为False

4、取消任务

BackgroundWorker组件有一个CancelAsync方法,调用此方法将会导致BackgroundWorker组件的只读属性CancellationPending为True,然后在DoWork中判断即可。

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker bw = sender as BackgroundWorker;
    if (bw.CancellationPending)//如果用户取消了操作
    {
        e.Cancel = true;//此结果将会传到RunWorkerCompleted事件中
        return;//仍需要手动提交结束任务
    }
    //...
}

5、进度报告

BackgroundWorker组件有一个ProgressChanged事件。在DoWork事件处理代码中合适的地方调用BackgroundWorker组件的ReportProgress方法,就会激发ProgressChanged事件。ReportProgress除了可以报告进度,也可以通过其重载报告一个object对象,往往是描述信息。

在ProgressChanged事件中使用线程同步上下文做了特殊处理,可以直接访问窗体上控件,无需考虑跨线程问题。

四、等等

好多东西以前都认真看过,没记性就忘了。马上要搬家了,装不进脑子里就带不走。每次搬家还要为几本书多付一些搬家费,惆怅。(没有大家电,书本就是最重的东西了)

给我一杯酒再给我一支烟,说走就走有缘就再见

.NET设计篇08-线程取消模型和跨线程访问UI的更多相关文章

  1. .NET设计篇08-线程统一取消模型和跨线程访问UI

    知识需要不断积累.总结和沉淀,思考和写作是成长的催化剂,输出倒逼输入 内容目录 一.线程统一取消模型1.取消令牌2.可以中断的线程1.设计一个中断函数2.创建CancellationTokenSour ...

  2. 线程取消 (pthread_cancel)

    线程取消(pthread_cancel) 基本概念pthread_cancel调用并不等待线程终止,它只提出请求.线程在取消请求(pthread_cancel)发出后会继续运行,直到到达某个取消点(C ...

  3. 线程处理模型 由于 SynchronizationContext 引起的死锁问题解决

    由于GUI 应用程序 不能使用线程池的线程更新UI,只能使用 GUI 线程更新,所以在 await 前后需要保证是同一个 GUI 线程 ASP.NET 程序 的线程处理客户端请求的时候,需要假定客户端 ...

  4. .NET 异步多线程,Thread,ThreadPool,Task,Parallel,异常处理,线程取消

    今天记录一下异步多线程的进阶历史,以及简单的使用方法 主要还是以Task,Parallel为主,毕竟用的比较多的现在就是这些了,再往前去的,除非是老项目,不然真的应该是挺少了,大概有个概念,就当了解一 ...

  5. .net 多线程之线程取消

    //线程取消不是操作线程,而是操作信号量(共享变量,多个线程都能访问到的东西,变量/数据库的数据/硬盘数据) //每个线程在执行的过程中,经常去查看下这个信号量,然后自己结束自己 //线程不能别人终止 ...

  6. .NET异步多线程,Thread,ThreadPool,Task,Parallel,异常处理,线程取消

    今天记录一下异步多线程的进阶历史,以及简单的使用方法 主要还是以Task,Parallel为主,毕竟用的比较多的现在就是这些了,再往前去的,除非是老项目,不然真的应该是挺少了,大概有个概念,就当了解一 ...

  7. Linux 线程取消(pthread_cancel)

    基本概念 pthread_cancel调用并不等待线程终止,它只提出请求.线程在取消请求(pthread_cancel)发出后会继续运行,直到到达某个取消点(CancellationPoint).取消 ...

  8. C# 跨线程调用问题

    纠结了好久,终于知道了winform和WPF的UI的跨线程调用的解决方法: winform下如果为了省事,可以直接禁用跨线程检查: Control.CheckForIllegalCrossThread ...

  9. C# Winform 跨线程更新UI控件常用方法汇总(多线程访问UI控件)

    概述 C#Winform编程中,跨线程直接更新UI控件的做法是不正确的,会时常出现“线程间操作无效: 从不是创建控件的线程访问它”的异常.处理跨线程更新Winform UI控件常用的方法有4种:1. ...

随机推荐

  1. Eclipse(Maven) web项目更改项目名称

    1. 右键工程:Refactor->Rename,更改项目名称: 2. 修改项目目录下:.project文件 <?xml version="1.0" encoding= ...

  2. oracle函数 round(x[,y])

    [功能]返回四舍五入后的值 [参数]x,y,数字型表达式,如果y不为整数则截取y整数部分,如果y>0则四舍五入为y位小数,如果y小于0则四舍五入到小数点向左第y位. [返回]数字 [示例] se ...

  3. linux自动挂载NTFS格式移动硬盘

    转自:http://blog.163.com/cmh_lj/blog/static/100812304201252522119264/ 由于移动硬盘还有不少的资料,刚插入移动硬盘的时候发现只能自动挂载 ...

  4. python项目管理

    Python 通常没有对应 Java 的 Ant / Maven 这样的 build tool,有一个用于打包的 setuptools / distutils 但也并不完全等价.如果是用来管理依赖包, ...

  5. Android TextView点击效果

    在Android开发中,我们有时候需要单独的点击某一段文本,如图所示: 如上图,我们要求点击新用户注册这个TextView,为了有更好的用户体验,我们肯定要设置该TextView的点击效果.下面介绍如 ...

  6. HTML静态网页--JavaScript-Window.document对象

    1.Window.document对象 一.找到元素: docunment.getElementById("id"):根据id找,最多找一个:    var a =docunmen ...

  7. OP_REQUIRES failed at conv_ops.cc:386 : Resource exhausted: OOM when allocating tensor with shape..

    tensorflow-gpu验证准确率是报错如上: 解决办法: 1. 加入os.environ['CUDA_VISIBLE_DEVICES']='2' 强制使用CPU验证-----慢 2.'batch ...

  8. pip 指定目录安装

    pip install --target=d:\somewhere\other\than\the\default    package_name

  9. HTML 标签:常规元素和空元素

    HTML标签分为空元素和常规元素 其中空元素是自关闭的,不需要成对地添加关闭标签. 空元素包括:img,input,textarea,select,br,hr,command,link,keygen, ...

  10. 利用 jquery 获取某个元素下的所有图片并改变其属性

    HTML代码 <div id="mochu"> <p>内容....<./p> <p><img src="xxxx.p ...