进程和线程
线程没有父子关系,任务有父子关系,
1.每一个进程的内存空间是可以共享的,每一个线程都可以使用这些内存空间
2.互斥锁(Mutual exclusion),缩写Mutex,允许一个线程读写某一块内存区域,
防止多个线程同时读写某一块内存区域
3.信号量,允许固定数目的线程读取某一块内存区域,保证多个线程不会互相冲突,
防止固定数目的线程之外的其他线程同时读取某一块内存区域
4.2可以用3代替,但2效率高
线程开启方式一 -委托
代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace 线程和进程
{
class Program
{
//一般我们会对比较耗时的操作开启单独的线程去执行,比如下载操作
static int Text(int a)
{
Console.WriteLine("这是一个有返回值,有参数的线程Text " + a);
Thread.Sleep(100);//让当前线程休眠(暂停线程的执行)
return 90;
}
static void Text1(int a)
{
Console.WriteLine("这是一个无返回值,有参数的线程Text1 " + a);
}
static void Text2()
{
Console.WriteLine("这是一个无返回值,无参数的线程Text2 ");
}
static void Main(string[] args)//在main线程中执行,一个线程里面的语句是从上到下执行的
{
//通过一个委托启动一个无返回值,有参数的线程
Action<int> a2 = Text1;
a2.BeginInvoke(20, null, null);//输出值
//通过一个委托启动一个无返回值,无参数的线程
Action a3 = Text2;
a3.BeginInvoke(null, null);//输出值
//通过一个委托开启一个有返回值,有参数的线程
Func<int,int> a = Text;
//IAsyncResult 取得当前线程的状态
IAsyncResult ia = a.BeginInvoke(100,null, null);//开启一个新的线程去执行a所引用的方法,//输出值,不输出返回值
//可以认为线程是同步执行的(异步执行)
Console.WriteLine("main");
//1.第一种办法检测线程结束
while (ia.IsCompleted == false)//当前线程没有执行完毕
{
Console.Write(".");
Thread.Sleep(10);//控制检测的频率
}
int res1 = a.EndInvoke(ia);//取得异步线程的返回值
Console.WriteLine("通过IsCompleted取得返回值" + res1);
//2.第二种办法检测线程结束
bool isEnd = ia.AsyncWaitHandle.WaitOne(1000);//1000毫秒表示超时时间,如果等待了1000毫秒线程好没有结束,这个
//方法返回false,在1000毫秒内结束了,返回true
if (isEnd)
{
int res2 = a.EndInvoke(ia);
Console.WriteLine("取得异步线程的返回值" + res2);//取得异步线程的返回值
}
//3.第三种办法检测线程结束,通过回调
//倒数第二个参数是一个委托类型的参数,表示回调函数,线程结束的时候会调用这个委托指向的方法,倒数第一个参数
//用来给回调函数传递参数
IAsyncResult ia1 = a.BeginInvoke(100, OnCallBack, a);//开启一个新的线程去执行a所引用的方法 //输出值,不输出返回值
//4.通过lambda表达式使用回调函数检测线程结束
a.BeginInvoke(100, ar=>
{
int res4 = a.EndInvoke(ar);
Console.WriteLine("通过lambda表达式回调函数取得返回值" + res4);
}, null);//开启一个新的线程去执行a所引用的方法 //输出值,不输出返回值
Console.ReadKey();
}
static void OnCallBack(IAsyncResult ia1)
{
Func<int, int> a = ia1.AsyncState as Func<int, int>;
int res3 = a.EndInvoke(ia1);
Console.WriteLine("通过回调函数取得返回值" + res3);//取得异步线程的返回值
}
}
}
线程开启方式二 -Thread
通过Thread调用的线程,如果有参数,参数得是Object类型的
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace 线程开启方式2
{
class Program
{
static void Text1()
{
Console.WriteLine("开始下载");
Thread.Sleep(3000);
Console.WriteLine("下载完成");
}
static void Text2(object o)
{
Console.WriteLine("开始下载:{0}",o);
Thread.Sleep(3000);
Console.WriteLine("下载完成");
}
static void Main(string[] args)
{
Thread thread0 = Thread.CurrentThread;//获取当前线程
Console.WriteLine("输出主线程的信息:");
Console.WriteLine("ID:{0}", thread0.ManagedThreadId);//线程ID
Console.WriteLine("Name:{0}", thread0.Name);//当前线程的名字
Console.WriteLine("IsBackground:{0}", thread0.IsBackground);//前台线程还是后台线程
Console.WriteLine("Priority:{0}", thread0.Priority);//线程优先级
//开启无返回值,无参数的线程
Thread thread = new Thread(Text1);//创建thread对象,这个线程还没有启动
thread.Start(); //执行线程
//开启无返回值,有参数的线程
Thread thread2 = new Thread(Text2);//创建thread对象,这个线程还没有启动
thread2.Start("xxx.种子"); //执行线程
//
Download d = new Download("yy.zip", "www.baidi.com");
Thread thread3 = new Thread(d.Show);
thread3.Start();
Console.ReadKey();
}
}
}
Download.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace 线程开启方式2
{
class Download
{
public string fileName;
public string filePath;
public Download() { }
public Download(string name,string path)
{
this.fileName = name;
this.filePath = path;
}
public void Show()
{
Console.WriteLine("下载开始:{0},下载的路径:{1}",fileName,filePath);
Thread.Sleep(3000);
Console.WriteLine("下载完成");
}
}
}
线程的其他概念(后台和前台,优先级,线程的状态)
后台线程和前台进程:
默认情况下,Thread创建的线程都是前台线程。线性池总是后台线程。
前台线程运行完毕后,如果后台线程还在运行,所有的后台线程都降被终止。
控制线程
Thread thread = new Thread(Text2);//这个线程是前台线程
//thread.IsBackground = true;//设置为后台线程
thread.Start("hh.zip");
thread.Abort();//终止线程
//t.Join();让当前thread线程睡眠,等待t线程执行完,然后继续运行thread线程以及下面的代码
Console.ReadKey();
线程开启方式三 -线程池
线性池总是后台线程。
不能把入池的线程改为前台线程
不能设置入池的线程的优先级和名称
入池的线程只能用于时间比较短的任务,如果一直运行(如拼写检查器),就应该用Thread类创建一个线程。
代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace 线程开启方式2
{
class Program
{
static void ThreadMethod(object o)
{
Console.WriteLine("线程开始"+Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(3000);
Console.WriteLine("线程结束");
}
static void Main(string[] args)
{
ThreadPool.QueueUserWorkItem(ThreadMethod);//调用的线程得有一个参数,开启一个工作线程,(ThreadMethod,参数)。
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
Console.ReadKey();
}
}
}
运行截图
线程开启方式四 -任务
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace 线程开启方式2
{
class Program
{
static void ThreadMethod()
{
Console.WriteLine("任务开始");
Thread.Sleep(3000);
Console.WriteLine("任务结束");
}
static void Main(string[] args)
{
//任务开启的第一种方式
//Task t = new Task(ThreadMethod);//传递一个需要线程去执行的方法
//t.Start();
//任务开启的第二种方式
TaskFactory tf = new TaskFactory();
Task t = tf.StartNew(ThreadMethod);
Console.WriteLine("main");
Console.ReadKey();
}
}
}
连续任务、任务层次结构
我们在一个任务中启动一个新的任务,相当于新的任务是当前任务的子任务,两个任务异步执行,如果父任务执行完了但是子任务没有执行完,它的状态会设置为WaitingForChildrenToComplete,只有子任务也执行完了,父任务的状态就变成RunToCompletion
连续任务代码:
static void DoFirst(){
Console.WriteLine("do in task : "+Task.CurrentId);
Thread.Sleep(3000);
}
static void DoSecond(Task t){
Console.WriteLine("task "+t.Id+" finished.");
Console.WriteLine("this task id is "+Task.CurrentId);
Thread.Sleep(3000);
}
Task t1 = new Task(DoFirst);
Task t2 = t1.ContinueWith(DoSecond);//t2在t1任务完成之后执行
Task t3 = t1.ContinueWith(DoSecond);//t3在t1任务完成之后执行
Task t4 = t2.ContinueWith(DoSecond);//t4在t2任务完成之后执行
任务层次结构代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace 线程开启方式2
{
class Program
{
static void ParentTask()
{
Console.WriteLine("task id " + Task.CurrentId);
var child = new Task(ChildTask);
child.Start();
Thread.Sleep(1000);
Console.WriteLine("parent started child , parent end");
}
static void ChildTask()
{
Console.WriteLine("child");
Thread.Sleep(5000);
Console.WriteLine("child finished ");
}
static void Main(string[] args)
{
var parent = new Task(ParentTask);
parent.Start();
Thread.Sleep(2000);
Console.WriteLine(parent.Status);
Thread.Sleep(4000);
Console.WriteLine(parent.Status);
Console.ReadKey();
}
}
}
线程问题:争用和死锁
解决死锁的办法:在编程的开始设计阶段,设计锁定顺序
MyThreadObject.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 线程开启方式2
{
class MyThreadObject
{
private int state = 5;
public void ChangeState()
{
state++;
if (state ==5)
{
Console.WriteLine("state=5");
}
state = 5;
}
}
}
Program.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
namespace 线程开启方式2
{
class Program
{
static void ChangeState(object o)
{
MyThreadObject m = o as MyThreadObject;
while (true)
{
lock (m)//向系统申请锁定m对象,如果m对象没有被锁定,那么可以,如果
//m对象被锁定了,那么这个语句会暂定,直到申请到m对象,锁定一个引用类型,不能锁定值类型
{
m.ChangeState();//同一时刻,只有一个线程在使用这个方法
}//释放对m的锁定
}
}
static void Main(string[] args)
{
MyThreadObject m = new MyThreadObject();
Thread t = new Thread(ChangeState);
t.Start(m);
new Thread(ChangeState).Start(m);
Console.ReadKey();
}
}
}