根据我的理解,async and await做的主要事情之一是使代码易于编写和读取 - 但是使用它们等同于产生后台线程以执行长持续时间逻辑?
我正在尝试最基本的例子 . 我在内联添加了一些评论 . 你能为我澄清一下吗?
// I don't understand why this method must be marked as `async`.
private async void button1_Click(object sender, EventArgs e)
{
Task<int> access = DoSomethingAsync();
// task independent stuff here
// this line is reached after the 5 seconds sleep from
// DoSomethingAsync() method. Shouldn't it be reached immediately?
int a = 1;
// from my understanding the waiting should be done here.
int x = await access;
}
async Task<int> DoSomethingAsync()
{
// is this executed on a background thread?
System.Threading.Thread.Sleep(5000);
return 1;
}
20 回答
这个答案旨在提供一些特定于ASP.NET的信息 .
通过在MVC控制器中使用async / await,可以提高线程池利用率并实现更好的吞吐量,如下文所述,
http://www.asp.net/mvc/tutorials/mvc-4/using-asynchronous-methods-in-aspnet-mvc-4
在更高的层次上:
1)Async关键字启用等待,这就是它的全部功能 . Async关键字不会在单独的线程中运行该方法 . 开始的f async方法同步运行,直到它等待一个耗时的任务 .
2)您可以等待返回类型T的任务或任务的方法 . 您无法等待异步void方法 .
3)当主线程遇到等待耗时的任务或实际工作开始时,主线程返回当前方法的调用者 .
4)如果主线程看到等待仍在执行的任务,它不会等待它并返回当前方法的调用者 . 通过这种方式,应用程序仍然响应 .
5)等待处理任务,现在将在线程池的单独线程上执行 .
6)当等待任务完成时,它下面的所有代码将由单独的线程执行
以下是示例代码 . 执行它并检查线程ID
解释
这是一个高级别的异步/等待的快速示例 . 除此之外还有更多细节需要考虑 .
注意:
Task.Delay(1000)
模拟工作1秒钟 . 我认为's best to think of this as waiting for a response from an external resource. Since our code is waiting for a response, the system can set the running task off to the side and come back to it once it'已经完成了 . 同时,它可以在该线程上做一些其他的工作 .在下面的示例中, first block 正是这样做的 . 它立即启动所有任务(
Task.Delay
行)并将它们放在一边 . 代码将在await a
行上暂停,直到1秒延迟完成,然后再转到下一行 . 由于b
,c
,d
和e
都在几乎与a
完全相同的时间开始执行(由于缺少等待),因此在这种情况下它们应该在大致相同的时间完成 .在下面的示例中, second block 正在启动任务并等待它完成(这是
await
所做的),然后再开始后续任务 . 每次迭代需要1秒钟 .await
正在暂停程序并在继续之前等待结果 . 这是第一个和第二个块之间的主要区别 .示例
OUTPUT:
有关SynchronizationContext的额外信息
注意:这对我来说有点模糊,所以如果我基本了解它的工作方式很重要,但只要你从不使用
ConfigureAwait(false)
就可以顺利完成它,尽管你会我认为可能会失去一些优化机会 .这方面的一个方面使异步/等待概念有点棘手 . 这是事实,在这个例子中,这一切都发生在同一个线程上(或者至少看起来与其SynchronizationContext相同的线程) . 默认情况下,
await
将还原其运行的原始线程的同步上下文 . 例如,在ASP.NET中你有一个HttpContext,它在一个请求进来时与一个线程绑定 . 这个上下文包含特定于原始Http请求的东西,比如原始的Request对象,它包含语言,IP地址,头文件等 . 如果你在处理某些事情的过程中切换线程,你可能最终会尝试从不同的HttpContext中将信息从这个对象中提取出来,这可能是灾难性的 . 如果您知道不会使用任何上下文,则可以选择"not care" . 这基本上允许您的代码在单独的线程上运行,而不会带来上下文 .你是如何实现这一目标的?默认情况下,
await a;
代码实际上假设您要捕获并恢复上下文:如果您希望允许主代码在没有原始上下文的情况下继续使用新线程,则只需使用false而不是true,因此它知道它不需要恢复上下文 .
程序完成暂停后,它将继续在具有不同上下文的完全不同的线程上继续 . 这是性能改进的来源 - 它可以继续在任何可用的线程上,而不必恢复它开始的原始上下文 .
这个东西令人困惑吗?好吧,好吧!你能搞清楚吗?大概!一旦你掌握了概念,然后转向Stephen Cleary的解释,这些解释往往更倾向于对已经异步/等待有技术理解的人 .
这里的答案可作为await / async的一般指导 . 它们还包含有关如何连接await / async的一些细节 . 在使用此设计之前,我想与您分享一些您应该了解的实践经验图案 .
术语"await"是文字的,所以无论你调用它什么线程都会在继续之前等待方法的结果 . 在 foreground 线程上,这是 disaster . 前台线程承担构建应用程序的负担,包括视图,视图模型,初始动画以及其他任何与这些元素一起引导的东西 . 所以当你等待前台线程时,你 stop 应用程序 . 当没有任何事情发生时,用户等待并等待 . 这提供了负面的用户体验 .
您当然可以使用各种方法等待后台线程:
这些评论的完整代码位于https://github.com/marcusts/xamarin-forms-annoyances . 请参阅名为AwaitAsyncAntipattern.sln的解决方案 .
GitHub站点还提供了有关此主题的更详细讨论的链接 .
使用
async
和await
时,编译器会在后台生成状态机 .这是一个例子,我希望我可以解释一些正在发生的高级细节:
好的,所以这里发生了什么:
Task<int> longRunningTask = LongRunningOperationAsync();
开始执行LongRunningOperation
完成独立工作让我们假设主线程(线程ID = 1),然后达到
await longRunningTask
.现在,如果
longRunningTask
尚未完成且仍在运行,MyMethodAsync()
将返回其调用方法,因此主线程不会被阻止 . 当longRunningTask
完成后,来自ThreadPool的线程(可以是任何线程)将在其先前的上下文中返回MyMethodAsync()
并继续执行(在这种情况下将结果打印到控制台) .第二种情况是
longRunningTask
已经完成执行并且结果可用 . 到达await longRunningTask
时,我们已经得到了结果,因此代码将继续在同一个线程上执行 . (在这种情况下打印结果到控制台) . 当然,上述示例并非如此,其中涉及Task.Delay(1000)
.我认为你已经选择了一个错误的例子
System.Threading.Thread.Sleep
async
任务的一点是让它在后台执行而不锁定主线程,比如做一个DownloadFileAsync
System.Threading.Thread.Sleep
不是"being done",它只是睡觉,因此你的下一行在5秒后到达...阅读这篇文章,我认为这是对
async
和await
概念的一个很好的解释:http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx下面是通过打开对话框读取excel文件的代码,然后使用async并等待从excel逐行读取代码并运行异步并绑定到网格
请查看此小提琴https://dotnetfiddle.net/VhZdLU(并在可能的情况下进行改进)以运行 simple console application ,它在同一程序中显示 Task, Task.WaitAll(), async and await 运算符的用法 .
这个小提琴应该清除你的执行周期概念 .
这是示例代码
Trace coming from Output Window:
他们要使异步代码易于编写和阅读,是的 .
一点也不 .
async
关键字启用await
关键字 . 因此,任何使用await
的方法都必须标记为async
.不,因为
async
方法默认情况下不在另一个线程上运行 .没有 .
您可能会发现我的async/await intro很有帮助 . official MSDN docs也非常好(特别是TAP部分),
async
团队推出了出色的FAQ .说实话,我仍然认为最好的解释是关于维基百科的未来和承诺:http://en.wikipedia.org/wiki/Futures_and_promises
基本思想是您有一个单独的线程池,可以异步执行任务 . 使用时 . 该但是,对象确实承诺它会在某个时间执行操作,并在您请求时给出结果 . 这意味着它会在您请求结果时阻塞但尚未完成,但在线程池中执行 .
从那里你可以优化一些事情:一些操作可以实现async,你可以通过将后续请求和/或重新排序它们进行批处理来优化文件IO和网络通信之类的事情 . 我不确定这是否已经存在于Microsoft的任务框架中 - 但如果不是,那将是我要添加的第一件事 .
实际上,您可以使用C#4.0中的产量实现未来的模式排序 . 如果你想知道它是如何工作的,我可以推荐这个做得不错的工作的链接:http://code.google.com/p/fracture/source/browse/trunk/Squared/TaskLib/ . 但是,如果你自己开始玩它,你会注意到你真的需要语言支持,如果你想做所有很酷的事情 - 这正是微软所做的 .
我理解它的方式也是,应该在混合中增加第三个术语:
Task
.Async
只是你在方法上说的一个限定符,它说它是一个异步方法 .Task
是async
函数的返回值 . 它以异步方式执行 .你
await
一个任务 . 当代码执行到达此行时,控件会跳回到周围原始函数的调用者 .相反,如果将
async
函数(即Task
)的返回值分配给变量,则当代码执行到达此行时,它会在Task
异步执行时继续超出周围函数中的那一行 .异步/等待
实际上,Async / Await是一对关键字,它们只是用于创建异步任务回调的语法糖 .
以此为例:
该方法有几个缺点 . 错误不会传递,而且很难阅读 . 但是Async和Await会帮助我们:
Await调用必须在Async方法中 . 这有一些优点:
返回任务的结果
自动创建回调
检查错误并让它们在callstack中冒泡(最多只能等待 - 在callstack中等待调用)
等待结果
释放主线程
在主线程上运行回调
使用线程池中的工作线程来执行任务
使代码易于阅读
还有更多
NOTE :Async和Await与异步调用一起使用而不是这些 . 您必须使用 Task Libary ,例如Task.Run() .
以下是await和none await解决方案之间的比较
这是无异步解决方案:
这是异步方法:
实际上,您可以在没有await关键字的情况下调用异步方法,但这意味着此处的任何异常都会在发布模式下被吞下:
Async和Await不适用于并行计算 . 它们用于不阻止主线程 . 如果是关于asp.net或Windows应用程序 . 由于网络呼叫阻止主线程是一件坏事 . 如果您这样做,您的应用将无法响应或可能会崩溃 .
查看ms docs以获取一些示例 .
这里的所有答案都使用Task.Delay()或其他一些内置的异步函数 . 但这是我的例子,不使用这些异步函数:
在简单的控制台程序中显示上述解释 -
输出是:
从而,
Main通过TestAsyncAwaitMethods启动长时间运行的方法 . 这会立即返回而不会暂停当前线程,我们会立即看到'Press any key to exit'消息
所有这一切,LongRunningMethod在后台运行 . 完成后,来自Threadpool的另一个线程将获取此上下文并显示最终消息
因此,不会阻止线程 .
在下面的代码中,HttpClient方法GetByteArrayAsync返回一个Task,getContentsTask . 任务是在任务完成时生成实际字节数组的承诺 . 在getContentsTask完成之前,await运算符应用于getContentsTask以暂停SumPageSizesAsync中的执行 . 在此期间,控制权返回给SumPageSizesAsync的调用者 . 当getContentsTask完成时,await表达式求值为字节数组 .
For fastest learning..
了解方法执行流程(附图):3分钟
问题内省(学习缘故):1分钟
快速通过语法糖:5分钟
分享开发人员的困惑:5分钟
问题:快速将普通代码的实际实现更改为异步代码:2分钟
下一步去哪儿?
Understand method execution flow(with a diagram): 3 mins
在这张图片中,只关注#6
在第6步:AccessTheWebAsync()已经完成了它可以做的工作,没有getStringTask的结果 . 因此,AccessTheWebAsync使用await运算符来暂停其进度并将控制权返回(yield)给调用者 . AccessTheWebAsync返回一个Task(of字符串返回值)给调用者 . 该任务表示生成字符串结果的承诺 . 但什么时候会回电呢?再打一次电话?
AccessTheWebAsync()的调用者只做了等待(它可以完成一些内部任务,然后在需要时等待) . 所以调用者等待AccessTheWebAsync,而AccessTheWebAsync正在等待GetStringAsync .
请记住,该方法已经返回,它不能再次返回(没有第二次) . 那么来电者怎么知道呢?这是关于 Tasks! 任务被退回 . Task was waited for (不是方法,不是 Value ) . 值将在Task中设置 . 任务状态将设置为完成 . 来电者只是监控任务 . 进一步读取以后here .
Question introspection for learning sake: 1 min
让我们稍微调整一下问题:
因为学习
Task
会自动覆盖其他2.为了学习至少 . 当然,这是关于async
和await
的问题的答案 .Quickly get through syntax sugar: 5 mins
internal static int Method(int arg0, int arg1) { int result = arg0 + arg1; IO(); // Do some long running IO. return result; }
internal static Task<int> MethodTask(int arg0, int arg1) { Task<int> task = new Task<int>(() => Method(arg0, arg1)); task.Start(); // Hot task (started task) should always be returned. return task; }
我们提到等待还是异步?不 . 调用上面的方法,你就可以得到一个任务 . 哪个你可以监控 . 你已经知道任务返回了什么..一个整数 .
internal static async Task<int> MethodAsync(int arg0, int arg1) { int result = await HelperMethods.MethodTask(arg0, arg1); return result; }
我们完成了'awaiting'任务 . 因此
await
. 由于我们使用await,我们必须使用async(强制)和MethodAsync与'Async'作为前缀(编码标准) . 稍后进一步读取hereShare the confusion of a developer: 5 mins
开发人员犯了一个错误,即没有实现
Task
但它仍然有效!尝试理解问题,只接受已接受的答案provided here . 希望你已阅读并完全理解 . 同样在我们的示例中,调用已构建的MethodAsync()
比使用Task
(MethodTask()
)自己实现该方法更容易 . 大多数开发人员发现在将代码转换为异步代码时很难理解Tasks
.提示:尝试查找现有的Async实现(如
MethodAsync
或ToListAsync
)以外包难度 . 所以我们只需要处理Async和等待(这很简单,非常类似于普通代码)Problem: Quickly change a real-world implementation of normal code to Async operation: 2 mins
下面数据层中显示的代码行开始中断(许多地方) . 因为我们将一些代码从.Net framework 4.2更新为.Net核心 . 我们不得不在整个应用程序的1小时内解决这个问题!
十分简单!
EntityFrameWork nuget(它具有QueryableExtensions)
namespace = Microsoft.EntityFrameworkCore
代码改变了这样
Contract GetContract(int contractnumber)
至
async Task<Contract> GetContractAsync(int contractnumber)
调用方法也受到影响:
GetContractAsync(123456);
被调用为GetContractAsync(123456).Result;
我们在30分钟内到处改变了!
但是架构师告诉我们不要仅仅为此使用EntityFrameWork库!哎呀!戏剧!然后我们做了一个自定义的Task实现 . 你知道怎么做 . 还很容易!
Where to Next? 有一个很棒的快速视频,我们可以看一下Converting Synchronous Calls to Asynchronous in ASP.Net Core,因为这可能是读完之后的方向 .
本文MDSN:Asynchronous Programming with async and await (C#)明确解释了它:
进一步回答其他问题,看看await (C# Reference)
更具体地说,在所包含的例子中,它解释了你的情况
这是一个快速的控制台程序,以便跟随的人清楚 . “TaskToDo”方法是您想要进行异步的长时间运行方法 . 使其运行Async由TestAsync方法完成 . 测试循环方法只运行“TaskToDo”任务并运行它们Async . 您可以在结果中看到它们,因为它们在运行期间不会以相同的顺序完成 - 它们在完成时会向控制台UI线程报告 . 简单,但我认为简单的例子比更多涉及的例子更好地展示了模式的核心: