1.进程和线程
一般情况下,一个应用程序下有一个进程,有好几个线程
在一个进程中有多个线程,这些线程共享进程的内存空间。
在进程中通过互斥锁,防止多个线程同一时间读写某一块内存区域。
使用信号量保证多个线程不会相互冲突
通过委托开启一个线程,一般一个比较耗时的操作,开启单独的线程去执行,比如下载
static void Test()
{
Console.WriteLine("test");
}
static void Main(string[] args)
{
Action a = Test;
a.BeginInvoke(null,null);
Console.WriteLine("main");
Console.ReadKey();
}
这里委托开启的线程和Main是异步执行的
传递一个参数
static void Test(int i)
{
Console.WriteLine(i+" test");
}
static void Main(string[] args)
{
Action a = Test;
a.BeginInvoke(100,null,null);
Console.WriteLine("main");
Console.ReadKey();
}
IAsyncResult可以取得当前线程的状态
static int Test(int i)
{
Console.WriteLine(i+" test");
Thread.Sleep(10);//让当前线程休眠 单位ms
return 66;
}
static void Main(string[] args)
{
Func a = Test;
IAsyncResult ar = a.BeginInvoke(100,null,null);
Console.WriteLine("main");
while(ar.IsCompleted == false)
{
Console.Write(".");
}
int res = a.EndInvoke(ar);
Console.WriteLine(res);
Console.ReadKey();
}
通过BeginInvoke方法开启一个线程,通过EndInvoke结束线程,线程结束方法才能有返回值
2.检测委托线程的结束-通过句柄和回调函数
通过句柄检测线程结束
ar获得一个AsyncWaitHandle等待的句柄,调用WaitOne方法,等待1000毫秒,超时返回false。
bool isEnd = ar.AsyncWaitHandle.WaitOne(1000);//1000表示超时时间,如果等待了1000线程还没有结束,返回false。
if(isEnd)
{
int res = a.EndInvoke(ar);
Console.WriteLine(res);
}
通过回调函数检测线程结束
将回调函数作为BeginInvoke参数传递,最后一个参数为给回调函数传递数据的
在回调函数中通过ar.AsyncState获取传过来的数据(传入的委托a)
static void Main(string[] args)
{ //通过回调函数检测线程结束
Func a = Test;
IAsyncResult ar = a.BeginInvoke(100,OnCallBack,a);
Console.ReadKey();
}
static void OnCallBack(IAsyncResult ar)
{
Console.WriteLine("子线程END");
Func a = ar.AsyncState as Func;
int res = a.EndInvoke(ar);
Console.WriteLine(res + "在回调函数中取得结果");
}
Func a = Test;
//IAsyncResult ar = a.BeginInvoke(100,OnCallBack,a);
a.BeginInvoke(100, ar =>
{
int res = a.EndInvoke(ar);
Console.WriteLine(res + "lambda表达式");
},null);
通过Lambda表达式来结束
3.线程开启:通过Thread类
class Program
{
static void DownloadFile()
{
Console.WriteLine("开始下载"+Thread.CurrentThread.ManagedThreadId);//获取线程ID
Thread.Sleep(2000);
Console.WriteLine("END");
}
static void Main(string[] args)
{
//创建出来Thread,这个线程并没有启动
Thread t = new Thread(DownloadFile);
t.Start();
Console.WriteLine("Main");
Console.ReadKey();
}
}
通过Lambda表达式开启线程
Thread t = new Thread(() =>
{
Console.WriteLine("开始下载 " + Thread.CurrentThread.ManagedThreadId);//获取线程ID
Thread.Sleep(2000);
Console.WriteLine("END");
});
t.Start();
通过创建对象来创建线程
先定义一个类
再创建一个这个类的对象,再构造一个thread对象的时候,可以传递一个静态方法也可以传递一个普通的方法。
class MyThread
{
private string filename;
private string filepath;
public MyThread(string fileName,string filePath)
{
this.filename = fileName;
this.filepath = filePath;
}
public void DownFile()
{
Console.WriteLine("开始下载" + filepath + filename);
Thread.Sleep(2000);
Console.WriteLine("End");
}
}
MyThread my = new MyThread("xxx.bt", "www.baidu.com");
Thread t = new Thread(my.DownFile);
t.Start();
4.线程中的后台线程和前台线程
只要存在没有结束的前台线程,应用程序的进程就没有结束
默认情况下Thread类创建的线程是前台线程,线程池中的线程是后台线程
如果所有前台线程已经结束,那么后台线程会被终止
通过线程的IsBackground方法,将线程设置为后台线程
Thread t = new Thread(DownloadFile);
t.IsBackground = true;
t.Start("Loading");
线程的优先级
线程会根据优先级来执行
控制线程
Running或者Unstarted。再调用了Start方法后,都能带操作系统的线程调度器选择要运行得线程,
这个线程才会冲Unstarted修改为Running。使用Thread.Sleep()方法可以让当前线程休眠进入WaitSleepJoin状态
Abort方法会停止线程,这个方法会在要终止得线程抛出一个ThreadAbortException得异常,我们可以通过try catch来处理这个以常,
然后再线程结束进行一些清理工作。
如果需要等待线程得结束,可以调用Join方法,表示吧Thread加入进来,停止当前线程,并把它设置为WaitSleepJoin得状态,直到加入的线程完成为止。
5.线程池
Thread Pool一个线程池中有事先创建好的线程。使用线程池做一些小任务,创建出来的线程默认是后台线程。
线程池开启线程得方法要传递一个参数
class Program
{
static void ThreadMethod(object state)
{
Console.WriteLine("线程开始"+Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(2000);
Console.WriteLine("线程结束");
}
static void Main(string[] args)
{
//开启一个工作线程
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
ThreadPool.QueueUserWorkItem(ThreadMethod);
Console.ReadKey();
}
}
6.任务
给任务传递一个方法让他执行
任务开启一个线程传递的方法不能带有参数
class Program
{
static void ThreadMethod()
{
Console.WriteLine("任务开始");
Thread.Sleep(2000);
Console.WriteLine("任务结束");
}
static void Main(string[] args)
{
Task t = new Task(ThreadMethod);
t.Start();
Console.WriteLine("Main");
Console.ReadKey();
}
}
或者通过TaskFactory工厂模式开启一个任务,返回一个Task对象。
TaskFactory tf = new TaskFactory();
Task t = tf.StartNew(ThreadMethod);
连续任务
如果任务t1的执行是依赖于另一个任务t2的,那么就需要再这个任务t2执行完毕后才开始执行t1.这个时候我们可以使用连续任务
任务层次结构
我们在一个任务中启动另一个新的任务,相当于新的任务是当前任务的子任务,两个任务异步执行,如果父任务执行完了但是子任务没有执行完
他的状态会设置为WaitingForChildrenToComplete,只有子任务也执行完了,父任务的状态就变成RunToCompletion
7.线程问题-争用条件和死锁
eg:
创建一个类,定义一个方法,先将state++,再判断当state == 5 的时候打印state =5.
class MyThreadObject
{
private int state = 5;
public void ChangeState()
{
state++;
if(state == 5)
{
Console.WriteLine("state =5");
}
state = 5;
}
}
在main函数中开启两个线程,这两个线程都是循环调用MyThreadObject中的方法。
class Program
{
static void ChangeState(object o)
{
MyThreadObject m = o as MyThreadObject;
while (true)
{
m.ChangeState();
}
}
static void Main(string[] args)
{
MyThreadObject m = new MyThreadObject();
Thread t = new Thread(ChangeState);
t.Start(m);
Thread d = new Thread(ChangeState);
d.Start(m);
Console.ReadKey();
}
}
结果输出了很多state = 5
两个线程修改同一个属性,但是两个线程不同步,这样这个属性的值,就不能确定了。
解决这个问题要给当前对象加一个锁
修改代码
使用Lock锁定m,这样如果使用m,就会锁定m,如果其他线程也调用m,就会等待m解除锁定才能使用m。
static void ChangeState(object o)
{
MyThreadObject m = o as MyThreadObject;
while (true)
{
lock (m)
{
m.ChangeState();
}
}
}
死锁
两个线程都调用了两个属性。但是他们对属性的锁的先后顺序不一样,这样一个线程锁一个属性。两个线程都申请不到另一个属性。
形成了死锁。
在编程的时候设计好锁定的顺序。避免出现死锁的情况。