在调用.WaitAll()之后,将匿名任务添加到List<Task>中不会执行它。C#

5
我通过以下方式将任务添加到任务列表中:

我添加一个任务到任务列表中,方法如下:

taskList.Add(new Task(async () =>
    {
        // work here
        await MethodInsideThatNeedsAwaiting(); // if that has anything to do with it
        // more work 
    }));

调用 Task.WaitAll(tasklist); 之后会"卡住"。程序继续运行,但是从列表中任何任务都没有听到任何消息,也不会触发任何断点,就好像它在自己的内部异步循环中被卡住了。
我添加任务到列表的方式有问题吗?这里出了什么问题?
我还尝试了以下方法,以防万一 async 关键字是问题所在,但它仍然不能工作
taskList.Add(new Task(() =>
    {
        // work here
        MethodInsideThatNeedsAwaiting().Wait();
        // more work 
    }));

这个却 按预期工作
private async Task GetStuff()
{
    // same work here
    await MethodInsideThatNeedsAwaiting();
    // more work
}

然后使用 taskList.Add(GetStuff()); 将其添加。调用 Task.WaitAll(tasklist); 对此没有问题。


2
我很困惑你为什么要调用 new Task。你有一个异步 lambda,它返回一个任务。如果你想让它运行,那就调用它。你现在的做法就像是制作了一个待办事项清单,上面写着“制作一个待办事项清单”,但没有写“完成清单上的任务”!只需要完成清单上的任务,不需要再制作一个第二份清单来告诉你完成第一份清单上的任务;你只需要去完成它们。你已经通过实验发现了这一点,所以我不清楚你在这里提出了什么问题。 - Eric Lippert
你有一个异步lambda函数。这个异步lambda函数返回一个任务。因为显然,这部分在我的头脑中并不像应该的那样清晰。我在另一个答案中看到了以下任务的构造方式 - `new Task(() => { });`,并简单地将其封装在taskList.Add()调用中。 - Derptastic
1个回答

14

你现在所做的一切都是错误的。 在继续编写异步代码之前,请停止当前操作并学习如何使用异步工作流程。 因为你正在设置永远无法完成的任务,因为它们从未开始,并且正在等待自身完成的任务。异步工作流程有点棘手。

首先:你几乎永远不需要使用new Task。这只意味着“创建一个代表此工作的任务”而已。 这并不意味着执行该工作new Task 只是创建了一个待办事项列表;它并未执行列表中的任何工作!

其次,你几乎永远不需要使用 Task.Run。这意味着创建一个代表工作的任务并分配一个工作线程来运行它。 除非你要执行的工作是同步和CPU密集型的工作,否则你不需要分配一个工作线程,而你的工作不属于这种类型。

第三,你几乎永远不需要对已经是任务的东西使用这两个命令。你手头上已经有一个异步lambda。 当调用时,它会返回一个任务,因此,如果要为正在运行的工作流程获取任务,请调用 lambda 函数!

第四,你几乎永远不需要使用 WaitAll。这样做将异步工作流程转换回同步工作流程,破坏了异步性的全部意义

第五,由于相同的原因,你几乎永远不需要在任务上调用 Wait。但是情况会更糟糕! 我要让你完成以下待办事项:首先,将面包放在烤面包机中并开始烤。其次,同步等待三明治完成;在此步骤完成之前,请不要继续进行下一步。第三步,吃三明治。第四步,当烤面包机弹出时,将烤面包从烤面包机取出,并在烤面包上放些火腿制作三明治。 如果你尝试执行此工作流程,你将永远等待。异步工作流程会死锁,因为当你在其中插入同步等待时,你常常处于这样一种情况:你正在等待未来自己将要完成的工作。

(关于最后一点的小提示:如果你处于这种情况下,请勿将工作流程的第二步更改为“雇佣一个工人来完成我的三明治,并同步等待该工人完成”。你经常会看到这种奇怪的、浪费资源的解决方案,用于修复不正确的工作流程。当您插入异步等待(await)在工作流程不能继续进行直到任务完成的地方时,你会发现可以在单个线程上完成工作流程。)

你目前所做的一切都是完全错误的异步编程方式,如果你继续像这样做,你将无法成功。

好了,现在你知道了如何不做它,那么怎么做呢?

  • 如果你有一个返回任务的方法或lambda,请调用它以获取任务
  • 如果你需要
    private async Task GetStuffAsync()
    {
        // same work here
        await MethodInsideThatNeedsAwaitingAsync();
        // more work
    }
    private async Task DoItAsync()
    {
        // do work
        await GetStuffAsync();
        // do more work
    }
    
    如果您有多个任务,想要等待它们全部完成,但这些任务并不需要相互等待,该怎么办?
    private async Task DoItAsync()
    {
        // do work
        Task t1 = GetStuffAsync();
        // do work
        Task t2 = GetOtherStuffAsync();
        // do more work
        // We cannot continue until both are done
        await t1;
        await t2;
        // do even more work
    }
    
    如果你有一个未知数量的任务,会怎样呢?
    private async Task DoItAsync()
    {
        // do work
        var tasks = new List<Task>();
        while (whatever)
          tasks.Add(GetStuffAsync()); 
        // do work
        // we cannot continue until all tasks are done
        await Task.WhenAll(tasks);
        // do more work
    }
    

严厉但公正。=)作为一个快速的防御性注释,我实际上在我的代码中写成了await WhenAll(...),但在编写问题时无法注意到这种差异,这可能更糟糕。并且非常感谢您的快速讲解。 - Derptastic
1
@Derptastic:并非要表现得苛刻;而是要极其明确地指出这种做法是错误的,而你正在这样做。我希望所有读者都能清楚地了解到异步工作流程就像所有工作流程一样,由具有控制流关系的部分组成,而你需要知道这些关系才能在其中取得成功。如果你看到一个新手程序员混淆了“if”和“while”,你必须再次非常清楚地指出,当然,这些是密切相关的控制流,但它们并不相同。 - Eric Lippert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接