Task.Run()、Task.Factory.StartNew() 和 new Task() 的区别

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

Task.Run()、Task.Factory.StartNew() 和 new Task() 的区别

Task.Run()Task.Factory.StartNew()new Task() 是 .NET 中用于创建和启动任务的三种方式。它们在用法、灵活性和底层实现上有一些区别。下面我们将逐一讲解它们的异同点。

1. Task.Run()

Task.Run(() => SomeMethod());
特点:
  • 简化用法Task.Run() 是用于简化任务启动的 API,它同时创建并启动任务,不需要手动调用 Start()
  • 线程池Task.Run() 默认在线程池中执行任务,不会创建独立的线程。Task.Run() 只能使用线程池,无法直接将任务分配给独立的线程
  • 异步执行:通常用于简化异步操作,不带复杂的配置选项。
适用场景:
  • 适用于轻量级任务,需要快速将任务推送到线程池进行并发执行,推荐在处理非耗时的计算或 I/O 操作时使用。
  • 简单的并发任务处理,比如后台加载数据、发送网络请求等。
使用方式:
Task task = Task.Run(() => SomeWork());

2. Task.Factory.StartNew()

Task.Factory.StartNew(() => SomeMethod(), TaskCreationOptions.LongRunning);
特点:
  • 灵活性Task.Factory.StartNew() 提供了比 Task.Run() 更高的灵活性,你可以设置更多的配置选项,如 TaskCreationOptionsTaskScheduler 等。
  • 手动配置:可以设置 TaskCreationOptions 来控制任务的行为(如 LongRunningPreferFairness 等),这是 Task.Run() 所不具备的。
  • 线程池与独立线程:默认情况下,它会将任务推送到线程池中,除非你指定了 TaskCreationOptions.LongRunning,此时会创建一个独立的线程。
适用场景:
  • 适合那些需要自定义任务行为的场景,如长时间运行的任务、需要控制任务执行策略的情况。
  • 当你需要手动指定更多任务属性时(如使用自定义的 TaskScheduler 或手动设置 CancellationToken)。
使用方式:
Task task = Task.Factory.StartNew(() => SomeWork(), TaskCreationOptions.LongRunning);
Task.Run() 的区别:
  • 复杂度Task.Factory.StartNew() 提供了更复杂的配置选项,而 Task.Run() 更加简洁。
  • 独立线程Task.Factory.StartNew() 可以通过 TaskCreationOptions.LongRunning 创建独立的线程,而 Task.Run() 则只能在线程池中执行任务。

3. new Task()

Task task = new Task(() => SomeMethod());
task.Start();
特点:
  • 手动创建new Task() 创建一个未启动的任务,需要手动调用 Start() 才能启动任务。
  • 延迟启动:适合那些需要延迟启动的任务,或者你想要更灵活地控制任务的启动时间。
  • 灵活性:你可以在 Start() 之前配置任务的行为,但如果没有启动任务,它将不会被执行。
适用场景:
  • 当你需要手动控制任务的启动时机时使用。
  • 适用于那些需要在创建之后再根据某些条件决定是否启动任务的场景。
使用方式:
Task task = new Task(() => SomeWork());
// 一些额外的逻辑处理
task.Start(); // 手动启动任务
Task.Run()Task.Factory.StartNew() 的区别:
  • 启动控制new Task() 允许你在创建任务后手动启动,而 Task.Run()Task.Factory.StartNew() 创建任务后会立即启动。
  • 线程池行为:与 Task.Run()Task.Factory.StartNew() 一样,默认情况下,new Task() 也是在线程池中执行任务,除非指定 TaskCreationOptions.LongRunning

总结对比

