进程与线程的一个简单解释 - 阮一峰的网络日志 进程和线程的理解
进程 主线程和分线程关系
一个程序就是一个进程,然而进程里面包含若干个线程,而每个进程里面都有一个(可以说必须要有一个)线程,这个线程就是主线程,然而主线程有一天发现自己的工作太多了,在规定的时间内完不成工作,这时候他就召唤了一个小弟(子线程)帮他,他给小弟分配了一些任务,当小弟做完了分配给他的任务后,他就把小弟赶走了!这就是主线程和子线程。
有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");
线程阻塞 阻塞主线程 t.Join()
Thread t = new Thread(() => { for (int i = 0; i < 1000; i++) Console.Write("y"); });
t.Start();
t.Join();//线程阻塞
Console.WriteLine("Thread t has ended!");
线程抢占 如果两个线程同时对某个资源进行同时访问 就可能出现 线程抢占
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和Thread一样,位于System.Threading命名空间下
//2.Thread每次都会创建新线程,线程的创建和销毁是一个开销比较大的操作,Task每次执行将不会立即创建一个新线程,而是到CLR线程池查看是 否有空闲的线程,有的话就取一个线程处理这个请求,处理完请求后再把线程放回线程池,这个线程也不会立即撤销,而是设置为空闲状态,可供线程池再次调度, 从而减少开销。
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> task4 = new Task<int>(() => {
return 1111;
});
task4.Start();
task4.ContinueWith(tas =>
{
Console.WriteLine("task finished!");
//获取task4 完成后的返回值结果
Console.WriteLine(tas.Result);
});
例子2:一个Task完成后自动启动下一个Task,使用Task的延续
static void Main(string[] args)
{
Task task = Task.Run(() => {
Thread.Sleep(3000);
Console.WriteLine("1");
});
var result = task.ContinueWith<string>(tas =>
{
Console.WriteLine("task finished!");
Task task1 = Task.Run(() =>
{
Thread.Sleep(3000);
Console.WriteLine("2");
});
return "This is task 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#会另起一个线程执行await后面的代码。
不使用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;
}
async/await winform异步使用 :
private async void button1_Click(object sender, EventArgs e)
{
var t = Task.Run(() =>
{
Thread.Sleep(5000);
return "Hello";
});
string text = await t;
this.Text = text;
}
单击按钮后,程序执行到 string text = await t 会直接返回到调用方法的地方继续执行,t方法异步执行返回后为text赋值并继续执行执行 this.Text = text(回调在主线程执行),效果就是点击按钮后界面不会卡死,方法执行完后 this.Text 更改。
寥寥几行就搞定了,不用再多写那么多函数,使用起来也很灵活。最让人头疼的跨线程修改控件的问题完美解决了,再也不用使用Invoke了,因为修改控件的操作压根就是在原来的线程上做的,还能不阻塞UI。
//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;
}