鸡肋的TaskFactory是时候抛弃了

一、TaskFactory的作用

  • 通过TaskFactoryk可以生成Task
  • 并在系统线程池中执行

1. TaskFactory.StartNew调用同步方法的Case

  • 以下是Task经典的Case
  • 使用TaskFactory的StartNew异步执行3个耗时1秒的任务
  • 共耗时1秒
 var sw = Stopwatch.StartNew();
 var task = Task.Factory.StartNew(() => Hello("张三", 1000));
 var task2 = Task.Factory.StartNew(() => Hello("李四", 1000));
 var task3 = Task.Factory.StartNew(() => Hello("王二", 1000));
 await Task.WhenAll(task, task2, task3);
 sw.Stop();
 _output.WriteLine($"Thread{Environment.CurrentManagedThreadId} Total Span :{sw.Elapsed.TotalMilliseconds}");

void Hello(string name, int time = 10)
{
    Thread.Sleep(time);
    _output.WriteLine($"Thread{Environment.CurrentManagedThreadId} Hello {name},{DateTime.Now:HH:mm:ss.fff}");
}

// Thread10 Hello 张三,16:13:03.553
// Thread31 Hello 王二,16:13:03.553
// Thread11 Hello 李四,16:13:03.553
// Thread17 Total Span :1010.0936

二、Task.Run也能生成并执行Task

1. Task.Run调用同步方法的Case

var sw = Stopwatch.StartNew();
var task = Task.Run(() => Hello("张三", 1000));
var task2 = Task.Run(() => Hello("李四", 1000));
var task3 = Task.Run(() => Hello("王二", 1000));
await Task.WhenAll(task, task2, task3);
sw.Stop();
_output.WriteLine($"Thread{Environment.CurrentManagedThreadId} Total Span :{sw.Elapsed.TotalMilliseconds}");

// Thread31 Hello 王二,16:22:43.545
// Thread10 Hello 张三,16:22:43.545
// Thread11 Hello 李四,16:22:43.545
// Thread15 Total Span :1014.1269

2. Task.Run还能调用异步方法的Case

  • Task.Run还可以胜任异步处理
  • 不知为啥TaskFactory不支持该功能
  • TaskFactory.FromAsync应该能实现类似功能,但明显用起来不方便
var sw = Stopwatch.StartNew();
var task = Task.Run(() => HelloAsync("张三", 1000));
var task2 = Task.Run(() => HelloAsync("李四", 1000));
var task3 = Task.Run(() => HelloAsync("王二", 1000));
await Task.WhenAll(task, task2, task3);
sw.Stop();
_output.WriteLine($"Thread{Environment.CurrentManagedThreadId} Total Span :{sw.Elapsed.TotalMilliseconds}");

async Task HelloAsync(string name, int time = 10, CancellationToken token = default)
{
    await Task.Delay(time, token);
    _output.WriteLine($"Thread{Environment.CurrentManagedThreadId} HelloAsync {name},{DateTime.Now:HH:mm:ss.fff}");
}

// Thread31 HelloAsync 张三,01:35:17.541
// Thread11 HelloAsync 王二,01:35:17.541
// Thread8 HelloAsync 李四,01:35:17.541
// Thread17 Total Span :1008.7947

三、《手搓》TaskFactory能兼顾

1. 《手搓》TaskFactory调用同步方法的Case

  • 《手搓》TaskFactory可平替系统Factory调用同步方法
  • 还能实现系统Factory做不到的并发控制
var options = new ReduceOptions { ConcurrencyLevel = 3 };
var factory = new ConcurrentTaskFactory(options);
var sw = Stopwatch.StartNew();
var task = factory.StartNew(() => Hello("张三", 1000));
var task2 = factory.StartNew(() => Hello("李四", 1000));
var task3 = factory.StartNew(() => Hello("王二", 1000));
await Task.WhenAll(task, task2, task3);
sw.Stop();
_output.WriteLine($"Thread{Environment.CurrentManagedThreadId} Total Span :{sw.Elapsed.TotalMilliseconds}");

