任务类型不能执行的c#async / await能做什么?

时间:2019-08-20 21:31:52

标签: c# asynchronous async-await

我是C#异步/等待功能的新手,经过研究,我认为我已经很好地理解了这些关键字想要完成的工作。但是我想到这个问题: 有些事情使异步/等待成为可能,而使用Task类型无法完成这些事情?请考虑以下示例:

static async Task<int> jobAsync()
{
    // I could be writing here "await someTask()"
    await Task.Delay(2000);
    return 1;
}

static async void simpleAsync()
{
    int i = await jobAsync();
    Console.WriteLine("Async done. Result: " + i.ToString());
}

static void simpleTask()
{
    var t = Task.Run(() => { 
    //I could be writing here "return someTask();"
    Thread.Sleep(2000); return 1; });
    t.ContinueWith(tsk => { Console.WriteLine("Task done. Result: " + tsk.Result); });
}

现在,两个函数“ simpleTask()”和“ simpleAsync()”给出相同的结果,例如,如果调用Main方法:

static void Main(string[] args)
{
    simpleTask();
    //simpleAsync();
    Console.WriteLine("Doing other things...");
    Console.ReadLine();
}

这当然只是一个简单的示例,但是什么使async / await真正有用呢?在什么情况下? 谢谢。

3 个答案:

答案 0 :(得分:5)

  

任务类型不能执行的c#异步/等待可以做什么?

异步代码已经存在了很长时间。如果我不得不猜测的话,大概是1960年代。在.NET时间范围内,有asynchronous code patterns from the very beginning - in .NET 1.0

因此,上述问题的一个答案(有点古怪)是“什么都没有”。 ESCasync没有带来任何新功能;异步代码一直都是可能的。

  

什么使async / await真正有用?在什么情况下?

awaitasync的主要好处是代码可维护性。我有一个演讲要探讨异步设计的发展,从事件到回调再到承诺,最后是await / async。在每个步骤中,代码都更易于维护,但是直到awaitasync出现之前,它从未达到与同步代码同等的可维护性。

具体来说,您的await使用带有连续性的promise;这是下一个最可维护的模式。除非您尝试使用状态构建内容,否则它与simpleTask / async相似。尝试用诺言做到这一点,您就会明白我的意思:

await

答案 1 :(得分:2)

了解async和任务之间的区别的关键是围绕我们所谓的“消息泵”。多线程在现代.NET应用程序中的工作方式存在一个很大的循环。循环进入您的代码并执行所有操作。当控制权返回到消息泵时,其他事物可以轮流使用。

如果任何人都记得WinForms的旧时代,则UI更新存在问题。有时,当程序处于循环中时,UI会完全冻结。解决方法是将Application.DoEvents();添加到循环中。实际上,没有多少人知道这是怎么做的。这告诉消息泵暂停一下,并检查其他可能正在等待运行的代码。这包括单击鼠标时调用的代码,从而神奇地解冻了UI。 async是针对同一概念的更现代的方法。每当程序执行到达await时,它就会运行代码,但是它认为适当而不会阻塞泵

另一方面,任务总是(有一些罕见的例外)启动一个新线程来处理阻塞问题。这可行,但是async可能会提供更好(更优化)的解决方案。任务的优势在于能够并行运行多段同步代码,从而允许执行在相同功能中继续执行

这引出了一个问题:我们何时在如下所示的任务中使用async

Task.Run(async () => await MyFuncAsync());

评论中的任何想法都会受到赞赏。

答案 2 :(得分:-1)

我们可以深入探讨很多非常详细的要点,但是从您刚才给出的示例中可以得出一个简单而明显的要点,就是您的Main方法被迫等待用户输入一行以安全地结束该行。程序。

如果这样做,则可以在完成所有异步工作之后允许程序自动结束:

static async Task Main(string[] args)
{
    var task = simpleAsync();
    Console.WriteLine("Doing other things...");
    await task;
}

static async Task simpleAsync()
{
    int i = await jobAsync();
    Console.WriteLine("Async done. Result: " + i.ToString());
}

您会注意到,为了使其正常工作,除了异步/等待,我们实际上还利用了Task类。重点是:async / await 利用 Task类型。这不是一个或非命题。

详细地说,认识到您提供的simpleTask示例不是关于“使用任务类型”,而是“使用Task.Run()”,这一点很重要。 Task.Run()在完全不同的线程上执行给定的代码,当您编写诸如Web应用程序和基于GUI的应用程序之类的东西时,这会产生后果。而且,正如Kevin Gosse在下面的评论中指出的那样,您可以在返回的.Wait()上调用Task,以便阻塞主线程,直到完成后台工作为止。如果您的代码在基于GUI的应用程序中的UI线程上运行,这同样会产生严重的影响。

使用通常应使用的任务(通常避免使用Task.Run().Wait().Result)可以为您提供良好的异步行为,并且开销比分散一堆任务要少不必要的线程。

如果您愿意接受这一点,那么可以更好地比较async / await与不使用它,这将利用Task.Delay()和{{1} },从每个方法中返回ContinueWith()来保持异步,但是根本不使用Task关键字。

通常,您可以通过直接编程到async来复制使用async / await 可获得的大多数行为。您会看到的最大区别是可读性。在您的简单示例中,您可以看到异步如何使程序更简单:

Task

但是,当您处理逻辑分支和错误处理时,真正变得棘手的地方。这是static async Task simpleAsync() { int i = await jobAsync(); Console.WriteLine("Async done. Result: " + i.ToString()); } static Task simpleTask() { return jobAsync().ContinueWith(i => Console.WriteLine("Async done. Result: " + i.ToString())); } / async真正发挥作用的地方:

await

您也许可以提出模仿此方法行为的非static async Task simpleAsync() { int i; try { i = await jobAsync(); } catch (Exception e) { throw new InvalidOperationException("Failed to execute jobAsync", e); } Console.WriteLine("Async done. Result: " + i.ToString()); if(i < 0) { throw new InvalidOperationException("jobAsync returned a negative value"); } await doSomethingWithResult(i); } 代码,但这确实很丑陋,并且在IDE中进行逐步调试很困难,并且堆栈会跟踪不会很清楚地向您显示抛出异常时正在发生的事情。这就是async的一种神奇的幕后魔术,魔术般地为您服务。