【愚公系列】2023年08月 .NET CORE工具案例-CsGo并发流程控制框架


前言

并发流程控制框架是一种软件框架,用于管理并发执行的多个任务或进程。它可以帮助开发人员轻松地编写并发程序,并提供一些有用的功能,例如:

  1. 线程池:管理线程的创建和销毁,减少线程创建的开销和线程数量的过度使用。

  2. 同步机制:提供锁定、信号量、条件变量等工具,使多个线程能够有序地访问共享资源。

  3. 任务队列:通过将任务放入队列中,实现任务的异步执行和控制。

  4. 异常处理:捕捉和处理异常,确保程序执行的稳定性。

  5. 日志记录:记录程序的运行情况和错误信息,方便问题排查和调试。

常见的并发流程控制框架包括Java的Executor框架、.Net的Task Parallel Library框架、Python的concurrent.futures框架等。它们都提供了类似的功能,但具体的实现方式和语法略有不同。

一、CsGo并发流程控制框架相关概念

CsGo的并发流程控制框架、运动控制框架,适用于工业自动化运动控制、机器视觉流程开发。

相对于传统的多线程模型、状态机模型和类PLC模型,使用并发流程控制框架的优势包括:

  1. 可扩展性更好:传统的并发模型往往存在硬编码的问题,导致难以适应复杂的业务需求和高并发场景。而并发流程控制框架可以通过配置文件或者代码自动生成进行快速扩展。

  2. 编写和维护成本更低:并发流程控制框架提供了大量的现成工具和代码库,使得开发人员可以更轻松地编写和维护高质量的并发程序,并降低代码量和维护成本。

  3. 可读性更好:并发流程控制框架可以使程序逻辑更加清晰,从而提高代码可读性和可维护性。

  4. 操作更简单:并发流程控制框架使得开发人员可以快速构建并发程序,降低了操作难度和学习成本。

  5. 可移植性更强:并发流程控制框架可以在不同的操作系统、编程语言和硬件平台上运行,提高了程序的可移植性。

CsGo的并发流程控制框架主要是在golang语言的编程模式上设计开发,且进行必要的功能扩展:

  • 自定义单/多线程调度,亦可主UI线程调度,方便逻辑与UI的交互;

  • 自带高精度定时器、调度优先级、逻辑停止、逻辑暂停功能;

  • 树形多任务调度,提高逻辑的可靠性;

  • 单线程调度每秒100万次以上,从容应对千级IO点数;

相关网址:https://api.gitee.com/hamasm/CsGo

在这里插入图片描述

1.调度器

work_service

    一级调度,使用post将任务提交给一个单事件队列,启用一个或多个线程执行run函数,事件队列中的任务将被并行执行。这个类可用来执行一些无序任务,类似于线程池。

work_engine

    对work_service的多线程封装,设置好线程数和优先级,直接开启run执行即可。不用另外开启线程执行work_service.run()。

shared_strand

    二级调度,使用post将任务提交给一个双缓冲事件队列,内部将事件一个接一个的按序抛给Task执行,只有上一个任务完成,才开始下一个任务,这样尽管任务可能被分派到不同的线程中,但所执行的任务彼此也将是线程安全的。

work_strand

    继承自shared_strand,只不过将任务抛给work_service执行。

control_strand

    继承自shared_strand,只不过将任务抛给Control控件,任务将在UI线程中执行。这个对象一个进程只能创建一个.

2.generator

    任务执行实体,不同于Thread的抢占式调度,由shared_strand协作式调度运行。所有依赖同一个shared_strand的generator彼此将是线程安全的。

go

    创建并立即开始运行一个generator。

tgo

    创建并立即开始运行一个generator,并返回generator对象。

make

    创建一个generator但不立即运行。

run

    开始运行一个generator。

stop

    给generator发送一个中止操作,generator内部流程收到后自行处理。

suspend

    给generator发送一个挂起操作。

resume

    给generator发送一个挂起恢复操作。

lock_stop

    与unlock_stop成对出现,lock后generator将延时接收stop操作,只有unlock_stop后才能接收到stop。

