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

时间:2016-03-06 19:37:53

标签: c# asynchronous async-await task-parallel-library

我试图了解TPL。不幸的是,我无法通过返回类型克服任务。根据我的阅读,我认为将任务分配给变量会异步启动它。当您需要返回值时,只需等待它,这可确保当前线程等待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

我做错了什么?

4 个答案:

答案 0 :(得分:10)

  

我读过的内容,我认为将任务分配给变量会异步启动它。

这是完全错误的。

在这里,我有一个任务:做一个三明治。我把它写在一张纸上,然后把它放在标有&#34;任务&#34;的抽屉里。我开始做三明治吗?否。

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

变量和任务完全没有任何关系。无论是什么让你相信他们彼此有关,现在就不要相信了。

  

当您需要返回值时,您只需等待它

  

确保当前线程等待任务完成。

不,如果通过&#34;等待&#34;你的意思是&#34;阻止&#34;。

  

我的理解:第一个陈述应该开始任务

当然,假设TaskOfTMethodAsync启动任务并返回已启动的任务,这似乎是合理的。

  

之后立即附加文本框

  

然后线程被阻塞,直到integerTask完成。

绝对没有。等待的重点是不阻止线程!如果你给我一个任务 - 做一个三明治 - 然后你等着我完成那项任务,你不要小睡,而我却让你成为一个三明治!你一直在完成工作!另外,雇我让你做三明治是什么意思?你想雇佣工人为你做的工作,而不是在你等待他们完成的时候。

  

我做错了什么?

两件事。首先,你将void方法变成了一个任务,这是非常错误的。异步方法不应该是void,它应该返回一个任务!任务表示异步工作。其次,你只需要完成已完成的任务!

让我们一行一行地说出它的作用。

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。这种情况持续了很长时间。然后它返回一个值。然后我们生成一个表示该值的已完成任务。然后我们等待这项任务。等待完成的任务产生价值。所以这与

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

这里根本就没有异步。这是一种非常非常复杂的写入方式&#34;返回GetIntSync()&#34; - 运行该方法,然后构建不是一个,而是构建已完成任务的两个已完成的任务!

再次:此方法返回一个任务。此方法的所有子任务已经完成,因此我们只返回一个带有值的已完成任务。

这项任务会怎样?记得我们在:

var task = GetIntAsync();

这样任务就完成了。 (这是&#34; task2&#34;。)

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);

不,任务已经完成。这样就可以在不等待的情况下提取值并将其打印出来。没有什么可以等待的;任务已经完成!

现在从这个方法返回什么?没有。它是一种无效方法。所以我们现在回来了,完成了该方法的所有工作。

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

答案 1 :(得分:1)

有几个问题。

  1. 你在这里没有开始任何异步。 async-await不会自动使事物异步或创建任何线程。阅读stephen's introductory article on the subject

    要卸载某些作品,您需要使用Task.RunTask.Factory.StartNew

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

  3. 所以你的代码变成了:

    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已经向您提出了启动任务。

    鉴于两次更改,您的代码应按预期工作

答案 2 :(得分:1)

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

  1. 完全计算GetIntSync
  2. 返回int
  3. 将其传递给FromResult
  4. 立即返回完成的Task<int>
  5. 在已完成的任务上调用await,以便立即继续,
  6. 并返回Task<int>
  7. GetIntAsync根本不是异步的!第二个大问题是你正在使用Task混合测试你的异步/等待技能(也使用Task.Run)和并行执行。异步执行意味着线程的执行顺序不一定与代码顺序同步。您可以尽快在异步方法中执行代码。异步方法可以等待,将控制权交还给其调用者,直到它表示它已准备好再次工作。这样做的目的是使线程和CPU保持忙碌。为了说明,让我们使用我最喜欢的机制:metaphores!

    • 让逻辑处理器成为桌面工作者。每次切片时,你都会走到一个带有文件夹(方法)的桌子(线程)上,一个接一个地处理其中的表格(线条)。如果一行是一种方法,你抓住该文件夹,处理里面的表格,然后回到你正在处理的文件夹。

    • 然后一张表格X需要你老板的签名。你把它交给你的老板:异步方法Task task = GetBossToSign(Form X)是从Worker.WorkDay()调用的。然后,您可以处理其他表单。当你带着签名回到X后,你就完成了X,然后继续你所处的位置,而不是浪费一秒钟。

    • 但现在你来到一个需要X完成的表格Y.幸运的是,Worker.WorkDay()被标记为async,因此您可以致电await task来表示:处理此文件夹下方的文件夹,直到您看到X返回给您,仍在处理此过程。只有标有async的文件夹才能安全地被await收起。

    • 另一方面,如果你打电话给task.Wait(),你就会将此桌面标记为禁止(阻止线程),直到任务完成。如果您在处理底部文件夹时使用await,也会发生这种情况。工作人员(处理者)将尝试找到另一个有工作的服务台(准备好的线程),这可能与您尝试简化的实际流程无关。

    • 如果您正在呼叫Task.Run(RunItAsync)(或创建任务然后启动它),您将转到一些空桌面,并在那里保留一个文件夹RunItAsync。你打算在自己的办公桌上工作。最理想的是,工作可以快两倍。如果你是唯一的工作人员(处理器),这没有任何作用,因为你只是在两个办公桌上分散你的班次(时间片)。

    • 现在你的代码做了什么:一个线程在桌面A工作,文件夹Main在哪里。它抓取文件夹RunItAsync,将其放在空桌面B上,然后将此桌面标记为无法使用。 B台的工作人员(可能是同一个工作人员,也可能不是同一个工人)打电话给GetIntAsync,然后打GetIntSync。他自己填写表单,将完成的文件夹放在桌面C上Task.FromResult,然后检查文件夹是否在桌面C上await完成。它是!因此await无效,GetIntAsync会返回。完成的Task被分配给任务,B打印第一行,检查并看到task已经完成,并打印第二行。文件夹RunItAsync已完成,因此桌面A标记为可用,桌面B已被放弃。一名工人(可能也可能不是同一名工人)前往办公桌A并执行task.Wait()之后的任何工作。

      • 所以你的程序中没有任何地方可以同时处理两个工作程序,也没有任何执行程序与编写的代码异步执行。

    要测试您的异步/等待技能,只需使用已有的异步方法,例如: WebClientDownloadStringTaskAsync("http://stackoverflow.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://stackoverflow.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);
    

答案 3 :(得分:0)

在您的代码中

public static int GetIntSync()

Getintsync只是一个正常的无聊例程,你在写第一行之前调用它,所以它做了所有的工作,然后说,我正在做其他事情。

该示例有效,因为调用的函数是异步函数,因此使用

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

现在结果做了其他事情。

就像设置一个deligate

第一行基本上是一个deligate,所以不会对你的代码采取行动。你的命中代码在等待线上做了我。

请参阅microsofts example

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