为什么我的TCS不等待?

9
async关键字确实会改变CIL(即使方法内没有await),但其主要作用是允许使用await。然而,我并没有预料到会发生以下情况:
static void Main(string[] args)
{
    Task t = Go();
    t.Wait();
}

static async Task Go()
{
    Console.WriteLine(1);
    await AAA(3000);
    Console.WriteLine(2);
}


static  Task<object> AAA(int a) // <--- No `async`
{
    TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
    Task.Delay(a).ContinueWith(b => tcs.SetResult(null));
    return tcs.Task;
}

这是打印功能:
1
(wait)
2

但是如果我做出更改

static  Task<object> AAA(int a) 

to

static async  Task<object> AAA(int a) 

它会打印出:
1
2
(no wait)

问题

为什么我看不到延迟?TCS只有在三秒后才被解决。同时,任务没有解决,应该等待。


2
我认为razor118的回答是最好的问题演示 - 将static Task<object> AAA(int a)更改为static Task<int> AAA(int a)(并进行必要的代码更改),然后添加async - Alexei Levenkov
是的,我被<object>咬了,当时以为这是个愚蠢的问题。每个返回结果的异步方法 - 都会将其结果包装在一个Task中。所以这里实际上返回的是Task<Task> - Royi Namir
1
愚蠢与否由你决定,但绝对是有教育意义的(而且写得很好)。代码的两个版本(异步/非异步)看起来都很合理,让我感到困惑(我打赌根据投票结果,足够多的人也会这样认为)。 - Alexei Levenkov
@AlexeiLevenkov 谢谢 :-) - Royi Namir
3个回答

8
没有使用async关键字时,你从AAA返回TaskCompletionSource的任务,所以你需要等待它完成(这将在延迟完成后发生)。
然而,当你添加async关键字后,从方法返回的任务是同步完成的状态机任务。该任务内部包含(作为结果)TaskCompletionSource的任务,但这不是你正在等待的任务。
如果你想让该方法等待TaskCompletionSource的任务,你可以等待Task<Task>的内部任务。
await ((Task) await AAA(3000));

或者等待 TaskCompletionSource 而不是返回它:

async Task AAA(int a)
{
    TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
    Task.Delay(a).ContinueWith(b => tcs.SetResult(null));
    await tcs.Task;
}

甚至更好的是,只需等待Task.Delay本身:
async Task<object> AAA(int a)
{
    await Task.Delay(a);
    return null;
}

6
因为当您从一个返回类型为Task<object>的异步方法中返回Task时,您会得到Task<Task>,其中包含您期望被等待的任务。您应该这样做:
static async Task<object> AAA(int a)
{
  await Task.Delay(a);
  return null;
}

简而言之,在一个方法中尽量避免混合使用async-await和直接任务操作。

2

正如Serg所提到的,看一下Visual Studio中的这个例子:

        static async Task<int> AAA(int a) // <--- No `async`
        {
             TaskCompletionSource<int> tcs = new TaskCompletionSource<int>();
             Task.Delay(a).ContinueWith(b =>
             {
                  tcs.SetResult(1);
             });
             return tcs.Task;
        }

你会得到这样的错误:
由于该方法是异步的,返回表达式必须是类型为“int”,而不是“任务(Task)”,因为你的方法将同步运行。这就是为什么需要返回“int”而不是“Task”的原因。

2
关于您最后一句话,该方法将同步运行,并且需要返回一个 int 而不是 Task<int>,但是该方法同步运行并不是他必须返回 int 的原因。他需要返回 int 是因为所有的 async 方法都会将返回值包装在 Task 中。该方法将同步运行是正确的,但不是该错误的原因。 - Servy
razor118,那个答案作为评论会很棒 - 使用一些强类型而不是 Object 会使错误非常明显。或者您可以通过应用 @Servy 的评论来清理它。 - Alexei Levenkov
据我所知,async关键字负责通知编译器构建状态机。它不会将返回类型包装到Task<returnType>中。看一下这个的IL代码 private static void Test() { }和private static async void TestAsync(){} - razor118
@razor118,async 不会包装返回的 类型(所以它仍然是 Task<int>),但它会包装返回值 (这就是为什么你必须返回 int)。 - Sergei Rogovtcev

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