1. 多播(Multicast)委托:一个委托内部封装多个方法
使用形式如下
action1 += action2;//action1先把action2封装
action1 += action3;//action1再把action2封装
//现在action1里面封装了3个方法,调用action1如下三个方法都会得到执行
action1.Invoke();//像这种一个委托封装多个方法的使用方式叫做多播委托,封装顺序等于执行顺序
多播委托属于同步调用,源代码如下:
using Microsoft.VisualBasic;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
namespace DelegateExample
{
//一个委托内部封装多个方法
class Program
{
static void Main(string[] args)
{
//创建学生实例
Student stu1 = new Student() { Id=1,PenColor=ConsoleColor.Yellow};
Student stu2 = new Student() { Id=2,PenColor=ConsoleColor.Green};
Student stu3 = new Student() { Id=3,PenColor=ConsoleColor.Red};
//创建3个委托
Action action1 = new Action(stu1.DoHomeWork);
Action action2 = new Action(stu2.DoHomeWork);
Action action3 = new Action(stu3.DoHomeWork);
//调用委托
#region 单播委托
//action1.Invoke();
//action2.Invoke();
//action3.Invoke();
//以上这种一个委托封装一个方法的形式叫做单播委托
#endregion
#region 多播(multicast)委托
action1 += action2;//action1先把action2封装
action1 += action3;//action1再把action2封装
//现在action1里面封装了3个方法,调用action1如下三个方法都会得到执行
action1.Invoke();//像这种一个委托封装多个方法的使用方式叫做多播委托
//封装顺序等于执行顺序
#endregion
}
}
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);//在哪个线程调用这个sleep,这个线程就歇逼一秒钟
}
}
}
}
结果如下:
此处留意下,三个学生的颜色都是不一样的
2. 隐式异步调用
同步与异步的区别
同步:你做完了我在你的基础上继续做
以下三个方法依次运行,一个运行完了才会运行才一个
#region 直接同步调用
//且这三个方法都在主线程内调用
stu1.DoHomeWork();
stu2.DoHomeWork();
stu3.DoHomeWork();
for (int i = 0; i < 10; i++)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Main thread {0} ", i);
Thread.Sleep(100);//在哪个线程调用这个sleep,这个线程就歇逼一秒钟
}
#endregion
间接同步调用:
//间接同步调用
Action action1 = new Action(stu1.DoHomeWork);
Action action2 = new Action(stu2.DoHomeWork);
Action action3 = new Action(stu3.DoHomeWork);
//尽管是间接调用,但是还是同步的,一行行代码运行
action1.Invoke();
action2.Invoke();
action3.Invoke();
异步:咱俩同时开始做
使用委托进行的隐式异步调用,刚才我么已经知道使用Invoke是间接同步调用,现在使用BeginInvoke则可以进行隐式异步调用
//异步调用
action1.BeginInvoke(null, null);//会在一个分支线程调用action1的方法
//BeginInvoke参数一是异步调用的回调,指的是在分支线程里运行完action1以后继续执行的动作,null就是执行完action1啥也不干
//参数二默认null
action2.BeginInvoke(null, null);
action3.BeginInvoke(null, null);
结果如下:
首先,此时的顺序已经乱了,这个顺序是由分配的线程决定的,主线程和分支线程在同时运行;其次,颜色发生了混乱变化,如前三行结果颜色都一样,这是因为他们同时访问前景色,发生了冲突
3. 显式异步调用
1. 使用Thread的显式异步调用
//显示异步调用
//方式一:使用Thread创建三个线程(比较老的使用方式)
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();
结果如下:
可以看到与刚才的隐式异步调用是差不多的,而且也发生了冲突
2. 使用Task的显式异步调用(推荐)
//方式二:使用Task创建三个线程(推荐使用此方式)
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();
结果如下:
结果同上,仍然是发生了资源上的冲突
隐式和显示异步调用的完整代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace 委托的高级使用1
{
class Program
{
static void Main(string[] args)
{
//创建学生实例
Student stu1 = new Student() { Id = 1, PenColor = ConsoleColor.Yellow };
Student stu2 = new Student() { Id = 2, PenColor = ConsoleColor.Green };
Student stu3 = new Student() { Id = 3, PenColor = ConsoleColor.Red };
#region 隐式异步调用
//Action action1 = new Action(stu1.DoHomeWork);
//Action action2 = new Action(stu2.DoHomeWork);
//Action action3 = new Action(stu3.DoHomeWork);
//action1.BeginInvoke(null, null);//会在一个分支线程调用action1的方法
BeginInvoke参数一是异步调用的回调,指的是在分支线程里运行完action1以后继续执行的动作,null就是执行完action1啥也不干
参数二默认null
//action2.BeginInvoke(null, null);
//action3.BeginInvoke(null, null);
#endregion
#region 显示异步调用
方式一:使用Thread创建三个线程(比较老的使用方式)
//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();
//方式二:使用Task创建三个线程(推荐使用此方式)
//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();
#endregion
for (int i = 0; i < 10; i++)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("Main thread {0} ", i);
Thread.Sleep(100);//在哪个线程调用这个sleep,这个线程就歇逼一秒钟
}
Console.ReadKey();
}
}
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(100);//在哪个线程调用这个sleep,这个线程就歇逼一秒钟
}
}
}
}
4. 应当适时的使用接口(interface)取代一些对委托的使用
Java完全使用接口来代替了委托的功能,即Java没有与C#中委托相对应的功能实体
用之前的模板方法的程序做个例子,请按照我的注释顺序去看:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace _5.应用接口取代委托
{
internal class Program
{
static void Main(string[] args)
{
//8. 经过7的步骤以后下面这行会出错,我们的目标是把这个错误解决掉,首先,我们去重构一下模板方法
//ProductFactory productFactory = new ProductFactory();
//9. 创建新实例
IProductFactory pizzaFactory = new PizzaFactory();
IProductFactory toycarFactory= new ToyCarFactory();
WrapFactory wrapFactory = new WrapFactory();
//Func<Product> func1 = new Func<Product>(productFactory.MakePizza);
//Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);
//10. 把两个工厂传进去,此时程序之中已经没有委托的身影了
Box box1 = wrapFactory.WrapProduct(pizzaFactory);
Box box2 = wrapFactory.WrapProduct(toycarFactory);
Console.WriteLine(box1.Product.Name);
Console.WriteLine(box2.Product.Name);
Console.ReadKey();
}
}
//1. 首先,跟着佩奇叔叔声明一个接口
interface IProductFactory
{
Product Make();
}
//2. 其次,跟着佩奇叔叔再生两个类
class PizzaFactory : IProductFactory//这是一个披萨工厂,这个类实现了IProductFactory接口
{
//我们把鼠标点在上面的IProductFactory,并且同时按下Alt和Enter,选择实现接口就会生成下面的Make()方法
public Product Make()
{
//throw new NotImplementedException();//默认生成的这行代码我们不要
//从原来的披萨工厂剪切过来的代码
Product product = new Product();
product.Name = "Pizza";
return product;
//4. 所谓重构,就是基本不改变原来的代码,只是把它放在更合适的地方
}
}
//5. 跟着佩奇叔叔生成第二个类
class ToyCarFactory : IProductFactory
{
public Product Make()
{
//throw new NotImplementedException();
Product product = new Product();
product.Name = "ToyCar";
return product;
}
}
class Product
{
public string Name { get; set; }
}
class Box
{
public Product Product { get; set; }
}
class WrapFactory
{
//9. 现在我们的模板方法已经不需要委托类型的参数了,而是一个工厂类型的参数
public Box WrapProduct(IProductFactory productFactory)
{
Box box = new Box();
Product product = productFactory.Make();//这里对应更改
box.Product = product;
return box;
}
}
//7. 把东西都挪走以后这个类就没用了,我们把它注释掉
//class ProductFactory
//{
// //3. 我们把原来这个工厂的东西剪切到刚才的接口中去
// public Product MakePizza()
// {
// }
// //6. 同上
// public Product MakeToyCar()
// {
// }
//}
}