如何声明一个未启动的任务,将等待另一个任务?


9

我已经完成了此单元测试,但我不明白为什么“ 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");
    }

这是单元测试的输出:

单元测试输出

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


尚不清楚您要实现什么。您能加上您的预期输出吗?
OlegI '19

1
测试方法是非常明确的- ISOK有望成为真正的
入佛门爵士

没有理由创建未启动的任务。任务不是线程,它们使用线程。你想做什么?为什么Task.Run()先使用后不使用Console.WriteLine
Panagiotis Kanavos

1
@Elo您刚刚描述了线程池。您不需要任务池即可实现作业队列。您需要工作队列,例如Action <T>对象
Panagiotis Kanavos

2
@Elo您尝试执行的操作已在.NET中提供,例如通过诸如ActionBlock之类的TPL Dataflow类或更新的System.Threading.Channels类。您可以创建一个ActionBlock来使用一个或多个并发任务来接收和处理消息。所有块均具有可配置容量的输入缓冲区。DOP和容量使您可以控制并发,节流请求并实施背压-如果排队的消息过多,则生产者将等待
Panagiotis Kanavos

Answers:


8

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");
}

4
Task.Run(async() => ... )也是一种选择
Rufo爵士,

您与问题作者的行为相同
OlegI,

BTW myTask.Start();InvalidOperationException
Rufo爵士

@Oleg我没看到。你能解释一下吗?
nvoigt

@nvoigt我认为他的意思是myTask.Start()会为他的替代方案产生例外,使用时应将其删除Task.Run(...)。您的解决方案没有任何错误。
404

3

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

Task myTask = new Task(async () =>

...代表被视为async void。An async void不是a Task,它不能等待,它的异常无法处理,它是StackOverflow和其他地方沮丧的程序员提出的成千上万个问题的源头。解决方案是使用泛型Task<TResult>类,因为您想返回一个结果,而该结果是another Task。因此,您必须创建一个Task<Task>

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

现在,当您Start使用外部结构时Task<Task>,它将几乎立即完成,因为它的工作只是创建内部结构Task。然后,您还必须等待内部Task。这是可以做到的:

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

您有两种选择。如果您不需要显式引用内部,Task则可以等待Task<Task>两次外部:

await await myTask;

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

await myTask.Unwrap();

当您使用流行得多的Task.Run创建热点任务的方法时,这种解包会自动发生,因此,Unwrap如今这种方法已经很少使用。

如果您决定异步委托必须返回一个结果,例如a string,则应将myTask变量声明为typeTask<Task<string>>

注意:我不赞成使用Task构造函数来创建冷任务。由于我并不真正知道原因,因此通常不赞成这种做法,但可能是因为它很少使用,以至于有可能使其他未意识到的用户/维护者/代码审查者感到惊讶。

一般建议:每次提供异步委托作为方法的参数时,请务必小心。理想情况下,此方法应使用一个Func<Task>参数(表示理解异步委托)或至少一个Func<T>参数(表示至少Task不会忽略所生成的参数)。在不幸的情况下,此方法接受an Action,您的委托将被视为async void。如果有的话,这几乎不是您想要的。


多么详细的技术答案!谢谢。
Elo

@Elo,我的荣幸!
Theodor Zoulias

1
 [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");
        }

在此处输入图片说明


3
您意识到没有await,任务延迟实际上不会延迟该任务,对吗?您删除了功能。
nvoigt

我刚刚测试过,@ nvoigt是正确的:经过时间:0:00:00,0106554。我们在您的屏幕截图中看到:“经过时间:18毫秒”,应该> = 1000毫秒
Elo

是的,您是对的,我更新了我的回复。Tnx。在您发表评论后,我只需进行最小的更改即可解决此问题。:)
BASKA '19

1
一个好主意是使用wait()而不是从Task内部等待!谢谢 !
Elo
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.