【问题标题】:C#: Running an async lambda using Task.Run() / new Task() / Task.Factory.StartNew()C#:使用 Task.Run() / new Task() / Task.Factory.StartNew() 运行异步 lambda
【发布时间】:2022-11-30 10:26:15
【问题描述】:

我很清楚(感谢 Stephen Toub)通常不推荐使用 new Task(...) 构建任务,通常更愿意使用 Task.Run,但是当将异步 lambda 作为要运行的任务?我在生产代码中遇到过类似的东西,所以下面的代码是一个高度人为设计的简单示例。

当将异步 lambda 作为任务传递给 Task.Factory.StartNewnew Task(...)(后跟 Task.Start)时,即使我们等待返回的任务,lambda 也没有完成。但是,它在使用 Task.Run 时确实如此 - 这里有什么区别?

(Stephen Toub 指出 Task.Run 完全等同于

Task.Factory.StartNew(SomeTask
                      CancellationToken.None, 
                      TaskCreationOptions.DenyChildAttach, 
                      TaskScheduler.Default);

https://devblogs.microsoft.com/pfxteam/task-run-vs-task-factory-startnew/

这是我的代码:

using System;
using System.Threading.Tasks;
using System.Threading;

namespace TaskDelay
{
    class Program
    {
        static readonly long t0 = DateTime.Now.Ticks;
        private static void Main()
        {
            Console.WriteLine($"{Time} Starting t1");
            var t1 = new Task(async () => await F1(5000, "Task 1"));
            t1.Start();
            t1.Wait();

            Console.WriteLine($"{Time} Starting t2");

            var t2 = Task.Factory.StartNew(async () => await F1(5000, "Task 2"), 
                                  CancellationToken.None, 
                                  TaskCreationOptions.DenyChildAttach, 
                                  TaskScheduler.Default);
            t2.Wait();

            Console.WriteLine($"{Time} Starting t3");
            var t3 = Task.Run(async () => await F1(2000, "Task 3"));
            t3.Wait();

            Console.WriteLine($"{Time} State of {nameof(t1)} is {t1.Status}");
            Console.WriteLine($"{Time} State of {nameof(t2)} is {t2.Status}");
            Console.WriteLine($"{Time} State of {nameof(t3)} is {t3.Status}");
        }


        private static async Task F1(int delay, string taskName)
        {
            await Console.Out.WriteLineAsync($"{Time} Started to run F1 for {taskName}");
            await Task.Delay(delay);
            await Console.Out.WriteLineAsync($"{Time} Finished running F1 for {taskName}");
        }
        private static string Time => $"{(int)((DateTime.Now.Ticks - t0) / 10_000),5} ms:";
    }
}

输出是

请注意,我们永远不会看到“已完成任务 1 的 F1 运行”或“已完成任务 2 的 F1 运行”。

【问题讨论】:

  • @shingo - 不,不是。这个问题是在问为什么有几种方法可以做同样的事情。我的问题是指出至少两种方法之间的差异并询问为什么会这样。
  • Task.Factory.StartNew(SomeTask-- 我建议将SomeTask 重命名为SomeAsyncMethodSomeAsyncDelegateTask 不是方法!
  • @TheodorZoulias 但接受的答案与您在下面的答案中发布的链接相同。所以答案就在那里
  • Stephen Toub states that Task.Run is exactly equivalent to - 他说那些是完全等价的通过 Action.但是您的代码没有传递 Action(它传递的是 Func<Task>),所以它们是不是相当于你的情况。

标签: c# lambda async-await task task-parallel-library


【解决方案1】:
var t1 = new Task(async () => 
var t2 = Task.Factory.StartNew(async () => 

如果您在 Visual Studio 中检查 t1t2 的类型,您将看到它们都是 Task<Task>。这是一个Task,他的Result 是另一个Task。外层的Task表示异步操作开始,内层的Task表示异步操作完成。当外部Task完成时,意味着创建内部Task的委托刚刚完成,因此内部Task也刚刚创建。内部 Task 的创建发生在 ThreadPool 线程上(默认情况下),因此它与当前线程异步发生。

所以你有两个任务要等待:

t1.Wait();
t1.Result.Wait();

您可以使用 Unwrap 方法将这两个任务合二为一:

t1.Unwrap().Wait();

Task.Run 方法与其他两个不同,因为它配备了接受异步委托的重载,并且这些重载会自动执行 Unwrap。有关详细信息,请查看这篇文章:Stephen Toub 的Task.Run vs Task.Factory.StartNew

【讨论】:

    【解决方案2】:

    为避免 Theodor 解释的内部/外部任务问题并确保您等待正确的任务(而不是其包装任务),您可以:

    1. 直接开始工作,如果你想立即开始任务,稍后等待:
      var t1 = F1(5000, "Task 1"); // no 'await', starts the work
      
      ... other work
      
      await t1; 
      
      (但请注意,F1 中的非异步工作将在我们从 F1 返回之前执行,并且这不适用于假异步方法)
    2. 如果您不想立即启动任务,请使用异步 lambda:
      Func<Task<int>> f = async () => await WorkAsync(); // no work is started
      
      var result = await f(); // *Starts* and waits
      

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多