特性Task.Run()Task.Factory.StartNew()new Task()
创建任务并立即启动否(需要手动调用 Start()
是否使用线程池是(只能在线程池中执行)是(默认使用线程池,除非指定 LongRunning是(默认使用线程池,除非指定 LongRunning
简洁性非常简洁较为复杂复杂
灵活性高(可配置更多选项,如 TaskCreationOptions中(需要手动控制任务的启动)
独立线程可以通过 TaskCreationOptions.LongRunning 实现可以通过 TaskCreationOptions.LongRunning 实现
适用场景简单异步任务需要自定义任务行为或长时间运行的任务需要手动控制任务的启动时间

使用建议

  • Task.Run():适合大部分常见的异步任务场景,尤其是轻量级、短期任务,推荐优先使用。但请注意,它只能用于线程池,无法直接用于独立线程。
  • Task.Factory.StartNew():适合那些需要手动控制任务的配置、行为和调度策略的场景,适用于需要自定义 TaskScheduler 或者需要 LongRunning 等复杂选项的情况。
  • new Task():适合那些需要延迟启动任务的场景,允许你在创建之后根据业务逻辑启动任务,但除非需要特别灵活的控制,否则不推荐这种方式,因为它比 Task.Run()Task.Factory.StartNew() 更容易出错(如忘记启动任务)。

根据你的场景和需求,选择合适的 API 会让你的代码更简洁、高效。


那么问题来了,如何在线程中安全中止我们的异步线程?
在 .NET 中,线程的中止通常是通过使用 CancellationToken 来实现的,而不是强制终止线程。这是因为强制终止线程可能会导致资源泄漏、数据损坏等问题。下面是一些常用的中止线程的方法:

1. 使用 CancellationToken

步骤

  • 创建一个 CancellationTokenSource 对象。
  • 获取一个 CancellationToken
  • 在需要中止任务的地方检查该 CancellationToken 的状态,并相应地终止任务。

示例

CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;

Task task = Task.Run(() => {
    for (int i = 0; i < 10; i++)
    {
        // 检查是否请求取消
        if (token.IsCancellationRequested)
        {
            Console.WriteLine("任务已被取消。");
            return; // 退出任务
        }
        
        // 模拟工作
        Thread.Sleep(1000);
        Console.WriteLine($"正在处理: {i}");
    }
}, token);

// 请求取消
cancellationTokenSource.Cancel();
try
{
    task.Wait(); // 等待任务完成
}
catch (AggregateException ex)
{
    // 处理异常
    Console.WriteLine(ex.InnerExceptions[0].Message);
}

2. 使用 Thread.Abort()(不推荐)

尽管可以使用 Thread.Abort() 来强制终止线程,但这并不推荐使用,因为这可能会导致不安全的状态。.NET 4.0 及以后的版本中,Thread.Abort() 已被标记为过时。

示例(不推荐)

Thread thread = new Thread(() => {
    while (true)
    {
        // 模拟工作
        Thread.Sleep(1000);
        Console.WriteLine("线程运行中...");
    }
});

// 启动线程
thread.Start();

// 等待一段时间后强制中止
Thread.Sleep(5000);
thread.Abort(); // 强制中止线程(不推荐使用)

3. 优雅地停止线程

通过设置标志位的方式,可以让线程定期检查这个标志位,以决定是否退出。例如:

示例

bool isRunning = true;

Thread thread = new Thread(() => {
    while (isRunning)
    {
        // 模拟工作
        Thread.Sleep(1000);
        Console.WriteLine("线程运行中...");
    }
});

// 启动线程
thread.Start();

// 等待一段时间后设置标志位为 false
Thread.Sleep(5000);
isRunning = false; // 优雅地停止线程
thread.Join(); // 等待线程结束

总结

  • 使用 CancellationToken 是推荐的方式,可以安全地请求任务停止并处理取消请求。
  • 强制中止线程(如 Thread.Abort())不推荐,可能会导致不稳定和错误。
  • 通过标志位控制线程的运行 是一种简单有效的方法,可以优雅地停止线程。

在设计多线程程序时,最好考虑如何安全地中止任务,以避免潜在的资源问题和数据一致性问题。

开发板推荐:天空星STM32F407VET6开发板

超高性价比 STM32主控 | 超高主频 | 一板兼容百芯 | 比赛神器 | 沉金彩色丝印

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

码上有潜

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

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

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

打赏作者

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

抵扣说明:

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

余额充值