lock_suspend

    与unlock_suspend成对出现,lock后generator将延时接收suspend,只有unlock_suspend后才能接收到suspend。

lock_shield

    与unlock_shield成对出现,lock后generator将延时接收suspend/stop操作,只有unlock_shield后才能接收到stop/suspend。

chan_send

    执行chan.send()操作。

chan_timed_send

    执行chan.timed_send()操作。

chan_try_send

    执行chan.try_send()操作。

chan_receive

    执行chan.receive()操作。

chan_timed_receive

    执行chan.timed_receive()操作。

chan_try_receive

    执行chan.try_receive()操作。

select

    一次选择一个可立即操作的chan进行send/recv。

sleep

    延时运行一小会。

send_task

    发送一个任务到Task中,完成后返回,主要用来执行耗时算法。

send_service

    发送一个任务到work_service中,完成后返回,主要用来执行耗时算法。

send_strand

    发送一个任务到shared_strand中,完成后返回,主要用来串行耗时算法。

send_control

    发送一个任务到shared_control中,完成后返回,主要用来在非control_strand调度的generator中操作UI,或弹出模态对话框。

3.wait_group

    用来给任务计数,然后等待所有任务结束。

add

    添加一个任务计数。

done

    结束一个任务计数.

wait

    等待所有任务计数被清零。

async_wait

    异步等待所有任务计数被清零。

cancel_wait

    中止当前的async_wait操作。

4.wait_gate

    设置一个信号关卡,当计数满足后执行一个操作,然后开始接下来的任务。

enter

    开始一个计数等待,当计数满足且一个操作完成后,然后开始接下来的任务。

async_enter

    开始一个异步计数等待。

cancel_enter

    取消async_enter等待。

5.children

    child任务组,务必在generator上下文中创建和执行操作,且有效的管理内部的child,否则将造成泄漏,不然将等到generator被stop时才能清空。

stop

    中止一个或多个child任务。

wait

    等待一个或多个child任务结束。

timed_wait

    超时等待一个或多个child任务结束。

wait_any

    等待任意一个child任务结束。

timed_wait_any

    超时等待任意一个child任务结束。

wait_all

    等待所有child任务结束。

timed_wait_all

    超时等待所有child任务结束。

6.child

    继承自generator,由children创建的子任务,生命周期将受上级generator管理。

7.chan

    用于generator之间的消息通信,分无缓存(nil_chan)、有限缓存(limit_chan)、无限缓存(unlimit_chan)、过程调用(csp_chan)四种类型。

send

    发送消息,阻塞等待,成功后返回。

try_send

    尝试发送消息,不阻塞等待,立即返回。

timed_send

    尝试发送消息,超时阻塞等待,成功或过期后返回。

receive

    接收消息,阻塞等待,成功后返回。

try_receive

    尝试接收消息,不阻塞等待,立即返回。

timed_receive

    尝试接收消息,超时阻塞等待,成功或过期后返回。

close

    关闭chan。

cancel

    取消当前chan中等待的所有操作。

invoke

    在csp_chan中执行一次过程调用,得到结果后返回。

try_invoke

    在csp_chan中尝试执行一次过程调用,得到结果或失败后返回。

timed_invoke

    在csp_chan中超时执行一次过程调用,得到结果或过期后返回。

wait

    在csp_chan中等待一次过程调用,完成后返回。

try_wait

    在csp_chan中尝试等待一次过程调用,完成或失败后返回。

timed_wait

    在csp_chan中超时等待一次过程调用,完成或超时后返回。

8.async_timer

    受shared_strand调度的定时器,所有操作只能在该shared_strand中进行。

timeout

    开始一个计时操作。

deadline

    使用终止时间,开始一个计时操作。

interval

    开始一个周期性的定时操作。

restart

    如果当前计时还未结束,将重新开始计时。

advance

    如果当前计时还未结束,将提前完成计时。

cancel

    如果当前计时还未结束,将中止计时。

二、CsGo并发流程控制框架使用

本文是修改了CsGo相关源码,转换成.net core进行使用的

1.串行执行

