有这么一个场景:
假设有10000个相同逻辑的任务需要执行,而且希望是在后台异步运行;
然后在执行过程中,由于一些不确定的因素(内存、网络 等),限制了同时运行的数量(这里先假定可以同时运行50个任务);
最后关键的是,每一个任务所需的参数都不尽相同!
这就好比,有10000个工人要去做工,干的活都一样,但每个人所带的工具却不尽相同,然后又由于一些条件的限制,每次只允许50个工人在同时工作;该怎么办呢?
思路:
在现实的情况中,我们需要有一个工人调度的管理员,来随时调度这10000个带有不同工具的工人,
最开始先派上50个工人去做工,过个三五分钟就去查看一下工人的做工情况,
看了几次后,发现有20个工人已经做完了,于是就把这已经完成的20个工人撤下来,再补上新的20个工人,
又过了一段时间,发现又有15个工人做完了,于是又把这已完成的15个工人撤下来,再补上新的15个工人,
如此循环往复,只要有完成的,就撤走,换上新的...... 直到10000个工人都完成做工。
如此一来,便可以发挥最大的利用效率,总是在 允许 可做工的 范围内,拥有最多的做工人数,直到全部做工完成。
我在实际工作中的应用:
在下是做数据采集的(网络爬虫),经常会遇到这么一个情况,
发起的采集请求都是后台异步的,采集的数据有成百上千页,采集每一页的数据所发起的请求参数又不相同,然后网站又不稳定,不能同时请求太多,
于是就自己琢磨了一个功能任务自动控制类。
下面是代码,分享出来,给大家参考:
注意:看我的代码注释,帮助你理解我的实现思路,给你一个参考。
自动功能控制类:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ConsoleAppDemo
{
/// <summary>
/// 自动功能控制类
/// </summary>
public sealed class AutoActionControl
{
/// <summary>
/// AutoActionControl 无参构造函数
/// </summary>
public AutoActionControl() { }
/// <summary>
/// AutoActionControl 构造函数
/// </summary>
/// <param name="action">
/// <para>Func 委托,功能函数;传入一个 object 参数,返回一个 Task 结果</para>
/// <para>注:这个函数最好加上 try{} catch (Exception ex) { //这里可以写异常日志 }</para>
/// </param>
public AutoActionControl(Func<object, Task> action)
{
AutoAction = action;
}
/// <summary>
/// AutoActionControl 构造函数
/// </summary>
/// <param name="action">
/// <para>Func 委托,功能函数;传入一个 object 参数,返回一个 Task 结果</para>
/// <para>注:这个函数最好加上 try{} catch (Exception ex) { //这里可以写异常日志 }</para>
/// </param>
/// <param name="paramsList">
/// <para>参数集合 【 AutoAction 所需要的参数 】</para>
/// <para>当 AutoAction 功能函数不需要参数时,此属性赋值为 null (也可以不用设置,默认就是 null 值)</para>
/// <para>
/// 当 MaxExecuteCount != 0 时(有限执行),且 AutoAction 功能函数需要参数,
/// 此属性赋值为一个参数集合,集合的元素个数(Count 值)须等于 MaxExecuteCount
/// </para>
/// <para>当 MaxExecuteCount == 0 时(无限执行),此属性无意义;这种情况应当直接把“传参过程”写在 AutoAction 功能函数里</para>
/// </param>
public AutoActionControl(Func<object, Task> action, List<object> paramsList)
{
AutoAction = action;
ParamsList = paramsList;
}
/// <summary>
/// AutoActionControl 构造函数
/// </summary>
/// <param name="action">
/// <para>Func 委托,功能函数;传入一个 object 参数,返回一个 Task 结果</para>
/// <para>注:这个函数最好加上 try{} catch (Exception ex) { //这里可以写异常日志 }</para>
/// </param>
/// <param name="paramsList">
/// <para>参数集合 【 AutoAction 所需要的参数 】</para>
/// <para>当 AutoAction 功能函数不需要参数时,此属性赋值为 null (也可以不用设置,默认就是 null 值)</para>
/// <para>
/// 当 MaxExecuteCount != 0 时(有限执行),且 AutoAction 功能函数需要参数,
/// 此属性赋值为一个参数集合,集合的元素个数(Count 值)须等于 MaxExecuteCount
/// </para>
/// <para>当 MaxExecuteCount == 0 时(无限执行),此属性无意义;这种情况应当直接把“传参过程”写在 AutoAction 功能函数里</para>
/// </param>
/// <param name="maxExecuteCount">
/// <para>最大执行数量;也就是 功能函数最多运行几次</para>
/// <para>如果设为 0,则不计总量的一直运行;属性 RunAlready 和 RunCompleted 也将没有意义。</para>
/// </param>
/// <param name="simultaneousExecute">同时执行数量;也就是 最多同时运行几个功能函数</param>
/// <param name="scanningFrequency">扫描频率,单位:毫秒(每多少毫秒扫描一次)</param>
public AutoActionControl(Func<object, Task> action, List<object> paramsList,
int maxExecuteCount, int simultaneousExecute, int scanningFrequency)
{
AutoAction = action;
ParamsList = paramsList;
MaxExecuteCount = maxExecuteCount;
SimultaneousExecute = simultaneousExecute;
ScanningFrequency = scanningFrequency;
}
/// <summary>
/// 声明一个加锁对象,用来控制同时执行的任务数量
/// </summary>
private readonly object _lockTaskList = new object();
/// <summary>
/// Task 任务集合
/// </summary>
private readonly List<Task> _taskList = new List<Task>();
/// <summary>
/// 声明一个 bool 变量,用来指示是否停止任务的运行。true/false,是/否
/// </summary>
private bool _isStopTask = true;
#region 核心属性
/// <summary>
/// 正在运行的数量(该属性值 只会大于等于 0 且 小于等于 SimultaneousExecute 值)
/// </summary>
public int RunningCount
{
get
{
return _taskList.Count;
}
}
/// <summary>
/// 指示自动功能是否已全部完成【在调用 StartExecute() 方法前调用该属性,无意义】
/// </summary>
public bool IsFinish
{
get
{
if (MaxExecuteCount != 0)
{
return (RunCompleted >= MaxExecuteCount);
}
else
{
return (_isStopTask == true && _taskList.Count == 0);
}
}
}
/// <summary>
/// <para>Func 委托,功能函数;传入一个 object 参数,返回一个 Task 结果</para>
/// <para>注:这个函数最好加上 try{} catch (Exception ex) { //这里可以写异常日志 }</para>
/// </summary>
public Func<object, Task> AutoAction { get; set; } = null;
/// <summary>
/// <para>参数集合 【 AutoAction 所需要的参数 】</para>
/// <para>当 AutoAction 功能函数不需要参数时,此属性赋值为 null (也可以不用设置,默认就是 null 值)</para>
/// <para>
/// 当 MaxExecuteCount != 0 时(有限执行),且 AutoAction 功能函数需要参数,
/// 此属性赋值为一个参数集合,集合的元素个数(Count 值)须等于 MaxExecuteCount
/// </para>
/// <para>当 MaxExecuteCount == 0 时(无限执行),此属性无意义;这种情况应当直接把“传参过程”写在 AutoAction 功能函数里</para>
/// </summary>
public List<object> ParamsList { get; set; } = null;
private int runAlready = 0;
/// <summary>
/// <para>已经运行的数量(只要是已开始执行的任务,无需完成,该属性值就增加计数)</para>
/// <para>当 MaxExecuteCount = 0 时,该属性无意义,返回 -1</para>
/// </summary>
public int RunAlready
{
get
{
return MaxExecuteCount != 0 ? runAlready : -1;
}
private set
{
runAlready = value;
}
}
private int runCompleted = 0;
/// <summary>
/// <para>运行已完成的数量(只有运行已完成的任务,该属性值才会增加计数)</para>
/// <para>当 MaxExecuteCount = 0 时,该属性无意义,返回 -1</para>
/// </summary>
public int RunCompleted
{
get
{
return MaxExecuteCount != 0 ? runCompleted : -1;
}
private set
{
runCompleted = value;
}
}
private int maxExecuteCount = 100;
/// <summary>
/// <para>最大执行数量(默认 100 个);也就是 功能函数最多运行几次</para>
/// <para>如果设为 0,则不计总量的一直运行;属性 RunAlready 和 RunCompleted 也将没有意义。</para>
/// </summary>
public int MaxExecuteCount
{
get
{
return maxExecuteCount;
}
set
{
maxExecuteCount = value < 0 ? 100 : value;
}
}
private int simultaneousExecute = 10;
/// <summary>
/// 同时执行数量(默认 10 个);也就是 最多同时运行几个功能函数 (类似池的概念,有空闲的就自动加入下一个)
/// </summary>
public int SimultaneousExecute
{
get
{
if (simultaneousExecute > MaxExecuteCount)
{
simultaneousExecute = MaxExecuteCount;
}
return simultaneousExecute;
}
set
{
simultaneousExecute = value < 1 ? 10 : value;
}
}
private int scanningFrequency = 500;
/// <summary>
/// 扫描频率,单位:毫秒(每多少毫秒扫描一次)。默认:500
/// </summary>
public int ScanningFrequency
{
get
{
return scanningFrequency;
}
set
{
scanningFrequency = value < 1 ? 500 : value;
}
}
#endregion
/// <summary>
/// 任务调度(控制同时执行数)
/// </summary>
private void TaskScheduling()
{
if (MaxExecuteCount != 0)
{
if (RunAlready >= MaxExecuteCount)
{
_isStopTask = true;
if (_taskList.Count < 1)
{
// 已经执行完毕
return;
}
}
}
if (_taskList.Count > 0)
{
if (_taskList.Count <= SimultaneousExecute)
{
int i = 0;
while (i < _taskList.Count)
{
if (_taskList[i].IsCompleted)
{
_taskList[i].Dispose();
_taskList.Remove(_taskList[i]);
if (MaxExecuteCount != 0)
{
RunCompleted++;
}
}
else
{
i++;
}
}
JoinExecuteTaskAsync().Wait();
}
}
else
{
JoinExecuteTaskAsync().Wait();
}
}
/// <summary>
/// 加入执行任务
/// </summary>
private async Task JoinExecuteTaskAsync()
{
if (!_isStopTask)
{
int residue = SimultaneousExecute - _taskList.Count; //剩余数
if (MaxExecuteCount != 0)
{
while (residue > 0 && RunAlready < MaxExecuteCount)
{
_taskList.Add(await Task.Factory.StartNew(AutoAction, ParamsList?[RunAlready]));
RunAlready++;
residue--;
}
}
else
{
while (residue > 0)
{
_taskList.Add(await Task.Factory.StartNew(AutoAction, null));
residue--;
}
}
}
}
/// <summary>
/// 启动执行(该方法会重新启动执行)
/// </summary>
public AutoActionControl StartExecute()
{
if (AutoAction == null)
{
throw new Exception("不存在自动功能。");
}
if (MaxExecuteCount != 0)
{
if (ParamsList != null && ParamsList.Count != MaxExecuteCount)
{
throw new Exception("功能函数的参数集合不符合规格。");
}
}
_isStopTask = false;
RunAlready = 0;
RunCompleted = 0;
_taskList.Clear();
Task.Run(async () =>
{
while (true)
{
lock (_lockTaskList)
{
TaskScheduling();
}
if (_taskList.Count < 1)
{
break;
}
await Task.Delay(ScanningFrequency);
}
});
return this;
}
/// <summary>
/// 停止执行(正常情况会返回 true,无实际意义)
/// </summary>
/// <returns></returns>
public async Task<bool> StopExecuteAsync()
{
_isStopTask = true;
await Task.Run(() =>
{
while (true)
{
if (_taskList.Count < 1)
{
break;
}
}
});
return true;
}
/// <summary>
/// 继续执行
/// </summary>
public void GoOnExecute()
{
if (AutoAction == null)
{
throw new Exception("不存在自动功能。");
}
if (MaxExecuteCount != 0)
{
if (ParamsList != null && ParamsList.Count != MaxExecuteCount)
{
throw new Exception("功能函数的参数集合不符合规格。");
}
if (RunAlready >= MaxExecuteCount)
{
_isStopTask = true;
return;
}
}
_isStopTask = false;
Task.Run(async () =>
{
while (true)
{
lock (_lockTaskList)
{
TaskScheduling();
}
if (_taskList.Count < 1)
{
break;
}
await Task.Delay(ScanningFrequency);
}
});
}
/// <summary>
/// 等待全部任务完成
/// <para>注:使用该方法必须等待该方法的完成,否则使用无效</para>
/// <para>当 MaxExecuteCount = 0 时,该方法无意义,结果为 false</para>
/// </summary>
/// <param name="pollingTime">轮询时间,单位 ms 毫秒,默认 500</param>
/// <returns></returns>
public async Task<bool> WaitingAllFinishAsync(int pollingTime = 500)
{
if (MaxExecuteCount == 0)
{
return false;
}
return await Task.Run(async () =>
{
while (true)
{
await Task.Delay(pollingTime);
if (this.IsFinish)
{
break;
}
}
return true;
});
}
}
}
对上面自动功能控制类的使用:
注释部分是另一种使用情况 ; 最后的是一个“过桥”的示例
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleAppDemo
{
class Program
{
static void Main(string[] args)
{
// Action 委托,功能函数
async Task func(object param)
{
try
{
await Task.Delay(3 * 1000);
Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.ffff")} ==> "
+ $"耗时操作 ==> 参数:{(param == null ? "null 无用" : param.ToString())}\n");
}
catch (Exception ex)
{
Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss.ffff")} ==> 异常信息:{ex.Message}\n");
}
}
// 模拟参数
List<object> paramsList = new List<object>();
for (int i = 1; i <= 8; i++)
{
paramsList.Add(i + 10);
}
#region MaxExecuteCount != 0 示例
// 初始化 自动功能控制类
AutoActionControl autoAction = new AutoActionControl(func, paramsList)
{
SimultaneousExecute = 3, // 最多同时运行 SimultaneousExecute 个数量(类似池的概念,有空闲的就自动加入下一个)
// 功能函数最大运行 MaxExecuteCount 次数(当前功能函数最多运行几次);
// 如果设为 0,则不计总量的一直运行;属性 RunAlready 和 RunCompleted 也将没有意义。
MaxExecuteCount = paramsList.Count,
};
// IsFinish、RunAlready、RunCompleted 属性用法示例
Task.Run(async () =>
{
while (true)
{
await Task.Delay(1000);
if (autoAction.IsFinish)
{
Console.WriteLine($"autoAction.IsFinish = {autoAction.IsFinish}\n"
+ $"autoAction.RunAlready = {autoAction.RunAlready}\n"
+ $"autoAction.RunCompleted = {autoAction.RunCompleted}\n");
break;
}
else
{
Console.WriteLine($"autoAction.IsFinish = {autoAction.IsFinish}\n"
+ $"autoAction.RunAlready = {autoAction.RunAlready}\n"
+ $"autoAction.RunCompleted = {autoAction.RunCompleted}\n");
}
}
});
autoAction.StartExecute(); // 启动执行
System.Threading.Thread.Sleep(5 * 1000); // 模拟运行 5 秒后,
var task = autoAction.StopExecuteAsync(); // 停止自动运行
task.Wait();
bool res = task.Result; // 得到一个返回值(正常情况会返回 true,无实际意义)
System.Threading.Thread.Sleep(10 * 1000); // 模拟停止 10 秒后,
autoAction.GoOnExecute(); // 再继续自动运行
System.Threading.Thread.Sleep(20 * 1000); // 模拟运行 20 秒后,功能函数全部运行完毕,
Console.WriteLine("\n- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
Console.WriteLine("模拟运行 20 秒后,功能函数全部运行完毕,\n然后,再次调用 StartExecute() 会重新启动并开始运行");
Console.WriteLine("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -\n");
autoAction.StartExecute(); // 然后,再次调用 StartExecute() 会重新启动并开始运行
#endregion
#region MaxExecuteCount == 0 示例
//AutoActionControl autoAction = new AutoActionControl(func)
//{
// SimultaneousExecute = 2, // 最多同时运行 SimultaneousExecute 个数量(类似池的概念,有空闲的就自动加入下一个)
// // 功能函数最大运行 MaxExecuteCount 次数(当前功能函数最多运行几次);
// // 如果设为 0,则不计总量的一直运行;属性 RunAlready 和 RunCompleted 也将没有意义。
// MaxExecuteCount = 0
//};
IsFinish、RunAlready、RunCompleted 属性用法示例
//Task.Run(async () =>
//{
// while (true)
// {
// await Task.Delay(1000);
// if (autoAction.IsFinish)
// {
// Console.WriteLine($"autoAction.IsFinish = {autoAction.IsFinish}\n"
// + $"autoAction.RunAlready = {autoAction.RunAlready}\n"
// + $"autoAction.RunCompleted = {autoAction.RunCompleted}\n");
// break;
// }
// else
// {
// Console.WriteLine($"autoAction.IsFinish = {autoAction.IsFinish}\n"
// + $"autoAction.RunAlready = {autoAction.RunAlready}\n"
// + $"autoAction.RunCompleted = {autoAction.RunCompleted}\n");
// }
// }
//});
//autoAction.StartExecute(); // 启动执行
//System.Threading.Thread.Sleep(5 * 1000); // 模拟运行 5 秒后,
//var task = autoAction.StopExecuteAsync(); // 停止自动运行
//task.Wait();
//bool res = task.Result; // 得到一个返回值(正常情况会返回 true,无实际意义)
//System.Threading.Thread.Sleep(5 * 1000); // 模拟停止 5 秒后,
//autoAction.GoOnExecute(); // 再继续自动运行
//System.Threading.Thread.Sleep(5 * 1000); // 模拟运行 5 秒后,
//var task2 = autoAction.StopExecuteAsync(); // 最终停止
//task2.Wait();
//bool res2 = task2.Result; // 得到一个返回值(正常情况会返回 true,无实际意义)
#endregion
// 自动任务控制测试( 过桥 )
{
Task.Run(async () =>
{
List<object> list = new List<object>();
for (int i = 0; i < 10; i++)
{
list.Add(i + 1);
}
static async Task func(object index)
{
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffff} 第 {(int)index} 个人 上桥");
await Task.Delay(1000);
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffff} 第 {(int)index} 个人 正在过桥");
int ss = new Random().Next(3, 9);
await Task.Delay(ss * 1000);
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffff} 第 {(int)index} 个人 已过桥 - {ss + 1}s");
}
var autoAction = new AutoActionControl(func, list, list.Count, 3, 200).StartExecute();
await autoAction.WaitingAllFinishAsync(200);
Console.WriteLine($"{DateTime.Now:HH:mm:ss.ffff} 所有人都已过桥");
});
}
Console.ReadKey();
}
}
}
上面的代码是在下自己的一个实现思路,也很简单,但很实用。你可以直接复制代码,自己建一个控制台项目,尝试运行看看。