0. 前言

照例一份前言,在介绍任务和多线程之前,先介绍一下异步和同步的概念。我们之间介绍的知识点都是在同步执行,所谓的同步就是一行代码一行代码的执行,就像是我们日常乘坐地铁通过安检通道一样,想象我们每个人都是一行代码,我们依次通过安检仪器的时候就是同步。

那么,什么是异步呢?有一个时间利用率的故事,讲的是在烧水的同时,顺便准备茶叶,清洗茶杯等工序可以节省时间。这个故事就是异步的一个典型范例。异步通俗的将就是不暂停也不等待当前耗时的流程执行完成,继续执行后续的流程。

那么这和任务与多线程有什么关系呢?在C#中,基于任务可以很简单的创建一个异步程序或者异步方法;同时任务也是一个简单的多线程模式。不过值得注意的是,C#的异步可以由多线程实现,但多线程更多的是用来实现并行。所谓并行,顾名思义,就是多任务同时执行,这里的任务指的是程序需要完成的事,而不是C#中的任务机制。

这一篇是《C#基础知识系列》的一篇,简单介绍一下如何创建、使用任务和多线程,这部分的内容很多,包括有很多注意事项,将会另开一个系列专门讲解C#的异步和并行编程,名字暂定为《C#异步编程系列》。

1. 线程

了解过计算机的人可能知道程序最小执行单元是线程,最小资源分配单位是进程。进程里必然至少有一个线程,而一个程序也必然至少有一个进程。这里不过多的介绍进程和线程的区别于关系,只需要记着线程是程序最小执行单元,我们在开发中最常用的也是线程。

在很多不太严谨的编程教程中,都会把多线程和并行化作等号。但是这里有一个很微妙的区别,对于单核CPU来说,多进程和多线程一样,都不会产生并行的效果;对于多核CPU而言,多进程必然是并行的,但是多线程则不一定并行。所以C#中,线程更多的用作异步处理上,而不是并行计算上。

在C#程序中,需要引用System.Threading。C#的入门级线程操作只需要知道Thread类、一个带参数的无返回值方法和一个不带参数的无返回值方法,这三个要点就可以了。

1.1 创建一个线程

var thread = new Thread(() =>
{
});

以上示例代码演示了如果创建一个线程。但创建了线程,并不代表线程就会运行。

说到这里就必须说一下线程的状态,一般情况线程分为五个阶段,也就是五种状态:分别是准备、就绪、运行、阻塞、死亡。当然在不同的地方,状态可能会细分为更多的级别,这里只做初步的介绍。状态之间的切换如下:

线程的状态之间切换顺序有着严格的限制,而且只能从就绪态由CPU切换到运行态,运行态无法从其他状态切换过去,而且这一步的切换开发者不能控制。

现在,我们回到线程的创建方法,先来看看Thread构造方法的声明:

public Thread (System.Threading.ParameterizedThreadStart start);
public Thread (System.Threading.ThreadStart start);

碰到了两个没见过的类型,我们继续看看?

public delegate void ParameterizedThreadStart(object obj);
public delegate void ThreadStart();

到这里,线程的创建为我们揭开了它的谜底。根据之前《C# 基础知识系列- 11 委托和事件》那篇的介绍,我们可以很明确的得到 ThreadStart是一个 无返回值也没有参数的委托,而ParameterizedThreadStart表示有一个object的参数。所以,创建线程的时候,可以直接传一个方法进去。

有的同学可能要问了,为什么创建线程的委托参数那么少?这里涉及到一个并发概念,因为线程访问过多的主线程可能会导致锁,所以最佳的线程实践就是让线程的运行保持一个相对封闭的环境。

当然,C#的线程其实放宽了这部分的限制,这部分将在《C#异步编程系列》中继续探讨。

现在我们回过头来,再看看如何创建一个标准的线程:

class Program
{
static void Main(string[] args)
{
var thread1 = new Thread(ThreadTest1);
var thread2 = new Thread(ThreadTest2);
} /// <summary>
/// 不带参数的线程
/// </summary>
public static void ThreadTest1()
{
// 业务代码
}
/// <summary>
/// 带参数的线程
/// </summary>
/// <param name="obj"></param>
public static void ThreadTest2(object obj)
{
//业务代码
}
}

其中thread1就是一个没有参数的线程,thread2是一个带参数的线程。

注:Main方法是C#程序入口的固定写法,之前所有的示例代码都是在这个方法里执行的,后续这部分会在《C#基础篇之开发工具和项目的基本结构》这一篇中详细介绍,这里先记住这是一个固定写法。

1.2 启动并使用线程

在启动线程之前,我们先介绍一个概念:主线程。主线程指伴随着当前程序启动而启动的线程,以代码来看就是Main方法所在线程。

线程通过调用Thread.Start方法,来将线程标记为就绪态。

注意:线程不能直接进入运行态,该状态只能由CPU决定。

所以上一小节的创建的两个线程可以通过以下方式通知已经准备就续:

thread1.Start();

