重庆熊猫 Loading

.NET中按预定顺序执行任务

更新记录
本文迁移自Panda666原博客,原发布时间:2021年7月1日。

一、说明

在.NET中线程可以定义按先后顺序进行执行,适合部分有先后次序的业务逻辑。Task也可以按照预定义的先后顺序执行。现在我们分别用Thread和Task实现按次序执行业务逻辑。
image

二、使用ManualResetEvent类实现Thread的执行顺序

ManualResetEvent类表示 线程同步事件,通过信号机制来控制 多个线程。通过手动控制信号,实现线程通过信号互相通信。通常用于控制线程的先后顺序。注意:除了可以用ManualResetEvent类还可以使用AutoResetEvent类实现线程同步操作。

2.1基本使用

ManualResetEvent构造函数
ManualResetEvent的构造函数如下:

private static ManualResetEvent mre = new ManualResetEvent(false);

构造函数的参数指示信号的初始状态。

true 表示“开启”,线程可以访问资源,WaitOne处时,阻塞恢复正常执行。
false 表示“关闭”,线程碰到WaitOne处时,则发送阻塞暂停。
Set()方法

将事件状态设置为“开启”,类似“绿灯”可以通行。
被WaitOne() 方法阻塞的一/多个线程将可以继续执行代码。

Reset()方法
将事件状态设置为“关闭”,类似“红灯”禁止通行。
线程执行到WaitOne()方法时,就发生阻塞,阻止当前线程继续执行。
实现了和构造函数传入默认值false一样的效果。
直接再次接收信号(调用Set方法),才会再次执行被阻塞的代码。

WaitOne()方法
WaitOne()方法用于等待信号。
当事件状态设置为“开启”时,不进行阻塞,代码继续执行。
当事件状态设置为“关闭”时,进行阻塞,代码不继续执行。
WaitOne()方法发生阻塞的情况:

  • 事件同步对象一开始就设置信号为false
  • 调用了事件同步对象的Reset()方法
    WaitOne()方法发生不阻塞的情况:
  • 事件同步对象一开始就设置信号为true
  • 调用了事件同步对象的Set()方法

2.2实例:主线程控制被控制的线程

using System;
using System.Threading;

namespace ConsoleApplication2
{
    /// <summary>
    /// 测试类
    /// </summary>
    class PandaTestClass
    {
        /// <summary>
        /// 测试使用的被控制的线程
        /// </summary>
        public Thread TestThread { get; set; } = null;

        /// <summary>
        /// 信号事件对象
        /// 参数设置为false,需要手动开启
        /// </summary>
        public ManualResetEvent manualEvent = new ManualResetEvent(false); 

        /// <summary>
        /// 构造函数
        /// </summary>
        public PandaTestClass()
        {
            //线程实例化
            TestThread = new Thread(this.Run);
            //线程执行
            TestThread.Start();
        }

        /// <summary>
        /// 线程具体执行的内容
        /// </summary>
        private void Run()
        {
            //模拟耗时的事情
            while (true)
            {
                //根据信号是否进行阻塞当前事情
                this.manualEvent.WaitOne();
                //打印出当前线程Id
                Console.WriteLine("线程id:{0}", Thread.CurrentThread.ManagedThreadId);
                //模拟耗时的事情
                Thread.Sleep(2000);
            }
        }

        /// <summary>
        /// 开启
        /// </summary>
        public void Start()
        {
            //发送信号(设置为开启信号)
            this.manualEvent.Set();
        }

        /// <summary>
        /// 停止
        /// </summary>
        public void Stop()
        {
            //关闭信号(设置有无信号)
            this.manualEvent.Reset();
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //新建测试使用的类型实例
            PandaTestClass pandaTestClass = new PandaTestClass();
            //无限循环获得用户的输入
            while (true)
            {
                    Console.WriteLine("输入 STOP ,后台线程会挂起");
                Console.WriteLine("输入 START,后台线程会执行");
                //获得用户输入
                string userInput = (Console.ReadLine()).ToLower().Trim();

                //停止被控制的线程
                if (userInput == "stop")
                {
                    Console.WriteLine("线程停止运行");
                    //停止被控制的线程
                    pandaTestClass.Stop();
                }

                //开启被控制的线程
                if (userInput == "start")
                {
                    Console.WriteLine("线程开启运行");
                    //停止被控制的线程
                    pandaTestClass.Start();
                }
            }
        }
    }
}

2.3实例:阻塞主线程来运行被控制线程

using System;
using System.Threading;

namespace PandaTestNamespace
{
    class Program
    {
        //新建线程同步信号事件对象
        //参数设置为false表示默认无信号
        static ManualResetEvent mnlEvt = new ManualResetEvent(false);

