19.多线程
19.1 知识点
了解线程前先了解进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。简单来说,打开一个应用程序就是在操作系统上开启了一个进程。进程之间可以相互独立运行,互不干扰,也可以相互访问、操作。
什么是线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程。我们目前写的程序都在主线程中。简单理解线程:就是代码从上到下运行的一条“管道”。
什么是多线程
我们可以通过代码开启新的线程,可以同时运行代码的多条“管道”就叫多线程。
多线程的语法相关
在类语句块内、命名空间语句块外,可以设置线程死循环的布尔标识:
// 线程死循环中的布尔标识
static bool isRuning = true;
在类语句块内、主函数外,编写新开线程的逻辑方法:
static void NewThreadLogic()
{
Console.WriteLine("新开线程逻辑方法");
int i = 0;
while (isRuning)
{
// 新开线程执行的代码逻辑在该函数语句块中
// isRuning是死循环中的布尔标识
// 新线程死循环内
}
}
主函数内
线程类Thread
需要引用命名空间 using System.Threading;
传入委托或函数创建Thread类对象申明一个新的线程
//Thread构造函数(暂时只关心这个构造函数好了)
//public Thread(ThreadStart start);
//ThreadStart实际上是一个无参无返回值的委托
//public delegate void ThreadStart();
//注意 线程执行的代码 需要封装到一个函数中
//新线程 将要执行的代码逻辑 被封装到了一个函数语句块中
//传入构造函数
Thread newThread = new Thread(NewThreadLogic);
//这样知识构造出新线程 但是没有启动
Start方法启动线程
//Thread中的Start方法
newThread.Start();
IsBackground属性设置为后台线程
//Thread中的IsBackground属性设置为true
newThread.IsBackground = true;
//当前台线程都结束了的时候,整个程序也就结束了,即使还有后台线程正在运行
//后台线程不会防止应用程序的进程被终止掉
//如果不设置为后台线程 可能导致进程无法正常关闭
//说人话:就比如主线程开了个新线程 新线程有个死循环在那里挂在 永远不会结束 进程无法正常关闭
//假如把新线程设置为后台线程 主线程结束 进程就结束 新线程也就结束了 死循环停止不起作用
修改线程运行标识或Abort方法关闭释放一个线程
//如果开启的线程中不是死循环 是能够结束的逻辑 那么 不用刻意的去关闭它 想情空把线程设置为null也行
//如果是死循环 想要中止这个线程 有两种方式
//4.1-通过死循环中bool标识 关闭释放一个线程
////等待玩家输入 让主线程一直卡着 没有结束 新线程死循环逻辑一直运行
//Console.ReadKey();
////修改新线程死循环中bool标识 这样就不会进新线程死循环逻辑了
//isRuning = false;
////等待玩家输入 让主线程一直卡着 没有结束 但是因为修改新线程死循环中bool标识 不会进线程死循环逻辑了
//Console.ReadKey();
//4.2-通过线程提供的方法(注意在.Net core版本中无法中止 会报错) 关闭释放一个线程
//Thread中的Abort方法
//在让线程=null
//防止报错 用try catch
//中止线程
//try
//{
// newThread.Abort();
// newThread = null;
//}
//catch
//{
//}
Sleep方法线程休眠
//Thread中的Sleep方法
//让线程休眠多少毫秒 1s = 1000毫秒
//在哪个线程里执行 就休眠哪个线程
Thread.Sleep(1000);//让主线程休眠1秒 再执行后面的逻辑
线程之间共享数据
在类语句块内、命名空间语句块外,可以声明一个用来加锁的引用类型对象:
// 随便声明的用来加锁的引用类型对象
static object lockObject = new object();
在新线程死循环内,可以使用lock
关键字对共享数据进行加锁:
// 加锁的循环中,将光标位置设置为 (60, 0),输出一个黄色的正方形
lock (lockObject)
{
Console.SetCursorPosition(60, 0);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write("■");
}
在主函数内,可以通过加锁避免多线程访问共享数据时可能产生的问题:
//多个线程使用的内存是共享的,都属于该应用程序(进程)
//所以要注意 当多线程 同时操作同一片内存区域时可能会出问题
//可以通过加锁的形式避免问题
//例子:
//主线程死循环设置 光标位置 30 0 输出一个红色的圆
//新线程死循环设置 光标位置 60 0 输出一个黄色的正方形
//因为用到的都是Console相关 两个线程可能会同时访问同一块内存
//执行顺序不一定 会造成冲突 不能达到想要的效果
//比如可能会设置 光标位置 30 0 输出了一个黄色的正方形等
//解决办法:加锁
//关键字:lock
//使用语法:lock(引用类型对象)
//当我们在多个线程当中想要访问同样的东西 进行逻辑处理时
//为了避免不必要的逻辑顺序执行的差错 我们要进行加锁
//加了锁的话 系统会判断传进去的引用类型对象有没有被锁住
//假如传进去的引用类型对象被锁住了 意味着有别的地方也在访问同样的东西
//就不会执行里面的代码 等待锁释放后在执行里面的代码
//缺点:锁会影响线程执行效率
//加锁的循环中在 光标位置 30 0 输出一个红色的圆
while (true)
{
lock (lockObject)
{
Console.SetCursorPosition(30, 0);
Console.ForegroundColor = ConsoleColor.Red;
Console.Write("●");
}
}
多线程对于我们的意义
可以用多线程专门处理一些复杂耗时的逻辑,比如 寻路、网络通信等等
总结
多线程是多个可以同时执行代码逻辑的“管道”,可以通过代码开启多线程,用多线程处理一些复杂的可能影响主线程流畅度的逻辑。
19.2 知识点代码
using System;
using System.Threading;
namespace Lesson18_多线程
{
class Program
{
//class语句块内 namespace语句块外
#region 知识点四 多线程的语法相关
//线程死循环中bool标识
static bool isRuning = true;
#endregion
#region 知识点五 线程之间共享数据
//随便声明的用来加锁的引用类型对象
static object lockObject = new object();
#endregion
static void Main(string[] args)
{
Console.WriteLine("多线程");
#region 知识点一 了解线程前先了解进程
//进程(Process)是计算机中的程序关于某数据集合上的一次运行活动
//是系统进行资源分配和调度的基本单位,是操作系统结构的基础
//说人话:打开一个应用程序就是在操作系统上开启了一个进程
//进程之间可以相互独立运行,互不干扰
//进程之间也可以相互访问、操作
#endregion
#region 知识点二 什么是线程
//操作系统能够进行运算调度的最小单位。
//它被包含在进程之中,是进程中的实际运作单位
//一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程
//我们目前写的程序 都在主线程中
//简单理解线程:就是代码从上到下运行的一条“管道”
#endregion
#region 知识点三 什么是多线程
//我们可以通过代码 开启新的线程
//可以同时运行代码的多条“管道” 就叫多线程
#endregion
//主函数内
#region 知识点四 多线程的语法相关
//线程类 Thread
//需要引用命名空间 using System.Threading;
//1.申明一个新的线程
//Thread构造函数(暂时只关心这个构造函数好了)
//public Thread(ThreadStart start);
//ThreadStart实际上是一个无参无返回值的委托
//public delegate void ThreadStart();
//注意 线程执行的代码 需要封装到一个函数中
//新线程 将要执行的代码逻辑 被封装到了一个函数语句块中
//传入构造函数
Thread newThread = new Thread(NewThreadLogic);
//这样知识构造出新线程 但是没有启动
//2.启动线程
//Thread中的Start方法
newThread.Start();
//3.设置为后台线程
//Thread中的IsBackground属性设置为true
newThread.IsBackground = true;
//当前台线程都结束了的时候,整个程序也就结束了,即使还有后台线程正在运行
//后台线程不会防止应用程序的进程被终止掉
//如果不设置为后台线程 可能导致进程无法正常关闭
//说人话:就比如主线程开了个新线程 新线程有个死循环在那里挂在 永远不会结束 进程无法正常关闭
//假如把新线程设置为后台线程 主线程结束 进程就结束 新线程也就结束了 死循环停止不起作用
//4.关闭释放一个线程
//如果开启的线程中不是死循环 是能够结束的逻辑 那么 不用刻意的去关闭它 想情空把线程设置为null也行
//如果是死循环 想要中止这个线程 有两种方式
//4.1-通过死循环中bool标识 关闭释放一个线程
////等待玩家输入 让主线程一直卡着 没有结束 新线程死循环逻辑一直运行
//Console.ReadKey();
////修改新线程死循环中bool标识 这样就不会进新线程死循环逻辑了
//isRuning = false;
////等待玩家输入 让主线程一直卡着 没有结束 但是因为修改新线程死循环中bool标识 不会进线程死循环逻辑了
//Console.ReadKey();
//4.2-通过线程提供的方法(注意在.Net core版本中无法中止 会报错) 关闭释放一个线程
//Thread中的Abort方法
//在让线程=null
//防止报错 用try catch
//中止线程
//try
//{
// newThread.Abort();
// newThread = null;
//}
//catch
//{
//}
//5.线程休眠
//Thread中的Sleep方法
//让线程休眠多少毫秒 1s = 1000毫秒
//在哪个线程里执行 就休眠哪个线程
Thread.Sleep(1000);//让主线程休眠1秒 再执行后面的逻辑
#endregion
#region 知识点五 线程之间共享数据
//多个线程使用的内存是共享的,都属于该应用程序(进程)
//所以要注意 当多线程 同时操作同一片内存区域时可能会出问题
//可以通过加锁的形式避免问题
//例子:
//主线程死循环设置 光标位置 30 0 输出一个红色的圆
//新线程死循环设置 光标位置 60 0 输出一个黄色的正方形
//因为用到的都是Console相关 两个线程可能会同时访问同一块内存
//执行顺序不一定 会造成冲突 不能达到想要的效果
//比如可能会设置 光标位置 30 0 输出了一个黄色的正方形等
//解决办法:加锁
//关键字:lock
//使用语法:lock(引用类型对象)
//当我们在多个线程当中想要访问同样的东西 进行逻辑处理时
//为了避免不必要的逻辑顺序执行的差错 我们要进行加锁
//加了锁的话 系统会判断传进去的引用类型对象有没有被锁住
//假如传进去的引用类型对象被锁住了 意味着有别的地方也在访问同样的东西
//就不会执行里面的代码 等待锁释放后在执行里面的代码
//缺点:锁会影响线程执行效率
//加锁的循环中在 光标位置 30 0 输出一个红色的圆
while (true)
{
lock (lockObject)
{
Console.SetCursorPosition(30, 0);
Console.ForegroundColor = ConsoleColor.Red;
Console.Write("●");
}
}
#endregion
#region 知识点六 多线程对于我们的意义
//可以用多线程专门处理一些复杂耗时的逻辑
//比如 寻路、网络通信等等
#endregion
}
//class语句块内 主函数外
#region 知识点四 多线程的语法相关
//新开线程逻辑方法
static void NewThreadLogic()
{
Console.WriteLine("新开线程逻辑方法");
int i = 0;
//新开线程 执行的代码逻辑 在该函数语句块中
//isRuning是死循环中bool标识
while (isRuning)
{
//Thread.Sleep(1000);//让新线程休眠1秒 再执行后面的逻辑
//Console.WriteLine("新开线程代码逻辑{0}", i);
//i++;
//新线程死循环内
#region 知识点五 线程之间共享数据
//加锁的循环中在 光标位置 60 0 输出一个黄色的正方形
lock (lockObject)
{
Console.SetCursorPosition(60, 0);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write("■");
}
#endregion
}
}
#endregion
}
//总结
//多线程是多个可以同时执行代码逻辑的“管道”
//可以通过代码开启多线程,用多线程处理一些复杂的可能影响主线程流畅度的逻辑
//关键字 Thread
}
19.3 练习题
控制台中有一个■,它会如贪食蛇一样自动移动,请开启一个多线程来检测输入,控制它的转向
class语句块内 namespace语句块外
// 方向枚举
enum E_MoveDir
{
Up,
Down,
Right,
Left,
}
// 图标类
class Icon
{
// 当前移动的方向
public E_MoveDir dir;
// 当前位置
public int x;
public int y;
// 构造函数 初始化位置和方向
public Icon(int x, int y, E_MoveDir dir)
{
this.x = x;
this.y = y;
this.dir = dir;
}
// 移动
public void Move()
{
switch (dir)
{
case E_MoveDir.Up:
y -= 1;
break;
case E_MoveDir.Down:
y += 1;
break;
case E_MoveDir.Right:
x += 2;
break;
case E_MoveDir.Left:
x -= 2;
break;
}
}
// 绘制
public void Draw()
{
Console.SetCursorPosition(x, y);
Console.Write("■");
}
// 擦除
public void Clear()
{
Console.SetCursorPosition(x, y);
Console.Write(" ");
}
// 转向
public void ChangeDir(E_MoveDir dir)
{
this.dir = dir;
}
}
class语句块内 主函数外
// 图标的静态成员变量
static Icon icon;
// 新线程逻辑
static void NewThreadLogic()
{
// 检测输入 修改方向
while (true)
{
switch(Console.ReadKey(true).Key)
{
case ConsoleKey.W:
icon.ChangeDir(E_MoveDir.Up);
break;
case ConsoleKey.A:
icon.ChangeDir(E_MoveDir.Left);
break;
case ConsoleKey.S:
icon.ChangeDir(E_MoveDir.Down);
break;
case ConsoleKey.D:
icon.ChangeDir(E_MoveDir.Right);
break;
}
}
}
主函数内
// 光标不显示
Console.CursorVisible = false;
// 构造一个图标
icon = new Icon(10, 5, E_MoveDir.Right);
icon.Draw();
// 开启多线程 这个线程主要用来检测输入
Thread newThread = new Thread(NewThreadLogic);
newThread.IsBackground = true;
newThread.Start();
// 死循环不停的擦除移动绘制图标
while (true)
{
Thread.Sleep(500); // 每次休眠个0.5秒 再擦除移动绘制图标
icon.Clear();
icon.Move();
icon.Draw();
}
19.4 练习题代码
using System;
using System.Threading;
namespace Lesson18_练习题
{
//class语句块内 namespace语句块外
#region 练习题一
//方向枚举
enum E_MoveDir
{
Up,
Down,
Right,
Left,
}
//图标类
class Icon
{
//当前移动的方向
public E_MoveDir dir;
//当前位置
public int x;
public int y;
//构造函数 初始化位置和方向
public Icon(int x, int y, E_MoveDir dir)
{
this.x = x;
this.y = y;
this.dir = dir;
}
//移动
public void Move()
{
switch (dir)
{
case E_MoveDir.Up:
y -= 1;
break;
case E_MoveDir.Down:
y += 1;
break;
case E_MoveDir.Right:
x += 2;
break;
case E_MoveDir.Left:
x -= 2;
break;
}
}
//绘制
public void Draw()
{
Console.SetCursorPosition(x, y);
Console.Write("■");
}
//擦除
public void Clear()
{
Console.SetCursorPosition(x, y);
Console.Write(" ");
}
//转向
public void ChangeDir(E_MoveDir dir)
{
this.dir = dir;
}
}
#endregion
class Program
{
static void Main(string[] args)
{
Console.WriteLine("多线程练习题");
//主函数内
#region 练习题一
//控制台中有一个■
//它会如贪食蛇一样自动移动
//请开启一个多线程来检测输入,控制它的转向
//光标不显示
Console.CursorVisible = false;
//构造一个图标
icon = new Icon(10, 5, E_MoveDir.Right);
icon.Draw();
//开启多线程 这个线程主要用来检测输入
Thread newThread = new Thread(NewThreadLogic);
newThread.IsBackground = true;
newThread.Start();
//死循环不停的擦除移动绘制图标
while (true)
{
Thread.Sleep(500);//每次休眠个0.5秒 再擦除移动绘制图标
icon.Clear();
icon.Move();
icon.Draw();
}
#endregion
}
//class语句块内 主函数外
#region 练习题一
//图标的静态成员变量
static Icon icon;
//新线程逻辑
static void NewThreadLogic()
{
//检测输入 修改方向
while (true)
{
switch (Console.ReadKey(true).Key)
{
case ConsoleKey.W:
icon.ChangeDir(E_MoveDir.Up);
break;
case ConsoleKey.A:
icon.ChangeDir(E_MoveDir.Left);
break;
case ConsoleKey.S:
icon.ChangeDir(E_MoveDir.Down);
break;
case ConsoleKey.D:
icon.ChangeDir(E_MoveDir.Right);
break;
}
}
}
#endregion
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com