C# Task任务队列

本文介绍了如何使用C#的Task API解决需要按顺序延时执行任务的需求,避免UI界面卡死。通过逐步改进,从基本的Task用法到异步委托,再到封装任务队列,最终实现异步任务队列按顺序执行,确保任务顺序执行的同时,不会阻塞主线程。示例代码展示了具体的实现方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、需求

二、基本的Task用法

三、让Task任务按顺序执行

四、使用异步委托解决UI界面卡死问题

五、异步任务队列按顺序执行

六、封装任务队列

七、封装任务队列优化版

结束


一、需求

众所周知,方法体内代码是从上往下执行的,在我们工作中经常会遇到一些需要延时执行,但又必须按顺序来执行的需求。这要怎么解决呢。微软官方提供的Task API就是专门来解决这个问题的。那么下面就开始吧。

二、基本的Task用法

新建一个Winfrom项目

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace 线程2
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            Task task1 = new Task(() =>
            {
                Thread.Sleep(400);
                Console.WriteLine("task1");
            });
            Task task2 = new Task(() =>
            {
                Thread.Sleep(300);
                Console.WriteLine("task2");
            });
            Task task3 = new Task(() =>
            {
                Thread.Sleep(200);
                Console.WriteLine("task3");
            });
            Task task4 = new Task(() =>
            {
                Thread.Sleep(100);
                Console.WriteLine("task4");
            });
            task1.Start();
            task2.Start();
            task3.Start();
            task4.Start();
        }
    }
}

运行:

由于各个任务内部延时不同,最先执行的Task1,反而最后一个执行完,如果既要做延时操作,又要求从任务按顺序执行,要怎么解决呢?

三、让Task任务按顺序执行

修改代码:

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace 线程2
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private List<Task> TaskList = new List<Task>();

        private void Form1_Load(object sender, EventArgs e)
        {
            Task task1 = new Task(() =>
            {
                Thread.Sleep(400);
                Console.WriteLine("task1");
            });
            Task task2 = new Task(() =>
            {
                Thread.Sleep(300);
                Console.WriteLine("task2");
            });
            Task task3 = new Task(() =>
            {
                Thread.Sleep(200);
                Console.WriteLine("task3");
            });
            Task task4 = new Task(() =>
            {
                Thread.Sleep(100);
                Console.WriteLine("task4");
            });

            TaskList.Add(task1);
            TaskList.Add(task2);
            TaskList.Add(task3);
            TaskList.Add(task4);

            foreach (Task task in TaskList)
            {
                task.Start();
                task.Wait();
            }
        }
    }
}

运行:

用上面的方法虽然有效,你可以看看,点击界面的时候,界面处鼠标指针会一直转圈,导致winfrom界面卡住,无法操作,这是因为使用Thread.Sleep 导致主线程阻塞,下面就来解决UI界面卡死的问题。

四、使用异步委托解决UI界面卡死问题

代码:

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace 线程2
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }


        private List<Task> TaskList = new List<Task>();


        private void Button_Calculate_Click(object sender, EventArgs e)
        {
            Task task1 = new Task(async () =>
            {
                await Task.Delay(TimeSpan.FromSeconds(4));
                Console.WriteLine("task1");
            });
            Task task2 = new Task(async () =>
            {
                await Task.Delay(TimeSpan.FromSeconds(3));
                Console.WriteLine("task2");
            });
            Task task3 = new Task(async () =>
            {
                await Task.Delay(TimeSpan.FromSeconds(2));
                Console.WriteLine("task3");
            });
            Task task4 = new Task(async () =>
            {
                await Task.Delay(TimeSpan.FromSeconds(1));
                Console.WriteLine("task4");
            });

            TaskList.Add(task1);
            TaskList.Add(task2);
            TaskList.Add(task3);
            TaskList.Add(task4);

            foreach (Task task in TaskList)
            {
                task.Start();
                task.Wait();
            }
        }
    }
}

运行:

用异步方式虽然界面不会卡住,但另一个问题来了,task.wait()方法似乎没有效果。里面的任务队列依然没有按顺序来执行。那么如何即使用异步执行,也不阻塞主线程,而且要任务按顺序来执行呢?

五、异步任务队列按顺序执行

代码:

private void Test()
{
    Task.Run(() =>
    {
        Task t1 = new Task(() => 
        {
            Thread.Sleep(2000);
            Console.WriteLine("t1");
        });
        t1.Start();
        t1.Wait();

        Task t2 = new Task(() => 
        {
            Thread.Sleep(1000);
            Console.WriteLine("t2");
        });
        t2.Start();
        t2.Wait();

        Console.WriteLine("线程执行完毕");
    });
}

运行:

效果是实现了,代码看起来好搓啊,强迫症都犯了,没关系,可以使用更优雅的写法:

private async void Test()
{
    await Task.Run(async () =>
    {
        await Task.Delay(4000);
        Trace.WriteLine("第1个线程执行");
    });
    await Task.Run(async () =>
    {
        await Task.Delay(3000);
        Trace.WriteLine("第2个线程执行");
    });
    await Task.Run(async () =>
    {
        await Task.Delay(2000);
        Trace.WriteLine("第3个线程执行");
    });
}

运行:

到此为止,功能就实现了,这个需求在Unity3d中使用协程很简单的几句就可以搞定,但在Winfrom等项目的开发中,确实有点繁琐。

六、封装任务队列