咦?是不是少了一个?注意力集中的小伙伴会发现,我没有演示thread2的调用方法。thread2与threa1有个不同的地方,thread2的委托参数有一个参数。那么必然Start也有一个对应的带参版本的重载,所以thread2就会有以下两种调用方式:

thread2.Start();

object obj;// 省略来源
thread2.Start(obj);

两种方法有什么区别吗?

有,但是区别不大。第一种调用方式对于方法ThreadTest2而言就是参数为null,第二种就是参数为obj的值。所以第一种调用约等于thread2.Start(null)

1.3 暂停或销毁线程

这一小节的标题是,暂停或销毁线程。当线程运行起来后,如果没有突发情况或者外力干涉会直接运行到结束。这时候,后续程序觉得这个线程执行时间过长,需要暂停或者取消线程的执行,那么就需要了解一下如何暂停或者销毁线程了。

thread1.Suspend();//挂起
thread1.Resume();//继续

中断线程,也就是终止线程:

thread1.Abort();// 已挂起的线程无法中断

强制终止销毁:

thread1.Interrupt();//在执行中的线程无法终止

以上是线程操作的基本概念,这部分并不是为了能让大家精通多线程,这是为了让大家有个初步概念。在C# 中,创建一个线程需要传递一个委托进去,因为委托的性质,并没有限制是否是静态方法,所以这里也可以传一个对象的方法。当然了,我们十分不提倡这样做,因为会导致一些多线程领域里的一些问题。

2. 任务

C#中的任务与线程的区别不是很大,因为C#的任务就是基于线程实现的,而任务比线程更友好,使用也更方便,当然使用也更加复杂。不过对于开发者而言,任务取消了线程的状态切换,只保留了有限的一部分。而且,在C# 更推荐使用任务,任务也是对线程的进一步抽象和改进。

2.1 创建一个任务

如线程相同的一点是,任务的创建也是通过传递一个方法(严格上讲是一个委托)。不同的是,线程的委托没有返回值而且也不接受从线程返回的值,而任务则不同,调用方可以期待任务是有返回值的而且也可以正常使用。

我们先来看看任务是什么,任务的命名空间System.Threading.Tasks,任务的类有以下两种声明:

public class Task : IAsyncResult, IDisposable;
public class Task<TResult> : System.Threading.Tasks.Task;

第一个,没有泛型的Task类表示一个没有返回值的任务;

第二个,泛型Task类表示该任务有一个返回值,返回值的类型为传递进来的泛型参数。

两个任务类的初始化类似于Thread类,不过与之不同的是 泛型Task的参数是Func,都有一个带Object参数的委托。

与线程不同,任务的创建就有很多种方法:

1 通过构造函数创建

var task1 = new Task(() => { });
var task2 = new Task<int>(()=>
{
int i = 0;
return i;
});

2 使用任务工厂:

var task1 = Task.Factory.StartNew(() => { });
var task2 = Task.Factory.StartNew(() =>
{
int i = 0;
return i;
});

3 通过Task.Run创建:

var task1 = Task.Run(() => { });
var task2 = Task.Run(() =>
{
int i = 0;
return i;
});

以上三种方式创建的任务是等效的。当然实际上任务的创建并非只有这么几种,但这几种是任务创建的基础,使用频率相当高。

2.2 执行任务

与线程不同的是,任务创建完成之后就会自动执行,不需要调用方法。

关于任务的运行有以下需要注意的地方:

  1. 任务的运行不会阻塞主线程;

  2. 主线程结束后,任务一定也会结束;

任务可以IsCompleted属性确定任务是否执行完成,所以可以通过访问任务对象的IsCompleted确认该任务是否执行完成,但有一个问题,这个属性只会表示当前任务是否完成。所以如果需要等待任务完成,则可以通过访问Wait()方法,强制主线程等待任务结束。

如果使用的任务是泛型Task也就是待返回值的任务,可以通过访问Result属性获取任务执行结果。有意思的地方就是,这个属性能获取到结果的时候,也是任务执行完成的时候,所以不需要调用Wait()IsCompleted来判断任务是否完成。

注:通过构造方法创建的任务需要调用 Start方法才能启动,而通过Task.Run和Task.Factory.StartNew创建的则不需要。

3. 总结

C#中任务基于线程,对其做了更多的抽象和封装,将线程的粒度进一步细分。所以线程在C#中就没有那么重要了,任务逐渐替代了线程在C#程序中的地位。

任务与线程,有共通的地方,也有完全不一样的地方。线程的运行环境相对封闭,所以线程出现错误导致线程中断,不会影响主线程的运行。但任务则不一样了,任务与主线程的关联性更大,一旦任务出现异常导致任务中断,如果没有正确处理,则会影响主线程的运行。

以上是本篇的全部内容,也请大家期待一下《C#异步编程系列》吧。

更多内容烦请关注我的博客