// Thread11 Hello 张三,01:50:15.835
// Thread8 Hello 李四,01:50:15.835
// Thread31 Hello 王二,01:50:15.835
// Thread15 Total Span :1010.3326

2. 《手搓》TaskFactory调用异步方法的Case

  • 《手搓》TaskFactory也能调用异步方法
  • 《手搓》TaskFactory可以平替系统Task.Run调用异步方法
var options = new ReduceOptions { ConcurrencyLevel = 3 };
var factory = new ConcurrentTaskFactory(options);
var sw = Stopwatch.StartNew();
var task = factory.StartTask(() => HelloAsync("张三", 1000));
var task2 = factory.StartTask(() => HelloAsync("李四", 1000));
var task3 = factory.StartTask(() => HelloAsync("王二", 1000));
await Task.WhenAll(task, task2, task3);
sw.Stop();
_output.WriteLine($"Thread{Environment.CurrentManagedThreadId} Total Span :{sw.Elapsed.TotalMilliseconds}");

// Thread31 HelloAsync 张三,01:57:06.110
// Thread8 HelloAsync 王二,01:57:06.110
// Thread11 HelloAsync 李四,01:57:06.110
// Thread17 Total Span :1004.2208

3. 《手搓》TaskFactory支持安全双保险

  • 《手搓》TaskFactory支持Token安全保护
  • 《手搓》TaskFactory还支持ItemLife安全保护
  • 系统的Factory和Task.Run只能在调用时判断Token是否取消,安全作用几乎可以忽略
  • 《手搓》TaskFactory也是Factory
  • 既然《手搓》TaskFactory那么强大,为什么还说鸡肋呢?
  • 那是因为这些都是《手搓》线程池提供的技术支持
  • 完全可以直接通过《手搓》线程池实现
  • 可以参考以前的文章异步"伪线程"重构《手搓》线程池,支持任务清退
var options = new ReduceOptions { ConcurrencyLevel = 1, ItemLife = TimeSpan.FromSeconds(1) };
var factory = new ConcurrentTaskFactory(options);
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(1));
var sw = Stopwatch.StartNew();
var task = factory.StartTask(() => HelloAsync("张三", 2000, tokenSource.Token));
Assert.NotNull(task);
try
{
    await task;
    sw.Stop();
}
catch (Exception ex)
{
    sw.Stop();
    _output.WriteLine(($"Thread{Environment.CurrentManagedThreadId} {ex}"));
}
_output.WriteLine($"Thread{Environment.CurrentManagedThreadId} Total Span :{sw.Elapsed.TotalMilliseconds}");
await Task.Delay(2000);

// Thread17 System.Threading.Tasks.TaskCanceledException: A task was canceled.
//    at TaskTests.Tasks.ConcurrentTaskFactoryTests.TaskItemLife() in D:\projects\HandCore.net\UnitTests\TaskTests\Tasks\ConcurrentTaskFactoryTests.cs:line 72
// Thread17 Total Span :1017.4384

四、《手搓》线程池的Task

1. 《手搓》线程池直接调用同步方法的Case

  • 通过Processor可以直接生成异步
var options = new ReduceOptions { ConcurrencyLevel = 3 };
var processor = new Processor();
var pool = options.CreateJob(processor);
var sw = Stopwatch.StartNew();
var task = processor.StartNew(() => Hello("张三", 1000));
var task2 = processor.StartNew(() => Hello("李四", 1000));
var task3 = processor.StartNew(() => Hello("王二", 1000));
await Task.WhenAll(task, task2, task3);
sw.Stop();
_output.WriteLine($"Thread{Environment.CurrentManagedThreadId} Total Span :{sw.Elapsed.TotalMilliseconds}");

// Thread31 Hello 王二,02:15:07.714
// Thread10 Hello 李四,02:15:07.714
// Thread11 Hello 张三,02:15:07.714
// Thread16 Total Span :1011.4239

