为什么 Task<TResult> 在等待结果之前先等待结果?

Why does Task<TResult> wait for result before awaiting it?

我正在尝试了解 TPL。不幸的是,我无法完成 return 类型的任务。根据我的阅读,我认为将任务分配给变量会异步启动它。当您需要 return 值时,您只需等待它,这可确保当前线程等待 Task<T> 完成。

MSDN 上的示例:

// Call and await in separate statements.
Task<int> integerTask = TaskOfT_MethodAsync();

// You can do other work that does not rely on integerTask before awaiting.
textBox1.Text += String.Format("Application can continue working while the Task<T> runs. . . . \r\n");

int result2 = await integerTask;

我的理解:第一条语句应该启动任务,紧随其后的是附加文本框。然后线程被阻塞,直到integerTask完成。

然而,当我自己尝试时,它并没有那样工作:

static void Main()
{
    var task = new Task(RunItAsync);
    task.Start();
    task.Wait();
}
static async void RunItAsync()
{
    // Should start the task, but should not block
    var task = GetIntAsync();
    Console.WriteLine("I'm writing something while the task is running...");
    // Should wait for the running task to complete and then output the result
    Console.WriteLine(await task);
}
static Random r = new Random();
static async Task<int> GetIntAsync()
{
    return await Task.FromResult(GetIntSync());
}
public static int GetIntSync()
{
    // Some long operation to hold the task running
    var count = 0;
    for (var i = 0; i < 1000000000; i++) {
        if (i % 2 == 0) count++;
    }
    return r.Next(count);
}

没有输出,几秒后一下子全部输出:

I'm writing something while the task is running...
143831542

我做错了什么?

在你的代码中你有

public static int GetIntSync()

Getintsync 是否只是一个普通的无聊例程,您在编写第一行之前调用它,所以它完成了所有工作,然后说,我在做其他事情。

该示例有效,因为调用的函数是一个异步函数,因此其设置为

Task<T> something = myAsyncFunc(params)
Now output im going to do something
T result = await myAsyncFunc..

现在结果有结果做其他事情。

这就像设立一个代表

第一行基本上是委托,所以不要执行您的代码。你的密码在等待线。

microsofts example

我的 ipad 所以不是最好的地方,但希望能看到你的错误以及如何修复它

有几个问题。

  1. 您没有在此处启动任何异步操作。 async-await 不会自动使事物异步或创建任何线程。阅读 stephen's introductory article on the subject

    要卸载一些工作,您需要使用Task.RunTask.Factory.StartNew

  2. 您正在使用async void方法;没有简单的方法可以等待它完成。您应该改用 async TaskRead more

因此您的代码变为:

static Task<int> GetIntAsync()
{
    return Task.Run(()=> GetIntSync());
}

static async Task RunItAsync()
{
    // Should start the task, but should not block
    var task = GetIntAsync();
    Console.WriteLine("I'm writing something while the task is running...");
    // Should wait for the running task to complete and then output the result
    Console.WriteLine(await task);
}

此外,您不需要使用 task.Start(); 手动启动任务; Task.Run 已经给你一个开始的任务。

考虑到两个更改,您的代码应该按预期工作

rom what I've read, I thought that assigning a task to a variable starts it asynchronously.

那是完全错误的。

这里,我有一个任务:做一个三明治。我把它写在一张纸上,然后把它放在标有 "tasks" 的抽屉里。我开始做三明治了吗?号

我开始做三明治。开始制作三明治是否导致一张描述任务的纸出现在我的抽屉里?号

变量和任务完全没有关系。不管是什么让你相信他们之间有关系,现在就停止相信吧。

When you need the return value, you just await it

是的。

which ensures that the current thread waits for the task to complete.

不,如果 "waits" 你的意思是 "blocks"。

My understanding: the first statement should start the task

当然,假设 TaskOfTMethodAsync 启动任务并且 returns 启动任务,这似乎是合理的。

immediately after that the text box is appended

是的。

Then the thread is blocked until integerTask is completed.

绝对不是。 await 的全部意义在于不阻塞线程!如果你给我一个任务——做一个三明治——然后你等着我完成那个任务,我在给你做三明治的时候你不能去小睡!你继续完成工作!否则雇我给你做三明治有什么意义?您想雇用工人为您工作而您正在做其他事情,而不是在您睡觉时等待他们完成。

What am I doing wrong?

两件事。首先,您将一个 void 方法变成了一个任务,这是非常错误的。异步方法不应该是void,应该是return一个task!任务代表异步工作。其次,您只是 return 完成了任务!

让我们逐行分析它的作用。

var task = new Task(RunItAsync);

创建一个任务来表示操作。

task.Start();

在线程池上启动任务。

然后主线程在任务完成时阻塞。

好的,这个方法运行在线程池上:

static async void RunItAsync() 
{

这个调用是做什么的?

    var task = GetIntAsync();

好吧,让我们看看:

static async Task<int> GetIntAsync()
{
   return await Task.FromResult(GetIntSync());
}

它做的第一件事是调用 GetIntSync。那 运行s 好久了。然后它 return 是一个值。然后我们生成一个代表该值的 completed 任务。然后我们等待那个任务。等待完成的任务会产生值。所以这与

完全相同
   value1 = GetIntSync()
   task1 = completed task representing value1
   value2 = value of task1
   task2 = completed task representing value2       
   return task2

这里根本没有异步的东西。这是一种非常非常复杂的写法 "return GetIntSync()" —— 你 运行 方法,然后你构建的不是一个而是两个代表任务已经完成的完成任务!

同样:此方法 return 是一项任务。此方法的所有子任务都已完成,因此我们只需 return 一个具有值的已完成任务。

那个任务会怎样?记得我们在:

var task = GetIntAsync();

这样任务就完成了。 (这是"task2"。)

Console.WriteLine("I'm writing something while the task is running...");

不,你是在任务完成后写东西。您 return 完成了一项任务!

// Should wait for the running task to complete and then output the result
Console.WriteLine(await task);

否,任务已经完成。这会在不等待的情况下提取值并打印它。没有什么可等待的;任务已经完成!

现在从这个方法中 return 得到了什么?没有什么。它是一个无效的方法。所以我们现在 return,已经完成了该方法的所有工作。

接下来会发生什么?传递给原始任务的委托已在工作线程上完成 运行ning,因此工作线程上的任务 运行ning 完成并通知主线程它可以停止等待。

我在这里看到两个大问题: 首先是你对 Task.FromResult(result) 的使用:它只不过是包装结果所以它看起来像一个任务,所以你可以在它上面使用任务的方法。当您调用 return await Task.FromResult(GetIntSync()); 时,它:

  1. 完全计算GetIntSync
  2. return一个int,
  3. 传递给FromResult,
  4. 立即return完成Task<int>,
  5. 在已完成的任务上调用 await,因此它会立即继续,
  6. 和 return 是 Task<int>

GetIntAsync 根本不是异步的!第二个大问题是你正在混合测试你的 asynch/await 技能(也使用 Task),同时使用 Task.Run 并行执行。异步执行意味着线程的执行顺序不一定与代码顺序同步。您尽快执行异步方法中的代码。异步方法可以等待,将控制权交还给它的调用者,直到它表示它已准备好再次工作。这样做的目的是让线程保持忙碌,从而 CPU。为了说明,让我们使用我最喜欢的机制:隐喻!

  • 让逻辑处理器成为办公桌工人。每次切片,你走到一张桌子(线程),上面有一个文件夹(方法),一个接一个地处理里面的表格(行)。如果一行是一种方法,您就可以获取该文件夹,处理其中的表单,然后返回到您正在处理的文件夹。

  • 然后一张表格X需要你老板的签名。你把它告诉你的老板:异步方法 Task task = GetBossToSign(Form X) 是从 Worker.WorkDay() 调用的。然后您处理其他表格。当你拿回 X 并签名时,你就完成了 X,然后继续你原来的地方,一秒钟也不会浪费。

  • 但是现在您来到了一个要求 X 已经完成的表单 Y。幸运的是,Worker.WorkDay() 被标记为 async,因此您可以调用 await task 表示:在该文件夹下的文件夹中工作,直到看到 X returned 给您,仍然致力于这个过程。只有标记为 async 的文件夹才能被 await.

  • 安全地收起来
  • 另一方面,如果您调用 task.Wait(),您将把这张桌子标记为禁区(阻塞线程),直到任务完成。如果您在已经在底部文件夹上工作时使用 await,也会发生这种情况。工作人员(处理器)将尝试找到另一张桌子上的工作(准备好的线程),这可能与您试图简化的实际流程无关。

  • 如果您正在调用 Task.Run(RunItAsync)(或创建一个任务然后启动它),您将前往某个空桌子,并在那里留下一个文件夹 RunItAsync。您将在自己的办公桌上工作。最理想的情况是,工作可以以两倍的速度完成。如果您是唯一的工作人员(处理者),这将无济于事,因为您只是要分散轮班(时间片)在两个办公桌上工作。

  • 那么现在你的代码做了什么:线程在桌面 A 上工作,文件夹 Main 就在那里。它获取文件夹 RunItAsync,将其放在空桌子 B 上,然后将这张桌子标记为不可用。办公桌 B 的一名工作人员(可能是也可能不是同一名工作人员)呼叫 GetIntAsync,然后呼叫 GetIntSync。他自己填表,完成后的文件夹放在C桌Task.FromResult,然后检查C桌的文件夹是否完成await。这是!所以 await 什么都不做,GetIntAsync returns。完成的Task分配给任务,B打印第一行,检查发现task已经完成,打印第二行。文件夹 RunItAsync 已完成,因此桌子 A 被标记为可用,桌子 B 被废弃。一个工人(可能是也可能不是同一个工人)去办公桌 A 并执行 task.Wait().

    之后的任何事情
    • 所以在你的程序中没有任何地方有两个工人同时工作,也没有任何执行与编写的代码异步执行。

要测试您的 asynch/await 技能,只需使用已经可用的异步方法,例如WebClientDownloadStringTaskAsync("http://whosebug.com")。示例:

//Calling asynch method, running on the same thread,
//doing the work as it is eligible for doing.;
Task<string> x = new WebClient().DownloadStringTaskAsync("http://whosebug.com");
Console.WriteLine("I'm writing something while the task is running...");
Console.WriteLine("Content of webpage is: {0}", await x);

此执行线程将控制传递给当前方法的调用者,直到下载完成,使用 await x

要测试您的任务并行化技能,任何同步工作都可以。

//Calling Task to delegate work to be done in parallel,
//running(hopefully) on another thread.
Task<int> longTask = Task.Run(() => {Thread.Sleep(1000); //this is hard work
                                     return 1;});
Console.WriteLine("I'm writing something while the task is running...");
Console.WriteLine("The number one is: {0}", await longTask);