C# 基础知识系列- 12 任务和多线程的更多相关文章

  1. C# 基础知识系列- 17 小工具优化

    0. 前言 不知道有没有动手能力强的小伙伴照着上一篇的内容写过程序呢?如果有的话,应该会在使用的时候发现以下几个问题: 每次启动都需要经过漫长的时间去遍历磁盘里的文件目录 因为数据是用的字典保存的,所 ...

  2. C# 基础知识系列- 3 集合数组

    简单的介绍一下集合,通俗来讲就是用来保管多个数据的方案.比如说我们是一个公司的仓库管理,公司有一堆货物需要管理,有同类的,有不同类的,总而言之就是很多.很乱.我们对照集合的概念对仓库进行管理的话,那么 ...

  3. 基础知识系列☞Abstract和Virtual→及相关知识

    转载地址→http://www.cnblogs.com/blsong/archive/2010/08/12/1798064.html 在C#的学习中,容易混淆virtual方法和abstract方法的 ...

  4. C# 基础知识系列- 9 字符串的更多用法(一)

    0. 前言 在前面的文章里简单介绍了一下字符串的相关内容,并没有涉及到更多的相关内容,这一篇将尝试讲解一下在实际开发工作中会遇到的字符串的很多操作. 1. 创建一个字符串 这部分介绍一下如何创建一个字 ...

  5. C# 基础知识系列-13 常见类库(三)

    0. 前言 在<C# 基础知识系列- 13 常见类库(二)>中,我们介绍了一下DateTime和TimeSpan这两个结构体的内容,也就是C#中日期时间的简单操作.本篇将介绍Guid和Nu ...

  6. C# 基础知识系列- 16 开发工具篇

    0. 前言 这是C# 基础知识系列的最后一个内容讲解篇,下一篇是基础知识-实战篇.这一篇主要讲解一下C#程序的结构和主要编程工具. 1. 工具 工欲善其事必先利其器,在实际动手之前我们先来看看想要编写 ...

  7. 基础知识系列☞C#中→属性和字段的区别

    "好吧...准备写个'基础知识系列',算是记录下吧,时时看看,更加加深记忆···" 其实本来准备叫"面试系列"... 字段.属性.你先知道的哪个概念? ***我 ...

  8. 学习javascript基础知识系列第二节 - this用法

    通过一段代码学习javascript基础知识系列 第二节 - this用法 this是面向对象语言中的一个重要概念,在JAVA,C#等大型语言中,this固定指向运行时的当前对象.但是在javascr ...

  9. 学习javascript基础知识系列第三节 - ()()用法

    总目录:通过一段代码学习javascript基础知识系列 注意: 为了便于执行和演示,建议使用chrome浏览器,按F12,然后按Esc(或手动选择)打开console,在console进行执行和演示 ...

随机推荐

  1. TensorFlow 实战卷积神经网络之 LeNet

    欢迎大家关注我们的网站和系列教程:http://www.tensorflownews.com/,学习更多的机器学习.深度学习的知识! LeNet 项目简介 1994 年深度学习三巨头之一的 Yan L ...

  2. OpenWrite技术自媒体界的JVM一次编辑、随处发布

    原文 :https://mp.weixin.qq.com/s/KUtJ2dwhBRuJ2G_-PkQFEA 最懂你的科技自媒体管理平台 [实用小工具推荐]给科技或技术同学们推荐一款比较好用的工具,可以 ...

  3. 泛型代码中的 default 关键字

    在泛型类和泛型方法中会出现的一个问题是,如何把缺省值赋给参数化类型,此时无法预先知道以下两点: l        T将是值类型还是引用类型 l        如果T是值类型,那么T将是数值还是结构 对 ...

  4. 在写微信小程序如何 首次编译的是当前写的页面

    首先点击顶部的编译如下图 染后点击添加模式哈 选择页面加载是启动的是哪一个页面

  5. Vulnhub DC-7靶机渗透

    信息搜集 nmap -sP 192.168.146.0/24 #主机发现 nmap -A 192.168.146.144 #端口扫描 查看robots.txt,看看admin,403,其他没有什么可利 ...

  6. C语言 文件操作(一)

    #include<stdio.h> int main(){          FILE *fp = fopen("f:\\lanyue.txt","r&quo ...

  7. ThinkPHP3.2.3发送微信模板消息

    一.开通模板消息功能 所有服务号都可以在功能->添加功能插件处看到申请模板消息功能的入口,但只有认证后的服务号才可以申请模板消息的使用权限并获得该权限:需要选择公众账号服务所处的2个行业,每月可 ...

  8. 06-jmeter参数化(函数对话框使用)

    概念: 1.变量命名的规则:字母.下划线开头,可包含数字,严格区分大小写 2.配置元件:用户定义的变量-------值是不变化的 用户命名的参数--------可以动态获取的并传参的 jmeter函数 ...

  9. 1、jmeter语言设置、版本颜色

  10. 【python实现卷积神经网络】上采样层upSampling2D实现

    代码来源:https://github.com/eriklindernoren/ML-From-Scratch 卷积神经网络中卷积层Conv2D(带stride.padding)的具体实现:https ...