下面的代码我不认为是一个很好的写法,需要添加任务后,还得手动去调用,如果能添加到任务队列就不管了,让其自己自动按顺序来执行任务,岂不是更好,读者如果有兴趣自己去完善这个猜想。另外,在游戏开发中,比如RGP项目中,有专门的任务系统,它和我这个帖子的概念不能混为一谈,RPG任务系统更多的偏向数据的存取,来获取任务的完成状态。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Utils
{
    public class TaskQueue
    {
        /// <summary>
        /// 任务列表
        /// </summary>
        private List<Task> TaskList = null;
        /// <summary>
        /// 是否在执行任务中
        /// </summary>
        private bool isPerformTask = false;
        /// <summary>
        /// 执行完任务的回调
        /// </summary>
        public Action CallBack = null;


        private static TaskQueue _instance = null;
        public static TaskQueue Instance
        {
            get
            {
                if (_instance == null)
                    _instance = new TaskQueue();
                return _instance;
            }
        }

        /// <summary>
        /// 添加任务
        /// </summary>
        /// <param name="task"></param>
        public void AddTask(Task task)
        {
            if (isPerformTask)
            {
                Console.WriteLine("[TaskQueue]任务正在执行中,此时不能做赋值操作");
                return;
            }

            if (task != null)
            {
                TaskList.Add(task);
            }
        }

        /// <summary>
        /// 执行任务
        /// </summary>
        public void PerformTask()
        {
            if (isPerformTask)
            {
                Console.WriteLine("[TaskQueue]任务正在执行中,不可重复调用");
                return;
            }
            if (TaskList == null || TaskList.Count == 0)
            {
                Console.WriteLine("[TaskQueue]任务列表为空");
                return;
            }         

            Task.Run(() =>
            {
                isPerformTask = true;

                foreach (Task item in TaskList)
                {
                    item.Start();
                    item.Wait();
                }

                TaskList.Clear();
                isPerformTask = false;

                if (CallBack != null) CallBack();
            });
        }

        private TaskQueue()
        {
            TaskList = new List<Task>();
        }
    }
}

调用:

Task task1 = new Task(() =>
{
    Thread.Sleep(1000);
    Console.WriteLine("t1");
});
Task task2 = new Task(() =>
{
    Thread.Sleep(2000);
    Console.WriteLine("t2");
});
Task task3 = new Task(() =>
{
    Console.WriteLine("t3");
});
Action callback = () =>
{
    Console.WriteLine("所有任务执行完成");
};
TaskQueue.Instance.AddTask(task1);
TaskQueue.Instance.AddTask(task2);
TaskQueue.Instance.AddTask(task3);
TaskQueue.Instance.CallBack = callback;
TaskQueue.Instance.PerformTask();

运行:

七、封装任务队列优化版

2022.07.26 优化,和上面版本的区别是,任务添加完成后,会自动调用。

执行任务队列,任务会一个个执行,中间可等待,并且不会阻塞主线程,在运行的时候,可以向任务队列中添加任务,不会影响任务的执行,

代码:

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

internal class TaskQueue
{
    /// <summary>
    /// 任务队列
    /// </summary>
    private static Queue<TaskData> QueuesTask = new Queue<TaskData>();
    /// <summary>
    /// 任务队列是否在执行中
    /// </summary>
    private static bool isExecuteing = false;

    /// <summary>
    /// 任务是否进行中
    /// </summary>
    /// <returns></returns>
    public static bool IsTasking { get => isExecuteing; }

    /// <summary>
    /// 添加任务,任务会按照队列自动执行
    /// </summary>
    /// <param name="task"></param>
    public static void AddTaskAndRuning(TaskData taskData)
    {
        if (taskData == null) return;

        QueuesTask.Enqueue(taskData);

        StartPerformTask();
    }

    /// <summary>
    /// 执行任务
    /// </summary>
    private static async void StartPerformTask()
    {
        if (isExecuteing) return; 

        while (QueuesTask.Count > 0)
        {
            isExecuteing = true;
            await Task.Run(() =>
            {
                var taskDatas = QueuesTask.Dequeue();
                var task = taskDatas.Tasks;
                task.Start();
                task.Wait();

                if (taskDatas.CallBack != null) 
                    taskDatas.CallBack(taskDatas.Other);
            });
        }

        isExecuteing = false;
    }

    private TaskQueue() { }
}

public class TaskData
{
    /// <summary>
    /// 任务名
    /// </summary>
    public string Name { get; set; }
    /// <summary>
    /// 其他内容
    /// </summary>
    public string Other { get; set; }
    /// <summary>
    /// 任务
    /// </summary>
    public Task Tasks { get; set; }
    /// <summary>
    /// 任务完成后的回调
    /// </summary>
    public Action<string> CallBack { get; set; }
}

调用:

TaskData taskData2 = new TaskData();
taskData2.Other = "来了老弟";
taskData2.Tasks = new Task(() =>
{
    Thread.Sleep(4000);
    Console.WriteLine("[Form1]taskData2");
});
taskData2.CallBack = (string res) =>
{
    Console.WriteLine("[Form1]taskData2执行完的回调,返回的参数是:{0}", res);
};
TaskQueue.AddTaskAndRuning(taskData2);


TaskData taskData3 = new TaskData();
taskData3.Other = "老六";
taskData3.Tasks = new Task(() =>
{
    Thread.Sleep(5000);
    Console.WriteLine("[Form1]taskData3");
});
taskData3.CallBack = (string res) =>
{
    Console.WriteLine("[Form1]taskData3执行完的回调,返回的参数是:{0}", res);
};
TaskQueue.AddTaskAndRuning(taskData3);

运行:

结束

如果这个帖子对你有用,欢迎给我点赞 + 留言,谢谢

end

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

熊思宇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值