2. 《手搓》线程池直接调用异步方法的Case

  • 通过Processor可以直接调用异步方法
var options = new ReduceOptions { ConcurrencyLevel = 3 };
var processor = new Processor();
var pool = options.CreateJob(processor);
var sw = Stopwatch.StartNew();
var task = processor.StartTask(() => HelloAsync("张三", 1000, CancellationToken.None));
var task2 = processor.StartTask(() => HelloAsync("李四", 1000, CancellationToken.None));
var task3 = processor.StartTask(() => HelloAsync("王二", 1000, CancellationToken.None));
await Task.WhenAll(task, task2, task3);
sw.Stop();
_output.WriteLine($"Thread{Environment.CurrentManagedThreadId} Total Span :{sw.Elapsed.TotalMilliseconds}");

// Thread8 HelloAsync 李四,02:25:06.458
// Thread11 HelloAsync 张三,02:25:06.458
// Thread31 HelloAsync 王二,02:25:06.458
// Thread17 Total Span :1005.3909

3. 《手搓》线程池双保险执行Task的Case

  • 《手搓》线程池支持双保险执行Task
  • 实际上QueueTaskScheduler(《手搓》TaskFactory核心组件)相当于一个特殊的Processor
  • ConcurrentTaskFactory内部就包含了一个手搓线程池
  • 从某种意义来说,ConcurrentTaskFactory(《手搓》TaskFactory)有点多余
  • 《手搓》线程池几乎能实现《手搓》TaskFactory的所有功能
  • 还能提供其他更多功能
  • 也可以说《手搓》TaskFactory是阉割版的线程池
var options = new ReduceOptions { ConcurrencyLevel = 1 };
var processor = new Processor();
var pool = options.CreateJob(processor);
var tokenSource = new CancellationTokenSource();
tokenSource.CancelAfter(TimeSpan.FromSeconds(1));
var sw = Stopwatch.StartNew();
var task = processor.StartTask(() => HelloAsync("张三", 2000, tokenSource.Token));
Assert.NotNull(task);
try
{
    await task;
    sw.Stop();
}
catch (Exception ex)
{
    sw.Stop();
    _output.WriteLine(($"Thread{Environment.CurrentManagedThreadId} {ex}"));
}
_output.WriteLine($"Thread{Environment.CurrentManagedThreadId} Total Span :{sw.Elapsed.TotalMilliseconds}");
await Task.Delay(2000);

// Thread17 System.Threading.Tasks.TaskCanceledException: A task was canceled.
//    at TaskTests.Job.ProcessorTests.HelloAsync(String name, Int32 time, CancellationToken token) in D:\projects\HandCore.net\UnitTests\TaskTests\Job\ProcessorTests.cs:line 207
//    at Hand.Tasks.Internal.TaskFuncState.RunAsync(CancellationToken token) in D:\projects\HandCore.net\Hand.Tasks\Internal\TaskFuncState.cs:line 32
//    at Hand.Job.Processor.Hand.Job.IQueueProcessor<Hand.States.IState<System.Boolean>>.Run(IQueue`1 queue, ThreadJobService`1 service, CancellationToken token) in D:\projects\HandCore.net\Hand.Job\Processor.cs:line 155
//    at TaskTests.Job.ProcessorTests.TaskItemLife() in D:\projects\HandCore.net\UnitTests\TaskTests\Job\ProcessorTests.cs:line 189
// Thread17 Total Span :1008.4003

五、《手搓》线程池与系统线程池有区别吗?

  • 当然有区别,而且区别很大
  • 系统线程池是面对过程的,也就是委托方法,可以带一个object参数
  • 《手搓》线程池是面向对象的

1. 《手搓》线程池的组成

  • 其一要处理的对象类型
  • 其二对象集合(队列)
  • 其三是处理器(处理逻辑)
  • 可以支持高度定制
