线程之间互不相干但是
多播(multicast)委托
举个例子:不同学生用不同颜色的笔做作业。
学生类(这个类在以下的各个例子中是不变的)
class Student
{
public int ID { get; set; }
public ConsoleColor PenColor { get; set; }
public void DoHomework()
{
for (int i = 0; i < 5; ++i)
{
Console.ForegroundColor = this.PenColor;
Console.WriteLine("Student{0} doing homework {1} hour(s).", this.ID, i);
Thread.Sleep(1000);//线程睡一秒
}
}
}
把委托1,2,3都合并到委托1里,只调用委托1。这样用一个委托封装多个委托方法的过程就叫多播委托。
class Program
{
static void Main(string[] args)
{
Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Red};
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Yellow };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Blue };
Action action1 = new Action(stu1.DoHomework);
Action action2 = new Action(stu2.DoHomework);
Action action3 = new Action(stu3.DoHomework);
action1 = action1 + action2;
action1 = action1 + action3;
action1.Invoke();
}
}
多播委托调用的执行顺序按照是封装方法的顺序执行的
action1.Invoke();
action2.Invoke();
action3.Invoke();
结果:
同步调用:
直接同步调用:通过方法名调用
间接同步调用:通过委托调用
直接同步调用(通过方法名调用)
这个直接和间接是以是否用委托来区分的,不用委托直接方法名调用就是直接调用,用委托就是间接调用。
class Program
{
static void Main(string[] args)
{
Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Red};
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Yellow };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Blue };
stu1.DoHomework();
stu2.DoHomework();
stu3.DoHomework();
for (int i = 0; i < 5; ++i)
{
Console.ForegroundColor = ConsoleColor.Cyan;//青色
Console.WriteLine("Main thread{0}", i);//这里thread代表主线程
Thread.Sleep(1000);
}
}
}
三个直接调用的颜色结束后,主线程还要进行一段(打印青色字体)
即这种情形,红色的是主线程,然后分别调用了三个方法,方法结束再回到主线程。
间接同步调用(通过委托调用)
stu1.DoHomework();
stu2.DoHomework();
stu3.DoHomework();
替换为
action1.Invoke();
action2.Invoke();
action3.Invoke();
结果是一样的,没什么好多说的。
上面的多播委托,也是同步调用的(间接调用),同步异步与直接间接是没有相关性的,这两个概念可以任意组合。
异步调用:
隐式异步调用:BeginInvoke方法
显式异步调用:Thread
显式异步调用:Task
隐式异步调用(使用委托,自然都是间接调用)
委托除了Invoke方法,还有个方法叫
这里的BeginInvoke方法会给我们自动生成一个分支线程,然后在分支线程里去调用它封装的方法。
BeginInvoke会要两个参数,
第一个是异步调用的回调(你不是要我在一个分支线程里去调用这个方法么,调用完了之后你打算让我后续做什么呢?也就是回调方法)
第二个参数,不知道是啥一般给null。
执行一下程序
class Program
{
static void Main(string[] args)
{
Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Red};
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Yellow };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Blue };
Action action1 = new Action(stu1.DoHomework);
Action action2 = new Action(stu2.DoHomework);
Action action3 = new Action(stu3.DoHomework);
action1.BeginInvoke(null, null);
action2.BeginInvoke(null, null);
action3.BeginInvoke(null, null);
for (int i = 0; i < 5; ++i)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Main thread{0}", i);
Thread.Sleep(1000);
}
}
}
结果(在.Net Core是会报错的,因此是在.Net Freamwork下运行的)
可以看到颜色和执行顺序是乱的
而且每次运行的结果都不同。
为什么呢?
是因为三个线程都在访问控制台的前景色(ForegroundColor)这个属性,
多个线程在访问同一个资源的时候,就可能在争抢资源时发生冲突,
我们看到的就是发生冲突的场景。为了避免冲突,我们会为线程加锁。
那么是如何争抢资源的呢?
我们通过监视可以发现:
控制台的ForegroundColor属性是个静态属性,属于主线程。
action1.BeginInvoke(null, null);
action2.BeginInvoke(null, null);
action3.BeginInvoke(null, null);
这三个子线程并发进行,每一时刻都不确定谁在执行,谁前谁后。
这三个子线程都有可能调用(修改)主线程的ForegroundColor静态属性
举个具体例子:
一:
如果是action1.BeginInvoke(null, null);先启动的,
把ForegroundColor属性赋予了 Red,
二:
action1还未打印就被action3.BeginInvoke(null, null);
把ForegroundColor属性改为了Blue
三:
action3还未打印就被action2.BeginInvoke(null, null);
把ForegroundColor属性改为了Yellow
四:
结果打印的时候action3.BeginInvoke(null, null);抢先打印的,这样
Student3 doing homework 0 hour(s).这句话是第一个被打印出来的,
而且它的颜色会是Yellow,
五:
随后
Student2 doing homework 0 hour(s).
Student1 doing homework 0 hour(s).
这两句话也紧跟着打印出来了,它们也是Yellow
具体情况随机性极强,千变万化。本质原因就是赋颜色和打印这两步里,
第一步都是三个线程对一个主线程的静态属性进行多次操作(争抢),
导致最后留下的是谁的颜色不清楚,随后在打印的时候三个线程的先后也有随机性,
这样重叠起来就会导致三个线程和一个主线程的各种颜色,输出顺序混乱。
显式异步调用(我们自己动手声明多线程)
方式一:比较古老的Thread
class Program
{
static void Main(string[] args)
{
Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Red };
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Yellow };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Blue };
//创建三个线程
Thread thread1 = new Thread(new ThreadStart(stu1.DoHomework));
Thread thread2 = new Thread(new ThreadStart(stu2.DoHomework));
Thread thread3 = new Thread(new ThreadStart(stu3.DoHomework));
//启动三个线程
thread1.Start();
thread2.Start();
thread3.Start();
for (int i = 0; i < 5; ++i)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Main thread{0}", i);
Thread.Sleep(1000);
}
}
}
结果:
主线程和支线程谁也不等谁,彼此独立运行,还有资源争抢。
除了使用Thread 进行显示的异步调用,C#类库给我们准备了一种更高级的方式——Task
方式二:
输入Task然后Ctrl+.会提示你输入名称空间
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Red };
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Yellow };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Blue };
Task task1 = new Task(new Action(stu1.DoHomework));
Task task2 = new Task(new Action(stu2.DoHomework));
Task task3 = new Task(new Action(stu3.DoHomework));
task1.Start();
task2.Start();
task3.Start();
for (int i = 0; i < 5; ++i)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Main thread{0}", i);
Thread.Sleep(1000);
}
}
}
结果:
同样是主线程和支线程谁也不等谁,彼此独立运行,还有资源争抢。
应该适时地使用接口(interface)取代一些对委托的使用
Java中没有委托这个功能,Java用接口实现委托
这个例子源于:
用接口代替委托的方式:
产品类和包装盒类
class Product
{
public string Name { get; set; }
}
class Box
{
public Product Product { get; set; }
}
加工工厂类
class WarpFactory
{
public Box WarpProduct(IProductFactory productFactory)
{
Box box = new Box();
Product product = productFactory.Make();//Product类的product变量去接Make()方法返回的Product类型对象。
box.Product = product;//传给box.product
return box;
}
}
生产工厂接口
interface IProductFactory
{
Product Make();
}
产品工厂类被拆分成了披萨工厂和玩具车工厂,都继承生产工厂这个接口
披萨工厂类:生产工厂
class PizzaFactory : IProductFactory
{
public Product Make()
{
Product product = new Product();
product.Name = "Pizza";
return product;
}
}
披萨工厂:生产工厂
class ToyCarFactory : IProductFactory
{
public Product Make()
{
Product product = new Product();
product.Name = "ToyCar";
return product;
}
}
}
主函数类
class Program
{
static void Main(string[] args)
{
IProductFactory pizzafactory = new PizzaFactory();
IProductFactory toycarfactory = new ToyCarFactory();
WarpFactory warpfactory = new WarpFactory();
Box pizzabox = warpfactory.WarpProduct(pizzafactory);
Box toycarbox = warpfactory.WarpProduct(toycarfactory);
Console.WriteLine(pizzabox.Product.Name);
Console.WriteLine(toycarbox.Product.Name);
}
}
结果是一样的:
但是我们的程序中已经没有委托了,没有了方法级别的耦合。