【问题标题】:How to declare a not started Task that will Await for another Task?如何声明将等待另一个任务的未启动任务?
【发布时间】:2020-03-11 09:31:19
【问题描述】:

我已经完成了这个单元测试,但我不明白为什么“await Task.Delay()”不等待!

   [TestMethod]
    public async Task SimpleTest()
    {
        bool isOK = false;
        Task myTask = new Task(async () =>
        {
            Console.WriteLine("Task.BeforeDelay");
            await Task.Delay(1000);
            Console.WriteLine("Task.AfterDelay");
            isOK = true;
            Console.WriteLine("Task.Ended");
        });
        Console.WriteLine("Main.BeforeStart");
        myTask.Start();
        Console.WriteLine("Main.AfterStart");
        await myTask;
        Console.WriteLine("Main.AfterAwait");
        Assert.IsTrue(isOK, "OK");
    }

这是单元测试的输出:

这怎么可能“等待”不等待,而主线程继续?

【问题讨论】:

  • 您想要达到的目标有点不清楚。你能添加你的预期输出吗?
  • 测试方法很清楚-isOK 预期为真
  • 没有理由创建未开始的任务。任务不是线程,它们使用线程。你想做什么?为什么不在第一个 Console.WriteLine 之后使用 Task.Run()
  • @Elo 你刚刚描述了线程池。您不需要需要任务池来实现作业队列。您需要一个作业队列,例如 Action 对象
  • @Elo 您尝试做的事情已经在 .NET 中可用,例如通过 ActionBlock 等 TPL 数据流类或较新的 System.Threading.Channels 类。您可以创建一个 ActionBlock 来使用一个或多个并发任务来接收和处理消息。所有块都具有可配置容量的输入缓冲区。 DOP 和容量允许您控制并发、限制请求和实施背压 - 如果排队的消息过多,生产者会等待

标签: c# .net async-await task .net-4.6


【解决方案1】:

new Task(async () =>

任务不需要Func<Task>,而是Action。它将调用您的异步方法并期望它在返回时结束。但事实并非如此。它返回一个任务。新任务不等待该任务。对于新任务,一旦方法返回,工作就完成了。

您需要使用已经存在的任务,而不是将其包装在新任务中:

[TestMethod]
public async Task SimpleTest()
{
    bool isOK = false;

    Func<Task> asyncMethod = async () =>
    {
        Console.WriteLine("Task.BeforeDelay");
        await Task.Delay(1000);
        Console.WriteLine("Task.AfterDelay");
        isOK = true;
        Console.WriteLine("Task.Ended");
    };

    Console.WriteLine("Main.BeforeStart");
    Task myTask = asyncMethod();

    Console.WriteLine("Main.AfterStart");

    await myTask;
    Console.WriteLine("Main.AfterAwait");
    Assert.IsTrue(isOK, "OK");
}

【讨论】:

  • Task.Run(async() =&gt; ... ) 也是一个选项
  • 你只是和问题的作者一样
  • 顺便说一句 myTask.Start(); 将引发 InvalidOperationException
  • @OlegI 我没看到。你能解释一下吗?
  • @nvoigt 我假设他的意思是myTask.Start() 会为他的替代方案产生一个例外,并且在使用Task.Run(...) 时应该将其删除。您的解决方案没有错误。
【解决方案2】:

问题是您使用的是非泛型 Task 类,这并不意味着产生结果。因此,当您创建传递异步委托的 Task 实例时:

Task myTask = new Task(async () =>

...委托被视为async voidasync void 不是 Task,它不能被等待,它的异常不能被处理,它是由 StackOverflow 和其他地方的受挫程序员制造的 thousands of questions 的来源。解决方法是使用通用的Task&lt;TResult&gt;类,因为你要返回一个结果,结果是另一个Task。所以你必须创建一个Task&lt;Task&gt;

Task<Task> myTask = new Task<Task>(async () =>

现在,当您 Start 外部 Task&lt;Task&gt; 时,它将几乎立即完成,因为它的工作只是创建内部 Task。然后,您还必须等待内部 Task。可以这样做:

myTask.Start();
Task myInnerTask = await myTask;
await myInnerTask;

您有两种选择。如果您不需要对内部 Task 的显式引用,那么您可以等待外部 Task&lt;Task&gt; 两次:

await await myTask;

...或者您可以使用内置的扩展方法Unwrap,将外部和内部任务合二为一:

await myTask.Unwrap();

当您使用更流行的创建热任务的Task.Run 方法时,这种展开会自动发生,因此Unwrap 现在不经常使用。

如果您决定异步委托必须返回结果,例如string,则应将myTask 变量声明为Task&lt;Task&lt;string&gt;&gt; 类型。

注意:我不赞成使用Task 构造函数来创建冷任务。作为一种做法,人们通常不赞成这种做法,原因我不太清楚,但可能是因为它使用得太少,以至于它有可能让其他不知情的用户/维护者/审阅者措手不及。

一般建议:每次提供异步委托作为方法的参数时都要小心。理想情况下,此方法应该期望一个 Func&lt;Task&gt; 参数(意味着理解异步委托),或至少一个 Func&lt;T&gt; 参数(意味着至少生成的 Task 不会被忽略)。在此方法接受Action 的不幸情况下,您的委托将被视为async void。这很少是你想要的,如果有的话。

【讨论】:

  • 注意:您无法启动解包任务。如果在 Unwrap() 之后调用 Start(),它会抛出 InvalidOperationException 并显示消息“start 可能不会在 promise 样式的任务上调用”。
【解决方案3】:
 [Fact]
        public async Task SimpleTest()
        {
            bool isOK = false;
            Task myTask = new Task(() =>
            {
                Console.WriteLine("Task.BeforeDelay");
                Task.Delay(3000).Wait();
                Console.WriteLine("Task.AfterDelay");
                isOK = true;
                Console.WriteLine("Task.Ended");
            });
            Console.WriteLine("Main.BeforeStart");
            myTask.Start();
            Console.WriteLine("Main.AfterStart");
            await myTask;
            Console.WriteLine("Main.AfterAwait");
            Assert.True(isOK, "OK");
        }

【讨论】:

  • 您意识到没有await,任务延迟实际上不会延迟该任务,对吧?您删除了功能。
  • 我刚刚测试过,@nvoigt 是对的:经过时间:0:00:00,0106554。我们在您的屏幕截图中看到:“经过时间:18 毫秒”,它应该 >= 1000 毫秒
  • 是的,你是对的,我更新了我的回复。肿瘤坏死因子。在你完成之后,我用最小的改变解决了这个问题。 :)
  • 使用 wait() 而不是从 Task 内部等待的好主意!谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-27
  • 1970-01-01
  • 2015-02-02
  • 1970-01-01
相关资源
最近更新 更多