使用Task.ContinueWith的异步回调

cho*_*tte 35 c# async-await

我正试图用C#的async/await/continuewith来玩.我的目标是必须有两个并行运行的任务,尽管哪个任务按顺序执行一系列操作.为此,我计划有一个List<Task>代表并行运行的2个(或更多)任务,并且ContinueWith在每个Task 问题上使用我的问题是,在await taskList已经返回的情况下,似乎不执行继续回调.

为了总结,这里有一个例子来说明我期待发生的事情:

class Program
{
    static public async Task Test()
    {
        System.Console.WriteLine("Enter Test");
        await Task.Delay(100);
        System.Console.WriteLine("Leave Test");
    }

    static void Main(string[] args)
    {
        Test().ContinueWith(
        async (task) =>
        {
            System.Console.WriteLine("Enter callback");
            await Task.Delay(1000);
            System.Console.WriteLine("Leave callback");
        },
        TaskContinuationOptions.AttachedToParent).Wait();
        Console.WriteLine("Done with test");
    }
}
Run Code Online (Sandbox Code Playgroud)

预期的产出是

Enter Test
Leave Test
Enter callback
Leave callback
Done with test
Run Code Online (Sandbox Code Playgroud)

但是,输出是

Enter Test
Leave Test
Enter callback
Done with test
Run Code Online (Sandbox Code Playgroud)

有没有办法让ContinueWith被调用的Task 等待提供的函数在被认为完成之前完成?即..Wait将等待两个任务完成,原始任务和ContinueWith返回的任务

Nek*_*esh 39

当使用链接多个任务的ContinueWith方法,您的返回类型会Task<T>,而T是传递给委托/方法的返回类型ContinueWith.

由于异步委托的返回类型是a Task,你最终会得到a Task<Task>并最终等待异步委托返回Task第一个之后完成的委托await.

为了纠正这种行为,你需要使用返回的Task,嵌入你的Task<Task>.使用Unwrap扩展方法来提取它.

  • 近 7 年后,我可以确认这是必要的步骤,但我在 MS 文档中找不到任何内容。我使用ContinueWith作为穷人的工作队列,奇怪的是,“queue = queue.ContinueWith(_ =&gt; asyncFunc());”似乎在Windows下按预期工作,但在Linux上却不然,你肯定需要`queue.ContinueWith(_ =&gt; asyncFunc()).Unwrap();`。那里的关键词是“似乎”——即使在没有 Unwrap() 的 Windows 下,它实际上也不会按顺序运行任务,但它们之间有足够的延迟,使其能够很好地工作。 (2认同)
  • @DylanNicholson 别开玩笑了!我在 Windows 机器上做了几十次测试,我精心设计的“Continuations”_似乎_工作得很好。直到我将相同的代码部署到基于 Linux 的 Docker 容器时,我才完全意识到“Unwrap”的必要性。 (2认同)

Ste*_*ary 26

当你在做async节目,你要努力,以取代ContinueWithawait,因为这样的:

class Program
{
  static public async Task Test()
  {
    System.Console.WriteLine("Enter Test");
    await Task.Delay(100);
    System.Console.WriteLine("Leave Test");
  }

  static async Task MainAsync()
  {
    await Test();
    System.Console.WriteLine("Enter callback");
    await Task.Delay(1000);
    System.Console.WriteLine("Leave callback");
  }

  static void Main(string[] args)
  {
    MainAsync().Wait();
    Console.WriteLine("Done with test");
  }
}
Run Code Online (Sandbox Code Playgroud)

使用的代码await更清晰,更易于维护.

此外,您不应将父/子任务用于async任务(例如,AttachedToParent).他们不是为了共同努力而设计的.


nos*_*tio 5

我想补充一下我的答案,以补充已经被接受的答案.根据您要执行的操作,通常可以避免异步委托和包装任务的额外复杂性.例如,您的代码可以像这样重新计算:

class Program
{
    static async Task Test1()
    {
        System.Console.WriteLine("Enter Test");
        await Task.Delay(100);
        System.Console.WriteLine("Leave Test");
    }

    static async Task Test2()
    {
        System.Console.WriteLine("Enter callback");
        await Task.Delay(1000);
        System.Console.WriteLine("Leave callback");
    }

    static async Task Test()
    {
        await Test1(); // could do .ConfigureAwait(false) if continuation context doesn't matter
        await Test2();
    }

    static void Main(string[] args)
    {
        Test().Wait();
        Console.WriteLine("Done with test");
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 在被这种“Task&lt;Task&gt;”行为和对晦涩的“Unwrap()”的需求所困扰之后,我将返回并重构为简单的代码块而不是延续。但是,值得注意的是,a)来自优秀 .Net 开发人员的数百个示例使用没有“Unwrap”的延续,b)[文档](https://docs.microsoft.com/en-us/dotnet/standard/并行编程/通过使用延续任务链接任务)本身将“延续”提升为“相对易于​​使用,但仍然强大且灵活”。这个特殊的晦涩的角落案例有点令人沮丧...... (2认同)