委托的高级使用


线程之间互不相干但是

多播(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);
        }
    }

结果是一样的:

但是我们的程序中已经没有委托了,没有了方法级别的耦合。

posted @ 2019-10-14 11:00  卯毛  阅读(364)  评论(0)    收藏  举报