简体   繁体   English

如何启动异步任务对象

[英]How to start async Task objects

I want to start a collection of Task objects at the same time and wait until all are complete.我想同时启动Task对象的集合并等到所有对象都完成。 The following code shows my desired behaviour.以下代码显示了我想要的行为。

public class Program
{
    class TaskTest
    {
        private Task createPauseTask(int ms)
        {
            // works well
            return Task.Run(async () =>
            // subsitution: return new Task(async () =>
            {
                Console.WriteLine($"Start {ms} ms pause");
                await Task.Delay(ms);
                Console.WriteLine($"{ms} ms are elapsed");
            });
        }

        public async Task Start()
        {
            var taskList= new List<Task>(new[]
            {
                createPauseTask(1000),
                createPauseTask(2000)
            });
            // taskList.ForEach(x => x.Start());
            await Task.WhenAll(taskList);
            Console.WriteLine("------------");
        }
    }

    public static void Main()
    {
        var t = new TaskTest();
        Task.Run(() => t.Start());
        Console.ReadKey();
    }
}

The output is:输出是:

Start 1000 ms pause
Start 2000 ms pause
1000 ms are elapsed
2000 ms are elapsed
------------

Now I wonder if it is possible to prepare my tasks and start them separately.现在我想知道是否可以准备我的任务并分别启动它们。 In order to do that I change the first line in createPauseTask(int ms) method from return Task.Run(async () => to return new Task(async () =>为了做到这一点,我将createPauseTask(int ms)方法中的第一行从return Task.Run(async () =>更改为return new Task(async () =>

and in the Start() method I included a taskList.ForEach(x => x.Start());Start()方法中,我包含了一个taskList.ForEach(x => x.Start()); before the WhenAll .WhenAll之前。 But then terrible things happen.但随后可怕的事情发生了。 The WhenAll call completes immediately and the output is: WhenAll调用立即完成,输出为:

Start 2000 ms pause
Start 1000 ms pause    
------------    
1000 ms are elapsed
2000 ms are elapsed

Can someone show me what my mistake is and how to fix it?有人可以告诉我我的错误是什么以及如何解决它吗?

The issue is that the Task constructor you're using accepts an Action delegate.问题是您使用的Task构造函数接受一个Action委托。 When you say当你说

var task = new Task(async () => { /* Do things */ });

you are not creating a task that "does things";您不是在创建“做事”的任务; you are instead created a task that executes an action that returns a task , and that (inner) task is what "does things".相反,您创建了一个任务,该任务执行返回一个任务的动作,而该(内部)任务就是“做事”。 Creating this (inner) task is very quick, as the delegate returns the Task at the first await , and completes almost immediately.创建这个(内部)任务非常快,因为委托在第一次await时返回Task ,并且几乎立即完成。 As the delegate is an Action , the resultant Task is effectively discarded, and can now no-longer be used to be awaited.由于委托是一个Action ,因此生成的Task被有效地丢弃,现在不能再用于等待。

When you call await Task.WhenAll(tasks) on your outer tasks, you are only waiting for the inner tasks to be created (almost immediate).当您在外部任务上调用await Task.WhenAll(tasks)时,您只是在等待内部任务被创建(几乎立即)。 The inner tasks continue running afterwards.之后内部任务继续运行。

There are constructor overrides which allow you to do what you want, but your syntax will be a little more cumbersome, along the lines of Paulo's answer:有构造函数覆盖可以让你做你想做的事,但是你的语法会有点麻烦,就像保罗的回答一样:

public static Task<Task> CreatePauseTask(int ms)
{
    return new Task<Task>(async () =>
        {
            Console.WriteLine($"Start {ms} ms pause");
            await Task.Delay(ms);
            Console.WriteLine($"{ms} ms are elapsed");
        });
}

You now have a task that does the same, but this time returns the inner Task .您现在有一个执行相同操作的任务,但这次返回内部Task To await the inner tasks, you can do this:要等待内部任务,您可以这样做:

await Task.WhenAll(await Task.WhenAll(taskList));

The inner await returns the list of inner tasks, and the outer await await's them.内部等待返回内部任务列表,外部等待等待它们。


As also mentioned, though - creating unstarted tasks using constructors is an area you really should only be in if you have a highly-specific requirement where the more-standard practice of using Task.Run() or simply calling task-returning methods does not meet the requirement (which it will do 99.99% of the time).不过,正如还提到的那样 - 使用构造函数创建未启动的任务是一个领域,只有当您有一个高度特定的要求时,使用Task.Run()或简单地调用任务返回方法的更标准做法不适合满足要求(它会在 99.99% 的时间内做到这一点)。

Most of those Task constructors were created before async-await even existed, and are probably only still there for legacy reasons.这些Task构造函数中的大多数是在 async-await 甚至存在之前创建的,并且可能只是由于遗留原因而仍然存在。


Edit : what might also help is to see the signatures of the 2 delegates, which the C# lambda notation allows us to - usually conveniently - overlook.编辑:查看 2 个委托的签名也可能有所帮助,C# lambda 表示法允许我们(通常很方便地)忽略这些签名。

For new Task(async () => { /* Do things */ }) we have对于new Task(async () => { /* Do things */ })我们有

async void Action() { }

(the void being the main issue here). void是这里的主要问题)。

For new Task<Task>(async () => { /* Do things */ }) we have对于new Task<Task>(async () => { /* Do things */ })我们有

async Task Function() { }

Both lambdas are syntactically identical, yet semantically different.两个 lambda 在语法上是相同的,但在语义上是不同的。

I don't know why you're so found of Task.Run .我不知道你为什么这么Task.Run Every use of it in your code is just not needed.只是不需要在您的代码中每次使用它。

I also wonder where you read that using the Task constructor is a recommended practice.我还想知道您在哪里读到使用Task构造函数是推荐的做法。

Anyway, without using the Task constructor, would be something like this:无论如何,不​​使用Task构造函数,将是这样的:

class TaskTest
{
    private async Task CreatePauseTask(int ms)
    {
        Console.WriteLine($"Start {ms} ms pause");
        await Task.Delay(ms);
        Console.WriteLine($"{ms} ms are elapsed");
    }

    public async Task Start()
    {
        var taskList = new List<Task>(new[]
        {
                CreatePauseTask(1000),
                CreatePauseTask(2000)
            });
        await Task.WhenAll(taskList);
        Console.WriteLine("------------");
    }
}


static void Main()
{
    var t = new TaskTest();
    t.Start();
    Console.ReadKey();
}

And that outputs:那输出:

Start 1000 ms pause
Start 2000 ms pause
1000 ms are elapsed
2000 ms are elapsed
------------

And using the Task constructor would be:使用Task构造函数将是:

class TaskTest
{
    private Task CreatePauseTask(int ms)
    {
        return new Task<Task>(async () =>
        {
            Console.WriteLine($"Start {ms} ms pause");
            await Task.Delay(ms);
            Console.WriteLine($"{ms} ms are elapsed");
        }, TaskCreationOptions.DenyChildAttach);
    }

    public async Task Start()
    {
        var taskList = new List<Task>(new[]
        {
                CreatePauseTask(1000),
                CreatePauseTask(2000)
            });
        taskList.ForEach(t => t.Start(TaskScheduler.Default));
        await Task.WhenAll(taskList);
        Console.WriteLine("------------");
    }
}


static void Main()
{
    var t = new TaskTest();
    t.Start();
    Console.ReadKey();
}

And that outputs:那输出:

Start 1000 ms pause
Start 2000 ms pause
------------
1000 ms are elapsed
2000 ms are elapsed

The problem here is that Task.Run understands Task returning functions and the Task constructor doesn't.这里的问题是Task.Run理解Task返回函数,而Task构造函数不理解。 So, using the Task constructor will invoke the function which will return at the first blocking await ( await Task.Delay(ms); ).因此,使用Task构造函数将调用将在第一个阻塞等待时返回的函数( await Task.Delay(ms); )。

This is the expected behavior.这是预期的行为。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM