进程与线程的一个简单解释 - 阮一峰的网络日志 进程和线程的理解
进程 主线程和分线程关系
一个程序就是一个进程,然而进程里面包含若干个线程,而每个进程里面都有一个(可以说必须要有一个)线程,这个线程就是主线程,然而主线程有一天发现自己的工作太多了,在规定的时间内完不成工作,这时候他就召唤了一个小弟(子线程)帮他,他给小弟分配了一些任务,当小弟做完了分配给他的任务后,他就把小弟赶走了!这就是主线程和子线程。
有4种创建线程的方式:
- 1.Thread 自己创建的独立的线程, 优先级高,需要使用者自己管理。
//创建线程1
//创建分线程执行哪个方法
ThreadStart childref = new ThreadStart(CallToChildThread);
//创建分线程实例对象
Thread childThread = new Thread(childref);
//执行分线程
childThread.Start();
public static void CallToChildThread()
{
while (true)
{
Console.WriteLine("执行繁重的任务");
}
}
// 简写
Thread childThread = new Thread(() => {
while (true)
{
Console.WriteLine("执行繁重的任务");
}
});
//线程暂停2
//创建分线程执行哪个方法
ThreadStart childref = new ThreadStart(CallToChildThread);
//创建分线程实例对象
Thread childThread = new Thread(childref);
// 设置线程的名字
childThread.Name = "分线程1";
//执行分线程
childThread.Start();
public static void CallToChildThread()
{
//线程休眠 当把方法写在哪个线程中就休眠哪个线程
Thread.Sleep(3000);
while (true)
{
Console.WriteLine("执行繁重的任务");
}
}
//线程销毁
1.线程方法Method执行完结,线程自动销毁
2.如果是无限循环需要手动销毁
//创建线程
//创建分线程执行哪个方法
ThreadStart childref = new ThreadStart(CallToChildThread);
//创建分线程实例对象
Thread childThread = new Thread(childref);
childThread.Name = "分线程1";
//执行分线程
childThread.Start();
//线程休眠 当把方法写在哪个线程中就休眠哪个线程
Thread.Sleep(3000);
//销毁线程
childThread.Abort();
Console.WriteLine("haha");
}
public static void CallToChildThread() {
while (true)
{
Console.WriteLine("111111");
}
}
线程函数通过委托传递,可以不带参数,也可以带参数(只能有一个参数)
Thread t2 = new Thread(new ParameterizedThreadStart(TestMethod));
t2.Start("hello");
public static void TestMethod(object data)
{
string datastr = data as string;
Console.WriteLine("带参数的线程函数,参数为:{0}", datastr);
}
//简写
Thread t2 = new Thread((e) => { Console.WriteLine("带参数的线程函数,参数为:{0}", e); });
t2.Start("hello");
线程阻塞 Join() 线程之间执行顺序默认是无关的 为了保持同步 使用join()
Thread thread1 = new Thread(() =>
{
Console.WriteLine("2");
});
Thread thread2 = new Thread(() => {
//线程同步
thread1.Join();
Console.WriteLine("3");
});
Thread thread3 = new Thread(() => {
thread2.Join();
Console.WriteLine("4");
});
thread1.Start();
thread2.Start();
thread3.Start();
线程抢占 如果两个线程同时对某个资源进行同时访问 就可能出现 线程抢占
static bool done;
static void Main(string[] args)
{
new Thread(Go).Start();
Go();
Console.ReadKey();
}
static void Go()
{
if (!done)
{
Console.WriteLine("Done");
done = true;
}
}
解决线程抢占问题 使用线程锁
static readonly object locker = new object(); //线程锁对象
static bool done;
static void Main(string[] args)
{
new Thread(Go).Start();
Go();
Console.ReadKey();
}
static void Go()
{
//lock关键字 线程锁 (唯一的对象)
lock (locker)
{
if (!done)
{
Console.WriteLine("Done");
done = true;
}
}
}
前台线程与后台线程
前台线程会随着主线程窗口关闭而停止,后台线程及时主线程窗口关闭自己独立运行。
IsBackground 设置是否是前后台线程
Thread worker = new Thread(() => Console.ReadLine());
worker.IsBackground = true;
worker.Name = "backThread";
worker.Start();
Console.WriteLine("finish!");
- 2.ThreadPool 线程池
-
线程和线程池都是进行多线程操作的,线程池是用来保存线程的一个容器,在程序创建线程来执行任务的时候线程池才会初始化一个线程,线程在执行完毕之后并不会被销毁,而是被挂起等待下一个任务的到来被激活执行任务,当线程池里的线程不够用的时候会新实例化一个线程,来执行,线程池里的线程会被反复利用。
方式一:
QueueUserWorkItem 接收一个参数,参数类型是WaitCallback 它是一个无返回值委托,委托函数接收一个obejct类型参数。
官方定义:public delegate void WaitCallback(object state);
WaitCallback callBack = DoSomething;
ThreadPool.QueueUserWorkItem(callBack);
static void DoSomething(object obj)
{
Console.WriteLine("do something");
}
以上代码可以简写, waitCallback委托赋值一个匿名方法
WaitCallback waitCallback = arg => Console.WriteLine("dosomething1");
ThreadPool.QueueUserWorkItem(waitCallback)
继续简写
ThreadPool.QueueUserWorkItem(e =>
{
Console.WriteLine("do something2");
});
方式二:
QueueUserWorkItem 接收两个参数,第一个参数是WaitCallback 委托类型,第二个参数是object对象,用于传递给委托函数
参数"dosomething3"将传递给委托函数DoSomething
static void DoSomething(object value)
{
Console.WriteLine(value);
}
WaitCallback waitCallback1 = DoSomething;
ThreadPool.QueueUserWorkItem(waitCallback1, "dosomething3");
以上代码简写
使用匿名函数进行简写:
ThreadPool.QueueUserWorkItem(e =>
{
DoSomething(e);
}, "dosomething4");
线程休眠
//ManualResetEvent mreset = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(e =>
{
Thread.Sleep(2000);
Console.WriteLine("dosomething");
// mreset.Set();
});
Console.WriteLine("dosomething else...");
Console.WriteLine("dosomething else...");
//阻塞主线程 等待分线程完成后 执行mreset.Set()后执行后续代码
mreset.WaitOne();
- 3.net4.0在ThreadPool的基础上推出了Task类
- ThreadPool相比Thread来说具备了很多优势,但是ThreadPool却又存在一些使用上的不方便◆ ThreadPool不支持线程控制,线程延续 ,线程取消
- task 解决了 以上问题
//1.Task位于using System.Threading.Tasks;命名空间下
2.Task和ThreadPool一样 都是线程池操作
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace DemoAsync
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Task With Thread Start !");
for (int i = 0; i <= 5; i++)
{
Thread t = new Thread(Dotaskfunction);
t.Start();
}
Console.WriteLine("Task With Thread End !");
Console.WriteLine("Task With Task Start !");
for (int i = 0; i <= 5; i++)
{
Task.Run(() => { Dotaskfunction(); });
}
Console.WriteLine("Task With Task End !");
Console.ReadLine();
}
public static void Dotaskfunction()
{
Console.WriteLine("ThreadID: {0}", Thread.CurrentThread.ManagedThreadId);
}
}
}
2. 创建Task
//方式1
//第一种创建方式,直接实例化:必须手动去Start 可以绑定有参数的委托对象
var task1 = new Task(() =>
{
//TODO you code
});
task1.Start();
//方式2
//第二种创建方式,工厂创建,直接执行 且绑定的都是无参无返回值的委托对象
var task2 = Task.Factory.StartNew(() =>
{
});
或者是
Task.Run(() =>{
});
三、Task的任务控制:Task比threadPool优点就是任务控制,很好的控制task的执行顺序,让多个task有序的执行
Task.Wait task1.Wait();就是等待任务执行(task1)完成,task1的状态变为Completed。
Task.WaitAll 待所有的任务都执行完成:
Task.WaitAny 等待任何一个任务完成就继续向下执行
Task.ContinueWith 第一个Task完成后自动启动下一个Task,实现Task的延续
CancellationTokenSource 通过cancellation的tokens来取消一个Task。
//task1.Wait();就是等待任务执行(task)完成
Task task = Task.Run(() => {
Thread.Sleep(3000);
Console.WriteLine("1");
});
等待任务执行(task)完成 后执行后续代码
task.Wait();
Task task1 = Task.Run(() => { Console.WriteLine("2"); });
Task task2 = Task.Run(() => { Console.WriteLine("3"); });
Console.WriteLine("All task finished!");
//Task.WaitAll 待所有的任务都执行完成
Task task = Task.Run(() => {
Thread.Sleep(3000);
Console.WriteLine("1");
});
Task task1 = Task.Run(() => { Console.WriteLine("2"); });
Task task2 = Task.Run(() => { Console.WriteLine("3"); });
Task.WaitAll(task, task1, task2);
待所有的任务都执行完成 执行以下内容
Console.WriteLine("All task finished!");
Task.WaitAny 等待任何一个任务完成就继续向下执行
Task task = Task.Run(() => {
Thread.Sleep(3000);
Console.WriteLine("1");
});
Task task1 = Task.Run(() => { Console.WriteLine("2"); });
Task task2 = Task.Run(() => { Console.WriteLine("3"); });
//等待其中任意一个任务完成后 执行后续代码
Task.WaitAny(task, task1, task2);
Console.WriteLine("All task finished!");
Task.ContinueWith 线程延续 :如果线程中的结果 需要再后续使用 使用线程延续
//线程延续1
Task<int> task10 = Task<int>.Run(() =>
{
for (int i = 0; i < 5; i++)
{
Thread.Sleep(1000);
Console.WriteLine(i);
}
return 30;
});
task10.ContinueWith(tas =>
{
//tas 代表 Task<int> task10
//获取task10的返回值结果
Console.WriteLine("task10任务执行完毕");
Console.WriteLine(tas.Result);
});
Console.WriteLine("11");
// 线程延续2
Task task11 = Task.Run(() =>
{
Console.WriteLine("1");
});
var result = task11.ContinueWith<string>(tas =>
{
Console.WriteLine("2");
Task task1 = Task.Run(() =>
{
Console.WriteLine("3");
});
Console.WriteLine("4");
return "This is task result!";
});
Console.WriteLine(result.Result);
延续3
Task<float> task12 = Task.Run(() =>
{
return 10.0f;
});
var task13 = task12.ContinueWith<int>(tas =>
{
10.0f;
Console.WriteLine(tas.Result);
return (int)(tas.Result + 10);
});
Console.WriteLine(task13.Result);
task线程取消 方法
static void Main(string[] args)
{
//1.初始化线程取消类
var tokenSource = new CancellationTokenSource();
//2.获取线程取消标记
var token = tokenSource.Token;
//3.开启task线程 并且绑定取消线程标记
var task = Task.Run(() =>
{
for (var i = 0; i < 1000; i++)
{
Thread.Sleep(1000);
//是否执行取消方法 如果取消 为true 反之为 false
if (token.IsCancellationRequested)
{
Console.WriteLine("Abort mission success!");
return;
}
}
}, token);
//取消线程后回调方法
token.Register(() =>
{
Console.WriteLine("Canceled");
});
Console.WriteLine("Press enter to cancel task...");
Console.ReadKey();
//取消线程方法
tokenSource.Cancel();
Console.ReadKey();
}
异步侧重于任务的执行顺序,而多线程则是关于多个线程如何并发执行。应该说多线程是实现异步的常用手段,但不能说他们是一回事。即便是只有一个线程的情况下,我们仍然可以实现异步
异步和同步
异步:表示执行某项操作之后不等待操作结束,但可以在操作结束后收到通知
同步反之
1. net5.0推出了async/await async/await特性是与Task紧密相关的
2.async 是“异步”的简写,sync 是“同步”的简写
await 是 async wait 的简写。await 用于等待一个异步方法执行完成
//如何通过使用async/await 完成异步编程
//1. async 必须修饰方法 被修饰的方法 表示是一个异步方法
//2.async 和await必须连用 如果不使用await 那么这个方法还是同步方法
//3.async 描述的方法 的返回值类型必须是void 或者是task 或者task<T>
//4.await 描述的也是方法 但是必须是使用线程(task)的方法
//5.Async方法在执行的时候,开始是以同步的方式执行,直到遇到await关键字,
//从await关键字开始,C#会另起一个线程执行 async方法之外的其他代码
不使用async/await 完成下载逻辑,则代码执行顺序时混乱的,不符合业务逻辑:
internal class Program
{
static void Main(string[] args)
{
DownloadHandle();
Console.ReadLine();
}
/// <summary>
/// 1. 通知用户下载开始
/// 2. 提示下载完成
/// 3. 开始下载
/// </summary>
public static void DownloadHandle()
{
Console.WriteLine("下载开始!->主线程ID:" + Thread.CurrentThread.ManagedThreadId);
Download();
Console.WriteLine("下载完成!->主线程ID:" + Thread.CurrentThread.ManagedThreadId);
}
/// <summary>
/// 下载
/// </summary>
/// <returns></returns>
public static Task Download()
{
return Task.Run(() =>
{
Console.WriteLine("下载线程ID:->" + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("10%");
Console.WriteLine("30%");
Console.WriteLine("50%");
Console.WriteLine("60%");
Console.WriteLine("80%");
Console.WriteLine("99%");
Console.WriteLine("100%");
});
}
}
使用async/await 完成下载逻辑,则代码执行顺序正常,符合业务逻辑:
internal class Program
{
static void Main(string[] args)
{
DownloadHandle();
Console.ReadLine();
}
/// <summary>
/// 模拟下载
/// </summary>
public static async void DownloadHandle()
{
Console.WriteLine("下载开始!->主线程ID:" + Thread.CurrentThread.ManagedThreadId);
await Download();
Console.WriteLine("下载完成!->主线程ID:" + Thread.CurrentThread.ManagedThreadId);
}
/// <summary>
/// 下载
/// </summary>
/// <returns></returns>
public static Task Download()
{
Console.WriteLine("下载线程ID:->" + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("10%");
Console.WriteLine("30%");
Console.WriteLine("50%");
Console.WriteLine("60%");
Console.WriteLine("80%");
Console.WriteLine("99%");
Console.WriteLine("100%");
return Task.CompletedTask;
}
winform使用BeginInvoke 和await 完成数据加载(后续讲)
//BeginInvoke
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
//接收缓冲区数据的字节数
int size = serialPort1.BytesToRead;
//动态创建数组接收数据
byte[] buffer = new byte[size];
//读取数据
serialPort1.Read(buffer, 0, buffer.Length);
string msg = Encoding.Default.GetString(buffer);
//异步执行,
//txtReceive.BeginInvoke(msgDelegate, msg);
txtReceive.BeginInvoke(new Action<string>(str =>
{
txtReceive.Text = str;
}), msg);
}
private async void serialPort1_DataReceived1(object sender, SerialDataReceivedEventArgs e)
{
//await异步执行
var t = Task.Run(() =>
{
//接收缓冲区数据的字节数
int size = serialPort1.BytesToRead;
//动态创建数组接收数据
byte[] buffer = new byte[size];
//读取数据
serialPort1.Read(buffer, 0, buffer.Length);
string msg = Encoding.Default.GetString(buffer);
return msg;
});
string str = await t;
txtReceive.Text = str;
}