串行执行是指按照一定的顺序依次执行任务,每个任务要等到前一个任务完成后才能执行下一个任务。在计算机中,串行执行通常指的是单线程的执行方式。在单线程中,每个操作都必须按照先后顺序进行,不能同时进行多项操作。这种执行方式适用于一些简单的任务,但对于一些复杂的任务,串行执行会大大降低任务处理速度,并且会浪费计算资源。因此,在处理复杂任务时,常常采用并行化的方式来提高计算效率。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Go;

namespace WorkerFlow
{
    
    
    class Program
    {
    
    
        static shared_strand strand;

        static void Log(string msg)
        {
    
    
            Console.WriteLine($"{
      
      DateTime.Now.ToString("HH:mm:ss.fff")} {
      
      msg}");
        }

        static async Task Worker(string name, int time = 1000)
        {
    
    
            await generator.sleep(time);
            Log(name);
        }

        //1 A、B、C依次串行
        //A->B->C
        static async Task Worker1()
        {
    
    
            await Worker("A");
            await Worker("B");
            await Worker("C");
        }

        static async Task MainWorker()
        {
    
    
            await Worker1();
        }

        static void Main(string[] args)
        {
    
    
            work_service work = new work_service();
            strand = new work_strand(work);
            generator.go(strand, MainWorker);
            work.run();
            Console.ReadKey();
        }
    }
}

在这里插入图片描述

2.并行执行

并行执行是指同时处理多个任务或操作的能力。在计算机科学中,这通常通过使用多个处理器或多个线程来实现。并行执行可以提高系统的效率和性能,特别是在需要处理大量数据或进行复杂操作的情况下。例如,在多线程编程中,可以将任务分配给不同的线程以实现并行执行,从而加快程序的速度。并行执行也是分布式计算和云计算的核心概念,允许多台计算机协同工作来处理大规模的任务。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Go;

namespace WorkerFlow
{
    
    
    class Program
    {
    
    
        static shared_strand strand;

        static void Log(string msg)
        {
    
    
            Console.WriteLine($"{
      
      DateTime.Now.ToString("HH:mm:ss.fff")} {
      
      msg}");
        }

        static async Task Worker(string name, int time = 1000)
        {
    
    
            await generator.sleep(time);
            Log(name);
        }

        //2 A、B、C全部并行,且依赖同一个strand(隐含参数,所有依赖同一个strand的任务都是线程安全的)
        //A
        //B
        //C
        static async Task Worker2()
        {
    
    
            generator.children children = new generator.children();
            children.go(() => Worker("A"));
            children.go(() => Worker("B"));
            children.go(() => Worker("C"));
            await children.wait_all();
        }

        static async Task MainWorker()
        {
    
    
            await Worker2();
        }

        static void Main(string[] args)
        {
    
    
            work_service work = new work_service();
            strand = new work_strand(work);
            generator.go(strand, MainWorker);
            work.run();
            Console.ReadKey();
        }
    }
}

在这里插入图片描述

3.串行混合并行执行

串行混合并行执行是一种程序或任务的执行方式,可以提高程序的效率和性能。

混合执行是串行和并行的结合,根据任务的复杂度和性质选择合适的执行方式。比如简单的任务可以串行执行,复杂的任务可以并行执行。

在实际应用中,串行混合并行执行方式可以根据具体的需求和情况进行选择,以实现最优的程序性能和效率。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Go;

namespace WorkerFlow
{
    
    
    class Program
    {
    
    
        static shared_strand strand;

        static void Log(string msg)
        {
    
    
            Console.WriteLine($"{
      
      DateTime.Now.ToString("HH:mm:ss.fff")} {
      
      msg}");
        }

        static async Task Worker(string name, int time = 1000)
        {
    
    
            await generator.sleep(time);
            Log(name);
        }

        //3 A执行完后,B、C再并行
        //  -->B
        //  |
        //A->
        //  |
        //  -->C
        static async Task Worker3()
        {
    
    
            await Worker("A");
            generator.children children = new generator.children();
            children.go(() => Worker("B"));
            children.go(() => Worker("C"));
            await children.wait_all();
        }