        //主线程
        static void Main(string[] args)
        {
            //被控制的线程
            Thread th = new Thread(() =>
            {
                int n = 1;
                int result = 0;
                while (n <= 100)
                {
                    // 模拟耗时任务
                    Thread.Sleep(20);
                    result += n;
                    Console.WriteLine("正在计算:{0}", n++);
                }

                Console.WriteLine("计算结果:{0}", result);
                //将事件状态设置为开启信号,继续执行WaitOne后续的代码
                mnlEvt.Set();
            });

            //开启线程
            th.Start();

            Console.WriteLine("正在等待线程计算……");
            //因为信号同步事件对象初始化为无信号
            //所以这里造成阻塞,需要等待信号到来
            //这时候系统会执行被控制的线程
            mnlEvt.WaitOne();
            Console.WriteLine("计算完毕!");

            //测试使用的等待
            Console.ReadKey();
        }
    }
}

2.4实例:按次序依次执行3个线程

using System;
using System.Threading;

namespace PandaTestNamespace
{
    class Program
    {
        //新建线程同步信号事件对象
        //参数设置为false表示默认无信号
        static ManualResetEvent mnlEvt1 = new ManualResetEvent(false);
        static ManualResetEvent mnlEvt2 = new ManualResetEvent(false);
        static ManualResetEvent mnlEvt3 = new ManualResetEvent(false);

        //主线程
        static void Main(string[] args)
        {
            //被控制的线程1
            Thread th1 = new Thread(() =>
            {
                int n = 1;
                int result = 0;
                while (n <= 100)
                {
                    // 模拟耗时任务
                    Thread.Sleep(20);
                    result += n;
                    Console.WriteLine("线程1正在计算:{0}", n++);
                }

                Console.WriteLine("线程1计算结果:{0}", result);

                //将事件状态设置为开启信号,继续执行WaitOne后续的代码
                mnlEvt1.Set();
            });
            //开启线程1
            th1.Start();

            //被控制的线程2
            Thread th2 = new Thread(() =>
            {
                //等待任务1完成
                mnlEvt1.WaitOne();

                int n = 1;
                int result = 0;
                while (n <= 100)
                {
                    // 模拟耗时任务
                    Thread.Sleep(20);
                    result += n;
                    Console.WriteLine("线程2正在计算:{0}", n++);
                }

                Console.WriteLine("线程2计算结果:{0}", result);

                //将事件状态设置为开启信号,继续执行WaitOne后续的代码
                mnlEvt2.Set();
            });
            //开启线程2
            th2.Start();

            //被控制的线程3
            Thread th3 = new Thread(() =>
            {
                //等待任务2完成
                mnlEvt2.WaitOne();

                int n = 1;
                int result = 0;
                while (n <= 100)
                {
                    // 模拟耗时任务
                    Thread.Sleep(20);
                    result += n;
                    Console.WriteLine("线程3正在计算:{0}", n++);
                }

                Console.WriteLine("线程3计算结果:{0}", result);

                //将事件状态设置为开启信号,继续执行WaitOne后续的代码
                mnlEvt3.Set();
            });
            //开启线程3
            th3.Start();

            Console.WriteLine("正在等待线程计算……");
            //因为信号同步事件对象初始化为无信号
            //所以这里造成阻塞,需要等待信号到来
            //这时候系统会执行被控制的线程
            mnlEvt3.WaitOne();
            Console.WriteLine("计算完毕!");

            //测试使用的等待
            Console.ReadKey();
        }
    }
}

三、使用Task任务延续

在Task中可以使用任务延续的概念实现我们上面Thread想要的效果。通过Task.ContinueWith()方法进行任务延续。

3.1串联任务

上一个任务完成后再进行下一个任务。

Task<int> task1 = Task.Run(() => { return 10; })
       .ContinueWith((preivewTask) =>{ return preivewTask.Result + 10; })
       .ContinueWith((previewTask) => { return previewTask.Result + 10; });

Console.WriteLine(task1.Result); //30

3.2实例:任务延续,任务123会按顺序执行

using System.Threading.Tasks;
//任务1
Task t1 = Task.Run(() => {
    Console.WriteLine("T1 Begin Task");
}).ContinueWith(t1=> {
    Console.WriteLine("T1 Continue Task");
});
//任务2
Task t2 = t1.ContinueWith(t1 => {
    Console.WriteLine("T2 Continue Task");
});
//任务3
Task t3 = t2.ContinueWith(t2 => {
    Console.WriteLine("T3 Continue Task");
});

//等待所有任务完成
Task.WaitAll(new Task[] { t1, t2, t3 });

//output
// T1 Begin Task
// T1 Continue Task
// T2 Continue Task
// T3 Continue Task
// 执行完成
posted @ 2022-04-16 16:51  重庆熊猫  阅读(155)  评论(0编辑  收藏  举报