class ReduceJobService<TItem>
{
    /// <summary>
    /// 队列
    /// </summary>
    public IQueue<TItem> Queue { get; }
    /// <summary>
    /// 处理器
    /// </summary>
    public IQueueProcessor<TItem> Processor { get; }
}

1. 《手搓》线程池默认处理器

  • 这是一个有状态任务处理器
  • 线程池保护可以基于这个状态
  • 其中的状态为bool类型,true为有效,false就表示线程池应该清退它
  • Processor支持同步(IJobItem)和异步(IAsyncJobItem)两种任务
  • 通过实现IExceptionable订阅异常处理
  • 通过实现ICancelable订阅取消处理
  • 为了兼容系统线程池使用习惯,默认提供了基于委托(方法)的处理逻辑
  • 实际《手搓》线程池是面向对象的
  • 《手搓》线程池是可以高度私人订制的线程池
  • 有谁会不喜欢私人定制自己的线程池呢
class Processor(IQueue<IState<bool>> queue)
    : IQueueProcessor<IState<bool>>;
/// <summary>
/// 状态
/// </summary>
/// <typeparam name="TStatus"></typeparam>
interface IState<TStatus>
{
    /// <summary>
    /// 状态
    /// </summary>
    TStatus Status { get; }
}
interface IExceptionable
{
    /// <summary>
    /// 触发异常回调
    /// </summary>
    /// <param name="exception"></param>
    void OnException(Exception exception);
}
interface ICancelable
{
    /// <summary>
    /// 取消
    /// </summary>
    void OnCancel();
}

2. 《手搓》线程池支持私人定制任务

2.1 必需实现接口IState<bool>

  • 只有一个Status属性告诉线程池你这个任务当前是否有效
  • 如果Status为false线程池不会执行,直接忽略该任务
  • 如果执行中途变成false,线程池会清退该任务

2.2 实现IJobItem或IAsyncJobItem

  • 同步处理实现IJobItem
  • 异步处理实现IAsyncJobItem

2.3 可选实现IExceptionable

  • 实现IExceptionable可以订阅异常处理

2.4 可选实现ICancelable

  • 实现ICancelable可以订阅取消处理
  • 特别要注意,有些取消往往会触发异常
  • 如果要完全处理取消,一般IExceptionable也是要实现的

3. 《手搓》线程池支持私人定制处理器

  • 通过实现定义IQueueProcessor接口可以定制处理器
  • 定制自己的处理器逻辑可能是一个更好的选择
  • 比如为了性能专门处理异步或同步
  • 或者处理特殊的数据结构和封装自己的异常处理逻辑

4. ActionProcessor

  • ActionProcessor非常简单,只支持执行Action
  • 正应为它逻辑简单,能获得更好的性能,专制这方面的强迫症患者
  • 虽然简单,线程池的任务清退和并发控制一点都不打折
  • 把它是比喻为小李飞刀一点都不为过
 var options = new ReduceOptions { ConcurrencyLevel = 1 };
 var pool = options.CreateJob(ActionProcessor.Instance);
 pool.Add(() => Hello("张三"));
 pool.Add(() => Hello("李四"));
 await Task.Delay(1000);

void Hello(string name)
{
    _output.WriteLine($"Thread{Environment.CurrentManagedThreadId} Hello {name},{DateTime.Now:HH:mm:ss.fff}");
    Thread.Sleep(1);
}

// Thread11 Hello 张三,09:57:57.195
// Thread11 Hello 李四,09:57:57.202

好了,就介绍到这里,更多信息请查看源码库
源码托管地址: https://github.com/donetsoftwork/HandCore.net ,欢迎大家直接查看源码。
gitee同步更新:https://gitee.com/donetsoftwork/HandCore.net

如果大家喜欢请动动您发财的小手手帮忙点一下Star,谢谢!!!

posted on 2025-11-10 09:19  xiangji  阅读(160)  评论(3)    收藏  举报

导航