【问题标题】:Why does Task<TResult> wait for result before awaiting it?为什么 Task<TResult> 在等待结果之前等待结果?
【发布时间】:2016-03-06 19:37:53
【问题描述】:

我正在尝试理解 TPL。不幸的是,我无法克服返回类型的任务。根据我的阅读,我认为将任务分配给变量会异步启动它。当你需要返回值时,你就等待它,这样可以保证当前线程等待Task&lt;T&gt;完成。

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

我做错了什么?

【问题讨论】:

  • 我认为你需要退后一步,更彻底地理解使异步工作所需的所有概念。 Stephen Cleary 的系列文章很好地介绍了该主题。 blog.stephencleary.com/2012/02/async-and-await.html
  • 是的,很遗憾,我学到的大部分东西都错了。
  • 这很棘手。如果您想要对这些东西进行更技术性的介绍,尽管有些过时,我在设计它时写了一系列文章。 blogs.msdn.microsoft.com/ericlippert/tag/async。从底部开始,这些是从最近到最近的。
  • 感谢您的帮助,我会调查的

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


【解决方案1】:

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

这是完全错误的。

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

我开始做三明治。开始做三明治是否会导致我的抽屉里出现一张带有任务描述的纸?没有。

变量和任务之间绝对没有任何关系。不管是什么让你相信他们之间有某种关系,现在就不要再相信了。

当你需要返回值时,你就等待它

是的。

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

不,如果“等待”是指“阻塞”。

我的理解:第一条语句应该开始任务

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

紧接着文本框被附加

是的。

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

绝对不是。等待的重点是不要阻塞线程!如果你给我一个任务——做一个三明治——然后你等着我完成那个任务,你就别在我给你做三明治的时候打盹!你继续完成工作!不然雇我给你做三明治有什么意义?您希望雇用工人为您工作在您做其他事情时,而不是在您睡觉等待他们完成的时候。

我做错了什么?

两件事。首先,您将 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

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

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

该任务会发生什么?记得我们在:

var task = GetIntAsync();

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

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

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

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

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

【讨论】:

    【解决方案2】:

    有几个问题。

    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 已经给你一个开始的任务了。

    鉴于有两个更改,您的代码应该可以按预期工作

    【讨论】:

      【解决方案3】:

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

      1. 完全计算GetIntSync
      2. 返回一个int
      3. 将其传递给FromResult
      4. 立即返回完成Task&lt;int&gt;
      5. 在已完成的任务上调用 await,以便立即继续,
      6. 并返回Task&lt;int&gt;

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

      • 让逻辑处理器成为办公桌工作人员。每次切片,你都会走到一张桌子(线程),上面有一个文件夹(方法),一个接一个地处理里面的表格(行)。如果一行是一个方法,你抓住那个文件夹,处理里面的表格,然后回到你正在处理的文件夹。

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

      • 但是现在你会看到一个表格 Y,它要求 X 已经完成。幸运的是,Worker.WorkDay() 被标记为async,所以你可以调用await task 来表示:在这个文件夹下面工作,直到你看到 X 返回给你,仍然在这个过程中工作。只有标记为async 的文件夹才能被await 安全收起。

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

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

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

        • 因此,在您的程序中,两个工作人员不会同时工作,任何执行也不会与编写的代码异步执行。

      要测试您的 asynch/await 技能,只需使用已经可用的 asynch 方法,例如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);
      

      【讨论】:

        【解决方案4】:

        在你的代码中你有

        public static int GetIntSync()
        

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

        这个例子有效,因为调用的函数是一个异步函数,所以它的设置是

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

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

        这就像设置一个代理

        第一行基本上是一个 deligate,所以不要操作你的代码。你的代码在等待线上打我。

        microsofts example

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-05-13
          • 1970-01-01
          • 2023-03-16
          • 2017-10-30
          • 2017-04-26
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多