【问题标题】:Chaining async tasks with ContinueWith in C# [duplicate]在 C# 中使用 ContinueWith 链接异步任务 [重复]
【发布时间】:2018-05-24 22:00:41
【问题描述】:

尽管asyncawait 已经出现了一段时间,而且作为一个长期的C# 开发人员,我仍然很难真正理解它们是如何工作的以及何时使用它们。所以我正在写一些测试代码!

我正在尝试异步线程调用任务A然后调用任务 B 当任务 A 完成时,然后调用任务 C

在伪代码中,这类似于:

RunAsync(TaskA())
    .Then(TaskB())
        .Then(TaskC());

我已经编写了以下示例,但它的行为与我预期的不同。相反,或者先运行 A,然后运行 ​​B,然后运行 ​​C,它运行 A,然后运行 ​​B C 并行运行

C# sn-p 如下(详情如下):

Task
  .Run(async () => await LongTaskAsync("A"))
  .ContinueWith(async (taskA) => await LongTaskAsync("B"))
  .ContinueWith(async (taskB) => await LongTaskAsync("C"));

我打印出线程 ID,并且我有:

  • 主线程 ID 1
  • 任务 A 的 ID 2
  • 任务 B 和 C 的 ID 4
  • 没有 ID 3(或我忽略的某个地方)

代码如下:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace AsyncTests
{
    class Program
    {
        private static DateTime _start;
        static void Main(string[] args)
        {
            _start = DateTime.Now;
            Log("======= Main thread starts ======");
            Log($"Main thread ID : {Thread.CurrentThread.ManagedThreadId}");

            // Start a stack of aynchronous calls
            Task
                .Run(async () => await LongTaskAsync("A"))
                .ContinueWith(async (taskA) => await LongTaskAsync("B"))
                .ContinueWith(async (taskB) => await LongTaskAsync("C"));

            Log("====== Main thread returns ======");
            Console.ReadKey();
        }  

        static async Task LongTaskAsync(string name)
        {
            Log($"Long async task {name} starts");
            Log($"{name} thread ID : {Thread.CurrentThread.ManagedThreadId}");
            for(var i = 1 ; i <= 5 ; i++)
            {
                Log($"Task {name} says {i}");
                await Task.Delay(1000);
            }            
            Log($"Long async task {name} returns");
        }

        static void Log(string text)
        {
            var elpased = (int)(DateTime.Now - _start).TotalMilliseconds;
            Console.WriteLine($"[+{elpased.ToString().PadLeft(4,'0')}] {text}");
        }
    }
}

还有控制台输出:

[+0003] ======= Main thread starts ======
[+0009] Main thread ID : 1
[+0016] ====== Main thread returns ======
[+0022] Long async task A starts
[+0024] A thread ID : 3
[+0024] Task A says 1
[+1028] Task A says 2
[+2029] Task A says 3
[+3030] Task A says 4
[+4031] Task A says 5
[+5032] Long async task A returns
[+5034] Long async task B starts
[+5034] B thread ID : 4
[+5034] Task B says 1
[+5035] Long async task C starts
[+5036] C thread ID : 4
[+5036] Task C says 1
[+6036] Task C says 2
[+6036] Task B says 2
[+7037] Task C says 3
[+7037] Task B says 3
[+8038] Task B says 4
[+8038] Task C says 4
[+9039] Task C says 5
[+9039] Task B says 5
[+10040] Long async task C returns
[+10040] Long async task B returns

【问题讨论】:

  • 为什么你要制作所有的 lambdas async,而他们什么都不做,但反对在一种可以实际使用它的方法中使用 async
  • 正如我在介绍文本中所说的,我对异步编程无论是 C# 还是任何语言都感到不舒服。因此,对这些关键字的误解使用是很有可能的。关于重复,我不认为这个问题是重复的,因为我的细节是链接几个 ContinueWith,而其他涉及一次调用。基本上,我的问题是“是否应该将ContinueWith 视为 JS/TS 中 Primises 上的 Then() 的 C# 等价物”。
  • 您需要将解决方案应用三次而不是一次,这一事实并没有使它成为一个根本不同的问题。如果您正在做需要修复三遍的事情,那么将解决方案应用三遍。

标签: c# async-await task


【解决方案1】:

如果您希望按顺序拨打电话,只需await 他们:

Task.Run(async () =>
{
    await LongTaskAsync("A");
    await LongTaskAsync("B");
    await LongTaskAsync("C");
});

样本输出:

[+0000] ======= Main thread starts ======
[+0001] Main thread ID : 1
[+0015] ====== Main thread returns ======
[+0020] Long async task A starts
[+0020] A thread ID : 3
[+0020] Task A says 1
[+1021] Task A says 2
[+2021] Task A says 3
[+3022] Task A says 4
[+4022] Task A says 5
[+5024] Long async task A returns
[+5024] Long async task B starts
[+5024] B thread ID : 4
[+5024] Task B says 1
[+6025] Task B says 2
[+7026] Task B says 3
[+8026] Task B says 4
[+9027] Task B says 5
[+10028] Long async task B returns
[+10028] Long async task C starts
[+10028] C thread ID : 3
[+10028] Task C says 1
[+11029] Task C says 2
[+12029] Task C says 3
[+13030] Task C says 4
[+14032] Task C says 5
[+15032] Long async task C returns

但是,如果任何任务失败(由于await),上述代码将会失败。如果您希望这些始终运行,您将需要一种更丑陋的方法:

Task.Run(() =>
{
    LongTaskAsync("A").ContinueWith((taskA) =>
    {
        LongTaskAsync("B").ContinueWith((taskB) => LongTaskAsync("C"));
    });
});

此代码与您的实际代码之间的区别在于您为后续工作分配的任务。

【讨论】:

  • 这正是我所期望的,而且比我的版本更具可读性。
  • Task 之一抛出异常时会发生什么,其他的将不会执行,ContinueWith 不是这种情况。此外,将 await 包装在 Task.Run 内几乎没有实际用途
  • @MrinalKamboj OP 没有提到他们想在任务失败时处理案例。是的,它确实具有在线程池线程中运行任务的实际用途。
  • 最好让包括Main在内的整个链异步,而不是包裹在Task.Run中并使用一个池线程执行异步操作
  • @kall2sollies 之前没有意识到您的问题被标记为重复。现在无论如何,当前的答案包含重要的区别,即 ContinueWith 与一组等待调用的不同之处。您当然可以针对特定语言版本的编译器。请考虑将您的程序入口点设为异步,这对于 ASP.Net mvc / api 控制器、控制台 Main 是可能的,这是启动非阻塞异步调用的理想方式。您甚至可以计划使用Task.WhenAllWhenAny 一起执行多个 Async 调用并等待代表任务
猜你喜欢
  • 2013-06-28
  • 1970-01-01
  • 1970-01-01
  • 2018-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-06
  • 1970-01-01
相关资源
最近更新 更多