线程和进程
在main线程中执行,一个线程里面的语句的执行是从上到下的
异步委托
Action a = Test;
a.BeginInvoke();//开启一个新的线程去执行a所引用的方法
可以认为线程是同时执行的(异步执行)
注意,net5.0和net code都不支持BeginInvoke,需要编辑项目文件,把TargetFramework标签的net5.0改为net45
IAsyncResult用于取得当前线程的状态
一般会为比较耗时的操作开启线程去单独执行
等待线程
bool isEnd = ares.AsyncWaitHandle.WaitOne(1000);//等待线程结束,括号参数是超时时间,如果超过返回false,反之返回true
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace learn
{
class Program
{
static void Main(string[] args)
{
//Func<int, int> a = Test;
//IAsyncResult ares = a.BeginInvoke(100, null, null);//开启一个新的线程去执行a所引用的方法
//注意,net5.0和net code都不支持BeginInvoke,需要编辑项目文件,把TargetFramework标签的net5.0改为net45
//IAsyncResult类型用于取得线程的状态。
//Console.WriteLine("main");
//while (ares.IsCompleted == false)//false代表这个线程没结束
//{
// Console.Write(".");
// Console.Write("\u0008");
// Thread.Sleep(1000);
//}
//var res = a.EndInvoke(ares);//取得线程的返回值
//Console.WriteLine(res);
//等待线程
//bool isEnd = ares.AsyncWaitHandle.WaitOne(1000);//等待线程结束,括号参数是超时时间,如果超过返回false,反之返回true
//if (!isEnd)
//{
// Console.WriteLine("超时了");
//}
//else
//{
// Console.WriteLine("没超时");
// var res = a.EndInvoke(ares);
// Console.WriteLine(res);
//}
//通过回调 检测线程结束
Func<int, int> a = Test;
//a.BeginInvoke(121, CallBack, a);
//倒数第二个参数是一个委托参数,表示一个回调函数,当线程结束的时候会调用这个委托指向的方法
//最后一个参数用来给回调函数传递数据
//Lambda表达式
a.BeginInvoke(121, ar =>
{
var res = a.EndInvoke(ar);
Console.WriteLine(res);
}, null);//因为可以访问到本地的a委托,所以这里就不需要传递
Console.WriteLine();
Console.ReadKey();
}
static int Test(int i)
{
Console.WriteLine(i);
Thread.Sleep(1000);
return i + 2;
}
static void CallBack(IAsyncResult ar)
{
Func<int, int> a = ar.AsyncState as Func<int, int>;
//这里as Func<int, int>代表把ar.AsyncState 转换成Func<int,int>类型
//ar.AsyncState可以是任何一种数据
var res = a.EndInvoke(ar);
Console.WriteLine(res);
}
}
}
Thread类
使用Thread创建一个对象,在构造函数里写上启动的线程方法
thread对象传递参数需要使用object类型
static void Test1(object a)
//thread对象传递参数需要使用object类型
{
Console.WriteLine("传入参数" + a);
}
Abort方法可以终止线程
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace learn
{
class Program
{
static void DownLoadFile()
{
Console.WriteLine("当前线程ID:" + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("正在下载文件");
}
static void Test1(object a)
//thread对象传递参数需要使用object类型
{
Console.WriteLine("传入参数" + a);
}
static void Main(string[] args)
{
//Thread t = new Thread(DownLoadFile);
创建出来的thread对象,这个线程并没有启动
//t.Start();//表示开始执行线程
//Thread t = new Thread(() =>//没有任何参数,直接使用括号
//{
// Console.WriteLine("开始下载" + Thread.CurrentThread.ManagedThreadId);
// Thread.Sleep(1000);
// Console.WriteLine("下载完成");
//});//lambada写法
//Thread t1 = new Thread(Test1);
//t1.Start("www");
//Thread t2 = new Thread(Test1);
//t2.Start(1);
//Console.WriteLine("END");
MyThread my = new MyThread(1, "li");
Thread t = new Thread(my.OutPut);
t.Start();
Thread.Sleep(1000);
t.Abort();//Abort方法可以终止线程
//我们构造一个thread对象的时候可以传递一个静态方法,也可以传递一个对象的普通方法
//Console.ReadKey();
}
}
class MyThread
{
private int id;
private string name;
public MyThread(int id, string name)
{
this.id = id;
this.name = name;
}
//提供一个线程方法
public void OutPut()
{
Console.WriteLine("MyId:" + id);
Thread.Sleep(3000);
Console.WriteLine("MyName:" + name);
}
}
}
两种传递参数方法,一种在线程方法里object一个变量传递参数,一种通过类存储数据然后把线程方法写在类里
后台线程和前台线程
Thread是前台线程,线程池中的线程总是后台线程。
join一般在线程中写入
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace learn
{
class Program
{
static void Main(string[] args)
{
Thread t = new Thread(Test1);
//t.IsBackground = true;//设置该进程是否为后台进程
Thread t1 = new Thread(Test2);
t1.Start();
//t.Start();
//t.Join();//当前线程睡眠,等待t线程执行完,再运行下面的代码
}
static void Test1()
{
//虽然设置是前台进程,但其附属进程被关闭
Console.WriteLine("开始");
Thread.Sleep(4000);
Console.WriteLine("结束");
}
static void Test2()
{
int i = 0;
while (i != -1)
{
Console.Write("#");
Thread.Sleep(1000);
Console.Write("\u0008");
if (i == 5)
{
Thread t = new Thread(Test1);
t.Start();
t.Join();//它会暂时休眠Test2的线程,然后启动Test1的线程
}
i++;
}
}
}
}
线程池
线程池创建出来的线程默认后台线程
线程池的线程是不能修改成前台线程的
不能修改线程当中的优先级和名字
线程池的线程只能用于时间较短的任务。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace learn
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("主线程:{0}", Thread.CurrentThread.ManagedThreadId);
ThreadPool.QueueUserWorkItem(Test1);//用线程池去运行线程必须带有一个参数
ThreadPool.QueueUserWorkItem(Test1);
ThreadPool.QueueUserWorkItem(Test1);
ThreadPool.QueueUserWorkItem(Test1);
ThreadPool.QueueUserWorkItem(Test1);
Console.ReadKey();//因为线程池的都是后台线程
}
static void Test1(object s)
{
Console.WriteLine("线程ID:" + Thread.CurrentThread.ManagedThreadId + "开始");
//Thread.CurrentThread获取当前线程
//.ManagedThreadId获取当前线程ID
Thread.Sleep(500);
Console.WriteLine("线程ID:" + Thread.CurrentThread.ManagedThreadId + "结束");
}
}
}
任务
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace learn
{
class Program
{
static void Main(string[] args)
{
//1
//Task t = new Task(Test);
传递一个需要线程去执行的方法
//t.Start();
//2
TaskFactory tf = new TaskFactory();
//任务工厂
Task t = tf.StartNew(Test);
//会返回一个任务对象
Console.ReadKey();
}
static void Test()
{
Console.WriteLine("任务开始");
Thread.Sleep(1000);
Console.WriteLine("任务结束");
}
}
}
连续任务
使用ContiithWith方法使进程依赖形成连续的任务
任务层次结构
这里的意思就是,在一个任务1启动任务2,任务2就是任务1的子任务,而且如果任务1执行完了任务2还没执行完,任务1会等着任务2执行完后再结束进程。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace learn
{
class Program
{
static void Main(string[] args)
{
//1
//Task t = new Task(Test);
传递一个需要线程去执行的方法
//t.Start();
//2
TaskFactory tf = new TaskFactory();
//任务工厂
Task t = tf.StartNew(Test);
//Task t2 = tf.StartNew(Test1, null);
因为ContinueWith必须传递一个参数过去
所以Test1定义了一个object变量,这里需要传递,所以写个null
//Task t3 = t2.ContinueWith(Test1);
//Task t4 = t2.ContinueWith(Test1);
//会返回一个任务对象
Console.ReadKey();
}
static void Test()
{
Console.WriteLine("任务" + Task.CurrentId + "开始");
Thread.Sleep(1000);
Task t = new Task(Test1, null);
Console.WriteLine("执行子任务");
t.Start();
Console.WriteLine("任务" + Task.CurrentId + "结束");
}
static void Test1(object s)
{
Console.WriteLine("任务" + Task.CurrentId + "开始");
Thread.Sleep(1000);
Console.WriteLine("任务" + Task.CurrentId + "结束");
}
}
}
线程问题-争用条件和死锁
争用条件
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace learn
{
class Program
{
static void Main(string[] args)
{
Mythread mythread = new Mythread();
Thread t = new Thread(ChangeId);
t.Start(mythread);//传递对象到ChangeId方法
//Thread t2 = new Thread(ChangeId);
//t2.Start(mythread);
}
static void ChangeId(object r)
{
Mythread t = r as Mythread;
while (true)
{
t.ChangeId();
}
}
}
class Mythread
{
private int id = 1;
public void ChangeId()
{
id++;
if (id == 5)
{
Console.WriteLine("ID=5");
}
id = 5;
}
}
}
单个线程的时候,预料之中,控制面板没有任何显示
双线程时,则会发现本不应该出现的东西出现了
因为两个线程再同时执行一个方法。
当一个线程执行到if(id == 5)且另一个线程执行到id = 5,这时候id的值就为5,因为两个线程使用的变量的共用的。
解决方法:加锁
向系统申请锁定对象,如果对象没被锁定,就会被锁定然后继续代码,如果没被锁定,就会暂停程序一直等到可以锁定
lock (t)
{
t.ChangeId();
}
死锁
如图所示
两个方法,他们都向系统申请了s1和s2的锁定请求,而且是嵌套的申请
某个时间内,两个线程一个先申请s1并通过并锁定,另一个申请s2通过并锁定
当第一个再去申请s2的时候,因为s2已经被锁定了无法通过,与此同时,另一个线程去
申请s1也是同样被锁定无法通过,两线程都是互相申请锁定被对方锁定的对象,导致程序卡死
也就是死锁。
如何解决死锁
在编程的开始阶段,设计锁定顺序
当某组对象可能会被线程同时访问,那么,此时的锁定顺序必须按照相同的锁定顺序来。