为什么在没有将异步功能分配给Task变量的情况下直接等待异步功能不起作用 [英] Why await an async function directly not work without assigning it to a Task variable

查看:384
本文介绍了为什么在没有将异步功能分配给Task变量的情况下直接等待异步功能不起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我通常会执行以下操作

public static async Task dosth()
{
    List<Task> job = new List<Task>();
    for (int i = 0; i < 3; i++)
    {
        job.Add(sleep());
    }
    Task.WhenAll(job.ToArray());
}

static async Task sleep()
{
    await Task.Delay(1000);
    Console.WriteLine("Finish new");
}

工作顺利,没问题.但是,当我检查自己的代码(尝试使用其他语法完成相同的工作)时,突然发现以下两个是不同的.

It works smoothly, no problem. But when I do a review on my own code (trying using other syntax to do the same job), I suddenly figure out the following two are different.

public static async Task dosthA()
{
    //This will be working synchronously, take 3 seconds.
    await sleep();
    await sleep();
    await sleep();

    //This will be working asynchronously, take 1 second only.
    Task A = sleep();
    Task B = sleep();
    Task C = sleep();
    await A;
    await B;
    await C;
}

为什么将异步函数分配给新变量会有所不同?我原本以为他们是一样的.

Why assigning the async function to a new variable make difference? I originally think they are the same.

更新

为什么让我感到困惑,实际上是在

Why it is confusing me is, actually in Microsoft doc on Async-await, They stated the following in their code.

// Calls to TaskOfTResult_MethodAsync  
Task<int> returnedTaskTResult = TaskOfTResult_MethodAsync();  
int intResult = await returnedTaskTResult;  
// or, in a single statement  
int intResult = await TaskOfTResult_MethodAsync();  

它们实际上是不同的,为什么在单个语句中使用//或,只是因为它们在自己的示例中没有什么不同?

They are actually different, why they use //or , in a single statement, just because it makes no different in their own example?

推荐答案

这是因为当您调用 Sleep()时,即使返回一个正在运行的 Task 时,'正在分配给变量.

This is because when you are returning a running Task when you call Sleep() even when you're assigning to a variable.

混淆之处在于,如果将 Task 分配给变量( A B C),直到您致电 await A; ,但这不是真的.一旦将 sleep(); 分配给 A ,就会调用 sleep();因此 sleep()方法中的 Task 正在运行.当您调用该方法时,便开始将其分配给变量或 Task .因为在方法中您启动 Task .

The confusion is that the Task does not begin if you assign it to a variable (A, B, or C) until you call await A; but that's not true. As soon as you assign sleep(); to A, sleep() was called; therefore the Task in the sleep() method is running. Assigning it to a variable or not the Task begins when you call the method; because in the method you start the Task.

知道这一点;致电时:

await A;
await B;
await C;

A,B和C已经同时开始...等待A之后,很可能B和C也已经完成或距离完成还有毫秒的时间.

A, B, and C, have already starting simultaneously... After awaiting A it is most likely B, and C have also completed or are milliseconds from completing.

在某些情况下,您可以引用尚未启动的 Task ,但是您必须有目的地返回未运行的 Task 来执行此操作.

There are situations where you can reference a Task that hasn't started yet but you would have to purposely return a non-running Task to do that.

也要回答对您问题的修改.

To answer the edit to your question also.

Task 具有一个名为 GetAwaiter()的方法,该方法返回 TaskAwaiter .在C#中,当您编写 var task = sleep(); 时,便会将实际的 Task 分配给任务变量.编写 await sleep(); 时都一样,编译器做了一些很酷的事情,实际上它调用了 Task.GetAwaiter()方法;已订阅. Task 将运行,完成后, TaskAwaiter 将触发继续操作.这不能用简单的答案来解释,但要知道外部逻辑会有所帮助.

Tasks have a method called GetAwaiter() which returns a TaskAwaiter. In C# when you write var task = sleep(); then you're assigning the actual Task to the task variable. All the same when you write await sleep(); the compiler does some cool stuff and it actually calls the Task.GetAwaiter() method; which is subscribed to. The Task will run and when it is complete the TaskAwaiter fires the continuation action. This can't be explained in a simple answer but to know the outer logic helps.

除其他外, TaskAwaiter 实现了 ICriticalNotifyCompletion ,后者又实现了 INotifyCompletion .两者都有一个方法,分别是 OnCompleted(Action) UnsafeOnCompleted(Action)(通过命名约定可以猜出是哪个).

Among other things the TaskAwaiter implements ICriticalNotifyCompletion which in turn implements INotifyCompletion. Both have one method each, OnCompleted(Action) and UnsafeOnCompleted(Action) (you can guess which is which by naming convention).

要注意的另一件事是, Task.GetAwaiter()返回一个 TaskAwaiter ,但是 Task< TResult> .GetAwaiter()返回一个 TaskAwaiter< TResult> .两者之间没有很大的区别,但是这两个任务的 GetResult()方法存在差异.当编组回到适当的线程上下文时,这就是所谓的. TaskAwaiter.GetResult()返回void,而 TaskAwaiter< TResult> .GetResult()返回 TResult .

