本章导读
基础篇
1:早晨一贴(为什么我们需要用多线程)。
2:怎样创建最基本的线程。
3:课后作业。
提高篇
1:中午一贴(提高我们对线程的了解。何为线程同步)。
2:线程同步的一些例子
3:课后作业。
高级篇
1:晚间一贴(怎么应用至我们编写的软件)。
2:Delegate异步线程与UI设计。
3:高级示例应用
基础篇
1:早晨一贴
记得上个星期六妈妈对我说:”小婷,昨天妈加班太迟了所以衣服没洗,你早晨的时候把洗了,然后去菜场买菜,中午妈可能回来的比较迟,把午饭做好,然后你下午自由活动吧.”不会吧,自己是个瞌睡虫,早晨都喜欢睡懒觉的.
算9点起床吧,家里三个人的衣服都是我洗,起码需要1个小时,玩会电脑需要1个小时,骑车去菜场买菜来回需要40-50分钟,做饭需要大约1个小时,天呀,一个早晨的时间就这么没了,多不划算。心里在嘀咕着如果能少花点时间就好了,幸好可以用洗衣机洗衣服,曾洗衣服的时候把饭做好,去菜场买菜,呵呵,那启不是可以节约很多时间呀,如果是一个工作接着一个工作做,那真是太浪费时间呢。读者们,听我唠叨了这么多会不会觉得很纳闷呀?这和线程有什么瓜葛,我们花钱买的是技术书,不是买的读者,知音等书刊??其实这正是我们现实生活中自己所想节约时间的办法,其实它就相当于我们程序语言的多线程(请个钟点工帮我一起做事,呵)。
2:怎样创建最基本的线程
一般我们用线程的时候需要引用System.Threading命名空间,里面包括我们绝大部分都用到的线程类Thread。我认为它就是我免费的钟点工.呵
下面是一个很简单的线程例子
程序输出结果为:Hello C#.是不是很简单了?其实.net 2.0之后,我们可以借助匿名方法使代码更简单化一点,下面是代码
代码确实少很多了,事实上在C#2.0里我们甚至可以不必写出ThreadStart委托,那么代码将会变
如下如示
真的很简便了,这样的代码你愿意写吗?
例子写好了,大家可以仿照上面的代码自己在VS里写一下然后编译看看,下面主要讲讲Thread类的一些用法.常用的有
Thread.Suspend() //挂起线程(使线程暂时停止)
Thread.Resume() //使挂起的线程开始工作
Thread.Abort() //使线程永远的停止
Thread.Join() //线程之间的插入
Thread.Start() //开始执行线程
这些方法比较简单,我重点讲解一下Join的用法,代码如下:
程序运行结果为bababababababababababababaaaaaaaaaaaaaccccccccccccccccccccccccccccccccccccccccccc,大家看出什么端倪了吗?对呢,一开始两个线程是同时运行的,当线程运行至Join的时候,那么线程bb将会等到线程tt全部执行完之后才执行线程bb下面的语句。
备注:基本的线程就讲到这里了,另外大家还需要了解的就是线程的状态,它有正在运行,已经结束等等,
名称为ThreadState枚举,线程级别名称为Priority枚举等,这些都是比较简单的话题,在这就不重复了。
3:课后作业。
大家试着用多线程编写一些小例子,把我们所讲的线程的停止,恢复,状态,级别等功能都做进去看看。
提高篇
1:中午一贴
记得自己读小学的时候经常参加学校每年组织的运动会,比赛项目是接力跑。
就是前面一个人把棒子传给你的时候你才会继续往前跑,当你跑到下面一个队员手中的时候把接力棒传给她,她又会继续跑,这样一直跑到终点.我们试想下如果我们在没有接到接力棒的时候就跑,这样跑到终点可以拿奖吗?显然是不行的,如果我们把几个人跑接力棒当作多线程的话,那么接力棒就是同步了。
2:线程同步的一些例子
在C#里,.net框架为我们提供了很多很多线程同步的类,一般常用的比如
InterLocked,Monitor,Mutex, ManualResetEvent, AutoResetEvent,另外还有C#为语言为我们提供的Lock关键字.
先讲InterLocked类,个人觉得它相对很简单,主要方法如下
Read //读取long型的数据
Increment //使long数据加1
Decrement //使long数据减1
大家需要注意一点的就是这个类的所有方法都是使用的ref型的参数,所以我们在使用的时候必须要添加成员变量。下面是我写的一个线程同步的小例子,道理很简单,希望大家能慢慢揣摩。
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
class Program
{
//用Interlocked同步线程来演示妈妈做菜,孩子在旁边偷吃第几盘菜
private static long num;
static void Main(string[] args)
{
Thread tt = new Thread(delegate()
{
for (int i = 1; i < 10; i++)
{
//判断变量是否等于1,如果等于1,则线程将会阻塞
//也就是说会执行其他的线程
while (Interlocked.Read(ref num) == 1)
{
Thread.Sleep(10);
}
Console.WriteLine("妈妈已经做好第{0}道菜了", i);
Interlocked.Increment(ref num);
}
});
Thread bb = new Thread(delegate()
{
for (int i = 1; i < 10; i++)
{
while (Interlocked.Read(ref num) == 0)
{
Thread.Sleep(10);
}
Console.WriteLine("我正在偷吃第{0}道菜",i);
Interlocked.Decrement(ref num);
}
});
tt.Start();
bb.Start();
Console.Read();
}
}
}
程序运行结果如下
下面我继续讲解使用Monitor类怎么同步这个例子,代码如下
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
class Program
{
//用Monitor同步线程来演示妈妈做菜,孩子在旁边偷吃第几盘菜
//obj并非必须要用Static关键字,只是因为这是在main控制台里
private static object obj = new object();
static void Main(string[] args)
{
Thread tt = new Thread(delegate()
{
for (int i = 1; i < 10; i++)
{
try
{
//Enter方法的意思为获取独占琐,或琐正在被占用,则自己等待
//Monitor还有TryEnter()方法,可以设置等待时间,防止死琐;
Monitor.Enter(obj);
Console.WriteLine("妈妈已经做好第{0}道菜了", i);
//Pulse方法就像是闹钟一样,提示别的进程可以访问独占琐了。
Monitor.Pulse(obj);
//当唤醒其余的线程之后,则自己在琐旁边打盹,知道被闹钟叫醒为止
Monitor.Wait(obj);
}
finally
{
Monitor.Exit(obj);
}
}
});
Thread bb = new Thread(delegate()
{
for (int i = 1; i < 10; i++)
{
try
{
//Enter方法的意思为获取独占琐,或琐正在被占用,则自己等待
//Monitor还有TryEnter()方法,可以设置等待时间,防止死琐;
Monitor.Enter(obj);
Console.WriteLine("我正在偷吃第{0}道菜了", i);
//Pulse方法就像是闹钟一样,提示别的进程可以访问独占琐了。
Monitor.Pulse(obj);
//当唤醒其余的线程之后,则自己在琐旁边打盹,知道被闹钟叫醒为止
Monitor.Wait(obj);
}
finally
{
Monitor.Exit(obj);
}
}
});
tt.Start();
bb.Start();
Console.Read();
}
}
}
当然结果和原来一样,不信大家可以运行试下。
Lock关键字.Lock基本就是封装了Monitor的enter和exit方法,但是它语法更简便,但有个缺点,它容易导致死琐,而Moniter可以用TryEnter方法来解决此问题,具体代码如下
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
class Program
{
//用Monitor同步线程来演示妈妈做菜,孩子在旁边偷吃第几盘菜
//obj并非必须要用Static关键字,只是因为这是在main控制台里
private static object obj = new object();
static void Main(string[] args)
{
Thread tt = new Thread(delegate()
{
for (int i = 1; i < 10; i++)
{
lock (obj)
{
Console.WriteLine("妈妈已经做好第{0}道菜了", i);
//Pulse方法就像是闹钟一样,提示别的进程可以访问独占琐了。
Monitor.Pulse(obj);
//当唤醒其余的线程之后,则自己在琐旁边打盹,知道被闹钟叫醒为止
Monitor.Wait(obj);
}
}
});
Thread bb = new Thread(delegate()
{
for (int i = 1; i < 10; i++)
{
lock (obj)
{
Console.WriteLine("我正在偷吃第{0}道菜", i);
//Pulse方法就像是闹钟一样,提示别的进程可以访问独占琐了。
Monitor.Pulse(obj);
//当唤醒其余的线程之后,则自己在琐旁边打盹,知道被闹钟叫醒为止
Monitor.Wait(obj);
}
}
});
tt.Start();
bb.Start();
Console.Read();
}
}
}
ManualResetEvent和AutoResetEvent类区别不是很大,主要的区别就是AutoResetEvent一次只能唤醒一个线程,而前一个可以同时获取几个线程。下面的代码我将演示用线程同步来计算
(1+2)*4/3这个方程式
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
//用ManualResetEvent同步演示线城(1+2)*4/3
//此例子代码较难理解,所以我会做过多的解释,大家也仔细揣摩揣摩
public class Person
{
private static ManualResetEvent eventwait = new ManualResetEvent(false);
private static ManualResetEvent eventover = new ManualResetEvent(false);
private int num1;
private int num2;
private int num3;
public void DianCai()
{
int numm = 1 + 2;
num1 = numm;
Console.WriteLine("1+2={0}",num1);
//这里添加睡眠的原因是别的线程可以在没有收到此线程的信号之前做别的事情
//这里我设置为1000,那么goto #2:这里每获取500就可以做其他事情了
Thread.Sleep(1000);
//Set方法我把它理解为和Monitor.Wait方法差不多
//它告诉其他线程我的事情已经做好,你们可以做事情了
eventwait.Set();
while (true)
{
if (eventover.WaitOne(500, false))
{
num3 = num2 / 3;
Console.WriteLine("{0}/3={1}",num2,num3);
break;
}
}
}
public void Chifan()
{
while (true)
{
//eventwait.WaitOne在收到Set传来的信号后它就会返回true,这时可以同步做事情了
if (eventwait.WaitOne(500, false))
{
num2 = num1 * 4;
Console.WriteLine("{0}*4={1}", num1, num2);
Thread.Sleep(1000);
//Reset代表线程已经结束
eventwait.Reset();
//这里同时发信号给其他线程,而自己则休眠
eventover.Set();
break;
}
/*
*
else
{
goto #2:当此线程为收到信号之前这里可以做别的事情
}
*/
}
}
}
class Program
{
static void Main(string[] args)
{
Person pp = new Person();
Thread t1 = new Thread(pp.DianCai);
Thread t2 = new Thread(pp.Chifan);
t1.Start();
t2.Start();
Console.Read();
}
}
}
程序运行结果如下
在提高篇里我主要讲解了诸如ManualResetEvent, Interlocked,Lock,Monitor的用法,大家仁者见仁,智者见智可以发挥想象力,它们能无所不用。另外大家需要知道当我们真正应用到同步编程技术的时候一定要甚用Lock,如果不小心就会死琐,比如我下面这个代码
lock(a)
{
lock(b)
{
}
}大家想想看,这样的代码程序能不能正确运行了?建议大家用Monitor,虽然代码多点,但保险.
3:课后作业。
大家试着用上面提到的技术自己编写一些小例子,最好别看我的,自己练习练习。
高级篇
1:晚间一贴
现在已经零晨2点多了,可自己还没有一点睡意,因为明年休息,嘿嘿。
上学时自己真不敢想象,真没考虑过自己以后会走IT这条路。毕业那年爸妈
找人让我去一家不是很大的软件公司做前台,那段时间真的很无聊,无所世事,成天聊QQ
,好后悔把积聚了几年的QQ给弄丢了,虽然自己以前也学过程序语言,可那真是皮毛,在公司看见别人在写代码,觉得反正自己也没什么事,为何不找些资料看看了?不说了,正题开始了。。。。。
2:Delegate异步线程与UI设计。
前段时间自己写了个播放器,可以播放歌曲的同时显示桌面歌词,自己觉得很得意,
而且我还创新了时下所有播放器没有的功能(默认自动在后台搜索歌曲),这项功能虽然很好,可事情总是有利有弊的,它给我带来了麻烦,因为在自动搜索歌曲的过程中播放器什么也操作不了,为什么呢?因为正在进行自动搜索的功能,在此功能未完成之前我只能眼巴巴的看着电脑,系统能快点,之后我改进了这项功能,用了BackgroundWorke来执行这项耗时的任务。虽然问题解决了,但是始终觉得效果不是很尽人意。其实我们大家都忘记了大名鼎鼎的delegate了,它拥有Invoke和BeginInvoke方法,前一种方法会阻塞线程,而后一种却不会。这两种方法比较难讲解,先看下下面这段代码吧,大致心里有个了解
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ConsoleApplication1
{
class Program
{
//创建一个委托
public delegate string MyDele(string name);
static MyDele mydele;
//这里创建一个值用来接受委托的异步方法穿来的值
static string GetZhi = null;
static void Main(string[] args)
{
mydele = new MyDele(GetName); //绑定方法
//BeginInvoke这三个第二个参数为一个异步接口对象,第三个参数为传送自身,或者为null
mydele.BeginInvoke("小徐", new AsyncCallback(Bing),null);
Console.WriteLine(GetZhi);
Console.Read();
}
//创建委托的方法
public static string GetName(string name)
{
return "Hello" + name;
}
//异步调用的方法
public static void Bing(IAsyncResult e)
{
GetZhi = mydele.EndInvoke(e);
//如果是这UI程序,我们要注意不能直接用这个值来修改控件,
//比如要用到控件的BeginInvoke方法
//如this.BeginInvoke(new MyS(My), GetZhi.ToString()); //这里MyS是委托,My是个委托方法
}
}
}
程序运行结果大家因该也猜到了吧。
关于BackgroundWorke的用法大家可以参考MSDN,主要就是开启工作者进度,报告继承等一些列的事件而已。。
总结:c#t提供的关于过线程的工具真的很多很多,尤其是4.0的并行编程,更是把多线程的功能推到了极限,如果一时不能理解多线程,就换种思维方式考虑问题。。