一、理论铺垫
什么是线程
1、举个例子,就拿我喜欢的LOL来说:
我们打游戏时,经常会按Tab键,显示计分板来查看双方的战绩,对方出了什么装备,进行针对性的出装。这个计分板就是一个线程,在不断地更新各自的信息。这个计分板并不影响我们打游戏。
各位骚年们,打游戏也要有梦想!
2、再举个例子,当我打开Word输入文字的时候,左下角会显示我们输入了多少字。其中计算输入了多少文字就是一个线程,在后台运行。我们可以不用管它,继续编辑文档。
前台线程跟后台线程
1、举个例子,还是拿LOL来说:
在游戏中,我们通过鼠标右击来使英雄移动。我是ad,想去拿个红Buff,我右击小地图红Buff所在的位置,英雄便自动地行走,这时,我可以查看计分板,这个计分板就是个后台线程,它不影响我的英雄移动。
如果,计分板一直打开,等待关闭后,英雄才开始往红Buff所在的位置移动,那这个计分板是个前台线程,它阻塞了其他线程的运行。
现在ad的地位是越来越尴尬了。。。
2、再举个例子:比如,网页中有某个按钮(这个按钮的功能是点击后,会在页面上显示额外的信息),如果我们点击了这个按钮,要等半天,并且不能进行其他操作,比如点击其他的按钮,这个就是前台线程;如果我们点击这个按钮,虽然要等半天,但我们仍可以进行其他操作,看看这个页面的其他部分,这个就是后台线程。
线程池
个人的理解:线程池嘛,就是一个池子,里面包含了很多待使用的线程。它的核心就是不需要自己每次都新建一个线程,比如某个线程执行完毕后
,不会自行销毁,而是以挂起的状态返回线程池里,等待新任务。
这么说的话,优点与劣势自然就出来了:
优点:
1、使用起来简单,只需使用QueueUserWorkItem来调用方法即可
2、减少了线程的开销(不需要自己手动新建与销毁线程),如果通过Thread新建一个线程的话,开销就差不多要消耗1M左右的内存
缺点:
1、它其中的一个优点,不需要自己手动新建与销毁线程,有时候也成了缺点,不好控制各个线程的执行,不好判断线程什么时候执行完了,那怎么办呢,这时,Task便出现了。
Task
Task其实就是在ThreadPool的基础上进行一层封装,ThreaPool启动的线程不好判断线程的执行情况,但Task可以,很好地解决了这个问题。
二、使用
备注:构造函数、属性、方法介绍常用到的。
通过Thread创建的线程默认为前台线程,当然也可以修改;通过ThreadPool创建的线程只能为后台线程。
Thread类
创建和控制线程,设置其优先级并获取其状态。
构造函数:
public Thread (System.Threading.ThreadStart start); | 无参数 |
public Thread (System.Threading.ParameterizedThreadStart start); | 有参数 |
属性:
IsBackground | 获取或设置线程是否为后台线程 |
Priority | 获取或设置优先级 |
ManagedThreadId | 获取当前线程的唯一标识符 |
方法:
Abort() | 终止线程 |
Join() | 让线程依次运行(这个方法经常用到) |
使用:
//无参数的线程
Thread thread=new Thread(new ThreadStart(方法名));//实例化线程
thread.Start();//启动线程
//有参数的线程
Thread threadParam = new Thread(new ParameterizedThreadStart(方法名));//有参数
//这里有个非常重要的知识
方法里面的形参必须是object类型的
threadParam.Start(DateTime.Now);//有参数的线程启动方法
ThreadPool
提供一个线程池,该线程池可用于执行任务、发送工作项、处理异步 I/O、代表其他线程等待以及处理计时器。
方法:
QueueUserWorkItem(WaitCallback) | 将方法排入队列以便执行 |
QueueUserWorkItem(WaitCallback, Object) | 将方法排入队列以便执行,Object需要传递的参数 |
使用:
ThreadPool.QueueUserWorkItem(方法名);//这个方法必须要有个参数object
Task
表示一个异步操作。
构造函数:
public Task (Action action); | 无参数无返回值 |
public Task(Action action, object state); | 有参数无返回值 |
public Task(Func<object, TResult> function, object state); | 有参数有返回值 |
属性:
CurrentId | 正在执行的Task的id |
IsCompleted | 是否完成 |
IsCompleted | 是否出现异常 |
方法:
Start() | 启动Task |
Wait() | 等待Task执行完成 |
使用:
Task task_NoParam = new Task(无参数无返回值的方法);
task_NoParam.Start();
Task task_WithParam = new Task(有参数无返回值的方法, 传给方法的参数);
task_WithParam.Start();
Task<string> task_WithParam_WithReturn = new Task<string>(有参数有返回值的方法, 传给方法的参数);
task_WithParam_WithReturn.Start();
string Result=task_WithParam_WithReturn.Result;//返回的结果
三、具体代码
class CommonClass
{
public void TestMethod()
{
Console.WriteLine("没有参数的方法");
for (int i = 0; i < 3; i++)
{
Console.WriteLine("无参数的方法" + i + "");
}
}
public void TestMethod(object obj)//这个形参必须是object类型的---这很重要
{
Console.WriteLine("有参数的方法,参数为" + obj.ToString() + "");
for (int i = 0; i < 3; i++)
{
Console.WriteLine("有参数的方法" + i + "");
}
}
public void TestMethod_ThreadPool(object obj)
{
if (obj != null)
{
Console.WriteLine("ThreadPool-有参数的方法,参数为" + obj.ToString() + "");
for (int i = 0; i < 3; i++)
{
Console.WriteLine("ThreadPool-有参数的方法" + i + "");
}
}
else
{
Console.WriteLine("ThreadPool-没有参数的方法");
for (int i = 0; i < 3; i++)
{
Console.WriteLine("ThreadPool-无参数的方法" + i + "");
}
}
}
public void TestMethod_Task_NoParam()
{
Console.WriteLine("Task-无参数");
}
public void TestMethod_Task_WithParam(object obj)
{
Console.WriteLine($"Task-有参数,参数为:{obj.ToString()}");
}
public string TestMethod_Task_WithParam_WithReturn(object obj)
{
Console.WriteLine($"Task-有参数,参数为:{obj.ToString()}");
return obj.ToString();
}
}
主程序代码:
class Program
{
static void Main()
{
for (int i = 0; i < 3; i++)
{
Console.WriteLine("主线程" + i + "");
}
CommonClass commonClass = new CommonClass();
//Thread的使用
Thread thread = new Thread(new ThreadStart(commonClass.TestMethod));//没有参数
//thread.IsBackground = false;//设置前台线程还是后台线程,在线程启动前设置
thread.Start();//没有参数的线程启动方法
Thread threadParam = new Thread(new ParameterizedThreadStart(commonClass.TestMethod));//有参数
threadParam.Start(DateTime.Now);//有参数的线程启动方法
//等待上面两个线程执行完后
thread.Join();
threadParam.Join();
Console.WriteLine("\n下面是ThreadPool的使用");
//ThreadPool的使用
ThreadPool.QueueUserWorkItem(commonClass.TestMethod_ThreadPool);
ThreadPool.QueueUserWorkItem(commonClass.TestMethod_ThreadPool, DateTime.Now);
//注意:使用ThreadPool不好判断线程什么时候完成
Thread.Sleep(1000);
Console.WriteLine("\n下面是Task的使用");
//Task的使用
Task task_NoParam = new Task(commonClass.TestMethod_Task_NoParam);//无参数无返回值的方法
task_NoParam.Start();
Task.WaitAll(task_NoParam);//等task_NoParam这个Task执行完执行下面的
//这就是使用Task的好处,便于控制,知道Task什么时候执行完
//不像TreadPool,让他启动后台线程,然后就没有然后了。任务完成后自动销毁。
Task task_WithParam = new Task(commonClass.TestMethod_Task_WithParam, "sdf");//有参数无返回值的方法
task_WithParam.Start();
Task<string> task_WithParam_WithReturn = new Task<string>(commonClass.TestMethod_Task_WithParam_WithReturn, "sfdgsdfgasdf");//有参数有返回值
task_WithParam_WithReturn.Start();
Console.WriteLine("有参数有返回值的Task执行结果:" + task_WithParam_WithReturn.Result + "");
Console.ReadKey();
}
}
结果:
四、总结
爬虫中,经常会用到多线程的。
希望这篇博客能够帮到大家!
理论上Task性能较好,但实际上ThreadPool性能最好,也有可能是我使用的方式不对。仅供参考!
各位骚年们,打游戏也是要有梦想滴!