        //4 B、C都并行执行完后,再执行A
        //B--
        //  |
        //  -->A
        //  |
        //C--
        static async Task Worker4()
        {
    
    
            generator.children children = new generator.children();
            children.go(() => Worker("B"));
            children.go(() => Worker("C"));
            await children.wait_all();
            await Worker("A");
        }

        static async Task MainWorker()
        {
    
    
            await Worker3();
            await Worker4();
        }

        static void Main(string[] args)
        {
    
    
            work_service work = new work_service();
            strand = new work_strand(work);
            generator.go(strand, MainWorker);
            work.run();
            Console.ReadKey();
        }
    }
}

在这里插入图片描述

4.并行任意一个执行完后,再串行

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Go;

namespace WorkerFlow
{
    
    
    class Program
    {
    
    
        static shared_strand strand;

        static void Log(string msg)
        {
    
    
            Console.WriteLine($"{
      
      DateTime.Now.ToString("HH:mm:ss.fff")} {
      
      msg}");
        }

        static async Task Worker(string name, int time = 1000)
        {
    
    
            await generator.sleep(time);
            Log(name);
        }

        //5 B、C任意一个执行完后,再执行A
        //B--
        //  |
        //  >-->A
        //  |
        //C--
        static async Task Worker5()
        {
    
    
            generator.children children = new generator.children();
            var B = children.tgo(() => Worker("B", 1000));
            var C = children.tgo(() => Worker("C", 2000));
            var task = await children.wait_any();
            if (task == B)
            {
    
    
                Log("B成功");
            }
            else
            {
    
    
                Log("C成功");
            }
            await Worker("A");
        }

        static async Task MainWorker()
        {
    
    
            await Worker5();
        }

        static void Main(string[] args)
        {
    
    
            work_service work = new work_service();
            strand = new work_strand(work);
            generator.go(strand, MainWorker);
            work.run();
            Console.ReadKey();
        }
    }
}

在这里插入图片描述

5.并行特定一个执行完后,再串行

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Go;

namespace WorkerFlow
{
    
    
    class Program
    {
    
    
        static shared_strand strand;

        static void Log(string msg)
        {
    
    
            Console.WriteLine($"{
      
      DateTime.Now.ToString("HH:mm:ss.fff")} {
      
      msg}");
        }

        static async Task Worker(string name, int time = 1000)
        {
    
    
            await generator.sleep(time);
            Log(name);
        }

        //6 等待一个特定任务
        static async Task Worker6()
        {
    
    
            generator.children children = new generator.children();
            var A = children.tgo(() => Worker("A"));
            var B = children.tgo(() => Worker("B"));
            await children.wait(A);
        }

        static async Task MainWorker()
        {
    
    
            await Worker6();
        }

        static void Main(string[] args)
        {
    
    
            work_service work = new work_service();
            strand = new work_strand(work);
            generator.go(strand, MainWorker);
            work.run();
            Console.ReadKey();
        }
    }
}

在这里插入图片描述

6.并行特定一个执行完后,终止其他并行任务

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Go;

namespace WorkerFlow
{
    
    
    class Program
    {
    
    
        static shared_strand strand;

        static void Log(string msg)
        {
    
    
            Console.WriteLine($"{
      
      DateTime.Now.ToString("HH:mm:ss.fff")} {
      
      msg}");
        }

        static async Task Worker(string name, int time = 1000)
        {
    
    
            await generator.sleep(time);
            Log(name);
        }

        //7 超时等待一个特定任务,然后中止所有任务
        static async Task Worker7()
        {
    
    
            generator.children children = new generator.children();
            var A = children.tgo(() => Worker("A", 1000));
            var B = children.tgo(() => Worker("B", 2000));
            if (await children.timed_wait(1500, A))
            {
    
    
                Log("成功");
            }
            else
            {
    
    
                Log("超时");
            }
            await children.stop();
        }

        static async Task MainWorker()
        {
    
    
            await Worker7();
        }

        static void Main(string[] args)
        {
    
    
            work_service work = new work_service();
            strand = new work_strand(work);
            generator.go(strand, MainWorker);
            work.run();
            Console.ReadKey();
        }
    }
}