Another thing to note is that Task.GetAwaiter() returns a TaskAwaiter but Task<TResult>.GetAwaiter() returns a TaskAwaiter<TResult>. There's not a strong difference in the two but there is a difference in the GetResult() method of the two tasks; which is what's called while marshalling back to the proper threading context. The TaskAwaiter.GetResult() returns void and the TaskAwaiter<TResult>.GetResult() returns TResult.

我觉得如果我进一步推动这一步,我将不得不写一些页面来详细解释这一切.希望只是解释一下您的问题并稍稍拉开帷幕将为您提供足够的帮助如果您更好奇的话,可以更深入地挖掘.

I feel like if I push further into this I'll have to write pages to explain it all in detail... Hopefully just explaining your question and pulling the curtain back a little bit will shed enough light to help you both understand and dig deeper if you're more curious.

好的,所以根据下面的评论,我想进一步描述我的答案.

Ok, so based on the comment below I want to describe my answer a little bit further.

我将以简单的方式开始;让我们做一个 Task ;一个没有运行的,先看一下.

I'll start this simple; let's just make a Task; one that isn't running, and look at it first.

public Task GetTask()
{
    var task = new Task(() => { /*some work to be done*/ });
    //Now we have a reference to a non-running task.
    return task;
}

我们现在可以调用以下代码:

We can now call code like:

public async void DoWork()
{
    await GetTask();
}

…但是我们将永远等待.直到应用程序结束,因为 Task 从未启动.然而;我们可以做这样的事情:

… but we'll be waiting forever; until the application ends, because the Task was never started. However; we could do something like this:

public async void DoWork()
{
    var task = GetTask();
    task.Start();
    await task;
}

…,它将等待正在运行的 Task ,并在 Task 完成后继续.

… and it will await the running Task and continue once the Task is complete.

知道这一点后,您可以随意调用 GetTask(),并且只会引用尚未启动的 Task .

Knowing this you can make as many calls to GetTask() as you like and you'll only be referencing Tasks that have not started.

在您的代码中,恰恰相反,这很好,因为这是最常用的方式.我鼓励您确保方法名称通知用户您将如何返回 Task .如果 Task 已经在运行,则最常见的约定是方法名称以 Async 结尾.为了清楚起见,这是另一个示例,该示例使用正在运行的 Task 进行操作.

In your code it's just the opposite, which is fine, as this is the most used way. I encourage you to make sure your method names notify the user of how you're returning the Task. If the Task is already running the most common convention is the end the method name with Async. Here's another example doing it with a running Task for clarity.

public Task DoTaskAsync()
{
    var task = Task.Run(() => { /*some work to be done*/ });
    //Now we have a reference to a task that's already running.
    return task;
}

现在我们很可能会像这样调用该方法:

And now we will most likely call this method like:

public async void DoWork()
{
    await DoTaskAsync();
}

但是;请注意,如果像我们之前那样只是想引用 Task ,我们可以做到的唯一区别是,此 Task 运行在没有先验的地方.因此此代码有效.

However; note that if we simply want to reference the Task just like we did earlier, we can, the only difference is this Task is running where the one prior was not. So this code is valid.

public async void DoWork()
{
    var task = DoTaskAsync();
    await task;
}

最重要的是C#如何处理async/await关键字. async 告诉编译器该方法将成为 Task 的延续.简而言之;编译器知道要查找所有 await 调用,并将该方法的其余部分继续.

The big take away is how C# handles the async / await keywords. async tells the compiler that the method is going to become a continuation of a Task. In short; the compiler knows to look for all await calls and put the rest of the method in a continuation.

await 关键字告诉编译器在 Task 上调用 Task.GetAwaiter()方法(基本上是订阅 INotifyCompletion ICriticalNotifyCompletion )来表示该方法的继续.

The await keyword tells the compiler to call the Task.GetAwaiter() method on the Task ( and basically subscribe to the INotifyCompletion and ICriticalNotifyCompletion) to signal the continuation in the method.

我想补充一下,以防万一您不知道.如果您确实有多个要等待的任务,但是宁愿等待一个任务都好像是一个任务,则可以使用 Task.WhenAll()做,而不是:

And this I wanted to add just incase you weren't aware. If you do have more than one task that you want to await but would rather await one task as if they were all one then you can do that with Task.WhenAll() So instead of:

var taskA = DoTaskAsync();
var taskB = DoTaskAsync();
var taskC = DoTaskAsync();

await taskA;
await taskB;
await taskC;

您可以这样写得更干净:

You could write it a little cleaner like so:

var taskA = DoTaskAsync(); 
var taskB = DoTaskAsync(); 
var taskC = DoTaskAsync();

await Task.WhenAll(taskA, taskB, taskC);

还有更多的内置方法可以用来做这种事情.只是探索它.

And there are more ways of doing this sort of thing built in; just explore it.

这篇关于为什么在没有将异步功能分配给Task变量的情况下直接等待异步功能不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