我是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真正有用呢?在什么情况下? 谢谢。
答案 0 :(得分:5)
任务类型不能执行的c#异步/等待可以做什么?
异步代码已经存在了很长时间。如果我不得不猜测的话,大概是1960年代。在.NET时间范围内,有asynchronous code patterns from the very beginning - in .NET 1.0。
因此,上述问题的一个答案(有点古怪)是“什么都没有”。 ESC
和async
没有带来任何新功能;异步代码一直都是可能的。
什么使async / await真正有用?在什么情况下?
await
和async
的主要好处是代码可维护性。我有一个演讲要探讨异步设计的发展,从事件到回调再到承诺,最后是await
/ async
。在每个步骤中,代码都更易于维护,但是直到await
和async
出现之前,它从未达到与同步代码同等的可维护性。
具体来说,您的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
的一种神奇的幕后魔术,魔术般地为您服务。