在这里插入图片描述

7.非固定时间内完成任务全部终止

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Go;

namespace WorkerFlow
{
    
    
    class Program
    {
    
    
        static shared_strand strand;

        static void Log(string msg)
        {
    
    
            Console.WriteLine($"{
      
      DateTime.Now.ToString("HH:mm:ss.fff")} {
      
      msg}");
        }

        static async Task Worker(string name, int time = 1000)
        {
    
    
            await generator.sleep(time);
            Log(name);
        }

        //8 超时等待一组任务,然后中止所有任务
        static async Task Worker8()
        {
    
    
            generator.children children = new generator.children();
            children.go(() => Worker("A", 1000));
            children.go(() => Worker("B", 2000));
            var tasks = await children.timed_wait_all(1500);
            await children.stop();
            Log($"成功{
      
      tasks.Count}个");
        }

        static async Task MainWorker()
        {
    
    
            await Worker8();

        }

        static void Main(string[] args)
        {
    
    
            work_service work = new work_service();
            strand = new work_strand(work);
            generator.go(strand, MainWorker);
            work.run();
            Console.ReadKey();
        }
    }
}

在这里插入图片描述

8.非固定时间内完成任务全部终止,并对终止进行后续处理

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Go;

namespace WorkerFlow
{
    
    
    class Program
    {
    
    
        static shared_strand strand;

        static void Log(string msg)
        {
    
    
            Console.WriteLine($"{
      
      DateTime.Now.ToString("HH:mm:ss.fff")} {
      
      msg}");
        }

        static async Task Worker(string name, int time = 1000)
        {
    
    
            await generator.sleep(time);
            Log(name);
        }

        //9 超时等待一组任务,然后中止所有任务,且在中止任务中就地善后处理
        static async Task Worker9()
        {
    
    
            generator.children children = new generator.children();
            children.go(() => Worker("A", 1000));
            children.go(async delegate ()
            {
    
    
                try
                {
    
    
                    await Worker("B", 2000);
                }
                catch (generator.stop_exception)
                {
    
    
                    Log("B被中止");
                    await generator.sleep(500);
                    throw;
                }
                catch (System.Exception)
                {
    
    
                }
            });
            var task = await children.timed_wait_all(1500);
            await children.stop();
            Log($"成功{
      
      task.Count}个");
        }

        

        static async Task MainWorker()
        {
    
    
            await Worker9();

        }

        static void Main(string[] args)
        {
    
    
            work_service work = new work_service();
            strand = new work_strand(work);
            generator.go(strand, MainWorker);
            work.run();
            Console.ReadKey();
        }
    }
}

在这里插入图片描述

9.嵌套任务

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Go;

namespace WorkerFlow
{
    
    
    class Program
    {
    
    
        static shared_strand strand;

        static void Log(string msg)
        {
    
    
            Console.WriteLine($"{
      
      DateTime.Now.ToString("HH:mm:ss.fff")} {
      
      msg}");
        }

        static async Task Worker(string name, int time = 1000)
        {
    
    
            await generator.sleep(time);
            Log(name);
        }

        //10 嵌套任务
        static async Task Worker10()
        {
    
    
            generator.children children = new generator.children();
            children.go(async delegate ()
            {
    
    
                generator.children children1 = new generator.children();
                children1.go(() => Worker("A"));
                children1.go(() => Worker("B"));
                await children1.wait_all();
            });
            children.go(async delegate ()
            {
    
    
                generator.children children1 = new generator.children();
                children1.go(() => Worker("C"));
                children1.go(() => Worker("D"));
                await children1.wait_all();
            });
            await children.wait_all();
        }

        

        static async Task MainWorker()
        {
    
    
            await Worker10();

        }

        static void Main(string[] args)
        {
    
    
            work_service work = new work_service();
            strand = new work_strand(work);
            generator.go(strand, MainWorker);
            work.run();
            Console.ReadKey();
        }
    }
}

在这里插入图片描述

10.嵌套任务终止

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Go;

namespace WorkerFlow
{
    
    
    class Program
    {
    
    
        static shared_strand strand;

