• 委托的高级使用



    线程之间互不相干但是

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

    结果是一样的:

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

  • 相关阅读:
    Java之设计模式详解 (转)
    强引用,软引用,弱引用和虚引用总结
    Java基础知识总结
    深入理解Java的接口和抽象类
    Android Studio高级配置
    JS中innerHTML 和innerText和value的区别
    Prompt isNaN 数组 function DOM window.open/close/location/history
    WebForm组合查询
    WebForm分页浏览
    WebForm上传文件FileUpload
  • 原文地址:https://www.cnblogs.com/maomaodesu/p/11670329.html
Copyright © 2020-2023  润新知