异步和等待结束在哪里?混乱

时间:2015-10-11 17:20:06

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

我有一个没有任何目的的程序,但是可以帮助我理解异步和等待是如何工作的。它是一个控制台应用程序,它解析XML并等待返回名称,无论是姓氏还是名字。这是代码:

static void Main(string[] args)
{
     Task<string> name = GetFirstParsedName();
     name.Wait();
     if (name.IsCompleted)
     {
         Console.WriteLine(name.Result);
     }

     Console.ReadLine();
 }

static async Task<string> GetFirstParsedName()
{
  string xmlSnippet = @"<person>
<FirstName>Chamir</FirstName>
<Surname>Bodasing</Surname>
<Gender>Male</Gender>
<Nationality>South African</Nationality></person>";

  XmlDocument xmlDoc = new XmlDocument();
  xmlDoc.LoadXml(xmlSnippet);

  XmlParser xmlParser = new XmlParser();
  Task<string> t_GetFirstName = xmlParser.GetFirstName(xmlDoc);
  Task<string> t_GetSurname = xmlParser.GetSurname(xmlDoc);  

  Task<string> t_firstReturnedName = await Task.WhenAny(new Task<string>[] { t_GetFirstName, t_GetSurname });

  string firstReturnedName = await t_firstReturnedName;
  return firstReturnedName;    
}

static async Task<string> GetFirstName(XmlDocument personXml)
{
  string firstName = personXml.SelectSingleNode("//FirstName").InnerText;
  await Task.Delay(5000);
  return firstName;
}

static async Task<string> GetSurname(XmlDocument personXml)
{
  string surname = personXml.SelectSingleNode("//Surname").InnerText;
  await Task.Delay(1);
  return surname;
}

当您不必将值返回到main方法时,似乎只使用异步方法才有意义。除非它意味着设置一个可以访问的全局类属性。如果没有,为了等待该方法,所有方法都需要是异步的,这反过来意味着返回类型必须是Task<T>。似乎它永远不会结束,除非我明确地必须编写以下代码(如上面的main方法):

Task<string> name = GetFirstParsedName();
name.Wait();
if (name.IsCompleted)
{
    Console.WriteLine(name.Result);
 }

我的理解是否正确?我必须使用result属性来获取这里的值,并且从阅读中了解到这一点,似乎这不是最好的做法。

3 个答案:

答案 0 :(得分:8)

  

当您不必将值返回到main方法时,似乎只使用异步方法才有意义。除非它意味着设置一个可以访问的全局类属性。

您的async方法可以返回Task<T>以向其调用者返回值。如果它们依赖于副作用(即设置属性/全局变量),则异步方法不能很好地工作;如果您的代码更纯粹(即,获取参数并返回结果),它们的工作效果会更好。

  

如果没有,为了等待该方法,所有方法都需要是异步的,这反过来意味着返回类型必须是&#34;任务&#34; 。好像它永远不会结束

这就是为什么async的一个核心原则是&#34;一直异步&#34;。在大多数应用程序中,这正是您应该所做的。最终,&#34;异步链&#34;通常以async void事件处理程序(对于UI应用程序)或async Task<T>入口点(对于ASP.NET应用程序)结束。控制台应用程序很不寻常,因为它们需要在Wait()方法中明确Result / Main或等效。

毕竟,async的重点是释放调用线程。如果调用堆栈的下一个方法阻塞相同的线程,直到async代码完成,那么,这是一大堆工作,没有任何好处......

答案 1 :(得分:5)

  

当您不必将值返回到main方法时,似乎只使用异步方法才有意义。

为什么这么说?使用异步方法是有意义的 - 在这种方法中,您正在进行自然异步操作。无论该操作是否具有返回值,或者不具有返回值。

  

为了等待该方法,所有方法都需要是异步的,这反过来意味着返回类型必须是“任务”。好像它永远不会结束

这是对的。异步在您的代码中像bottom to the top of your stack一样在广告牌中传播。它通常会到达堆栈中最高的调用位置(无论是控制台Main方法还是UI事件处理程序)。这是使用async的优点,它允许您在释放调用线程的同时异步等待操作。例如,如果您有一个需要同时处理大量请求的WebAPI端点,这可能会有所帮助。如果您花费大部分时间查询数据库,则可以同时释放该调用线程以提供更多请求。

  

我的理解是否正确?我必须使用result属性来获取这里的值,并且从阅读中了解到这一点,似乎这不是最好的做法。

您必须使用Result属性,因为控制台应用程序是一种特殊情况,Main无法标记为async(除非you're using ASP.NET CoreCLR console app)。如果这是一个UI事件处理程序或一个ASP.NET操作,您可以正确await异步调用。

答案 2 :(得分:1)

好吧,async关键字让我们的编译器知道你的方法将执行一系列异常调用,这些异步调用最有可能被等待但不应该阻塞main(或UI)线程。

为了帮助您理解异步 - 等待一点,这里有一些简单的例子:

考虑这种情况,当用户点击WinForm应用程序上的“保存”按钮时,某些UI操作在不同的线程中异步启动。

代码是:

    private Task SomeUIOperation()
    {
        // ui operation
        return Task.Run(() =>
        {
            this.Invoke(new Action(() => this.BackColor = Color.Aquamarine));
            Thread.Sleep(10000);
            this.Invoke(new Action(() => this.BackColor = Color.Gray));
        });
    }

    private async void button1_Click(object sender, EventArgs e)
    {
        await SomeUIOperation();

        // some other stuff
    }

如果我们不使用async-await,那么UI线程将在10秒内无响应。

这是一个如何使用async-await的示例,当您希望仅在异步操作完成时执行某些代码并且同时您不希望主线程被阻止时,您可以使用它。

控制台应用程序不是测试和学习Async-Await的最佳项目类型