        static void Log(string msg)
        {
    
    
            Console.WriteLine($"{
      
      DateTime.Now.ToString("HH:mm:ss.fff")} {
      
      msg}");
        }

        static async Task Worker(string name, int time = 1000)
        {
    
    
            await generator.sleep(time);
            Log(name);
        }

        //11 嵌套中止
        static async Task Worker11()
        {
    
    
            generator.children children = new generator.children();
            children.go(() => Worker("A", 1000));
            children.go(async delegate ()
            {
    
    
                try
                {
    
    
                    generator.children children1 = new generator.children();
                    children1.go(async delegate ()
                    {
    
    
                        try
                        {
    
    
                            await Worker("B", 2000);
                        }
                        catch (generator.stop_exception)
                        {
    
    
                            Log("B被中止1");
                            await generator.sleep(500);
                            throw;
                        }
                        catch (System.Exception)
                        {
    
    
                        }
                    });
                    await children1.wait_all();
                }
                catch (generator.stop_exception)
                {
    
    
                    Log("B被中止2");
                    throw;
                }
                catch (System.Exception)
                {
    
    
                }
            });
            await generator.sleep(1500);
            await children.stop();
        }


        static async Task MainWorker()
        {
    
    
            await Worker11();

        }

        static void Main(string[] args)
        {
    
    
            work_service work = new work_service();
            strand = new work_strand(work);
            generator.go(strand, MainWorker);
            work.run();
            Console.ReadKey();
        }
    }
}

在这里插入图片描述

11.并行执行算法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Go;

namespace WorkerFlow
{
    
    
    class Program
    {
    
    
        static shared_strand strand;

        static void Log(string msg)
        {
    
    
            Console.WriteLine($"{
      
      DateTime.Now.ToString("HH:mm:ss.fff")} {
      
      msg}");
        }

        static async Task Worker(string name, int time = 1000)
        {
    
    
            await generator.sleep(time);
            Log(name);
        }

        //12 并行执行且等待一组耗时算法
        static async Task Worker12()
        {
    
    
            wait_group wg = new wait_group();
            for (int i = 0; i < 12; i++)
            {
    
    
                wg.add();
                int idx = i;
                var _ = Task.Run(delegate ()
                {
    
    
                    try
                    {
    
    
                        Log($"执行算法{
      
      idx}");
                    }
                    finally
                    {
    
    
                        wg.done();
                    }
                });
            }
            await wg.wait();
            Log("执行算法完成");
        }

        static async Task MainWorker()
        {
    
    
            await Worker12();

        }

        static void Main(string[] args)
        {
    
    
            work_service work = new work_service();
            strand = new work_strand(work);
            generator.go(strand, MainWorker);
            work.run();
            Console.ReadKey();
        }
    }
}

在这里插入图片描述

12.串行执行算法

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Go;

namespace WorkerFlow
{
    
    
    class Program
    {
    
    
        static shared_strand strand;

        static void Log(string msg)
        {
    
    
            Console.WriteLine($"{
      
      DateTime.Now.ToString("HH:mm:ss.fff")} {
      
      msg}");
        }

        static async Task Worker(string name, int time = 1000)
        {
    
    
            await generator.sleep(time);
            Log(name);
        }

        //13 串行执行耗时算法,耗时算法必需放在线程池中执行,否则依赖同一个strand的调度将不能及时
        static async Task Worker13()
        {
    
    
            for (int i = 0; i < 12; i++)
            {
    
    
                await generator.send_task(() => Log($"执行算法{
      
      i}"));
            }
        }

        static async Task MainWorker()
        {
    
    
            await Worker13();

        }

        static void Main(string[] args)
        {
    
    
            work_service work = new work_service();
            strand = new work_strand(work);
            generator.go(strand, MainWorker);
            work.run();
            Console.ReadKey();
        }
    }
}

在这里插入图片描述

备注

源码地址:https://download.csdn.net/download/aa2528877987/88180702?spm=1001.2014.3001.5503

猜你喜欢

转载自blog.csdn.net/aa2528877987/article/details/132139337