C#5.0的asynchronous等待function与TPL有什么不同?

我没有看到C#的(和VB的)新的asynchronousfunction和.NET 4.0的任务并行库之间的不同 。 以Eric Lippert的代码为例:

async void ArchiveDocuments(List<Url> urls) { Task archive = null; for(int i = 0; i < urls.Count; ++i) { var document = await FetchAsync(urls[i]); if (archive != null) await archive; archive = ArchiveAsync(document); } } 

看起来await关键字有两个不同的用途。 第一次出现( FetchAsync )似乎意味着: “如果此值稍后在方法中使用且其任务未完成,请等到它完成后再继续。 第二个例子( archive )似乎意味着: “如果这个任务尚未完成,请立即等待,直到完成。” 如果我错了,请纠正我。

难道不能像这样轻松地写出来吗?

 void ArchiveDocuments(List<Url> urls) { for(int i = 0; i < urls.Count; ++i) { var document = FetchAsync(urls[i]); // removed await if (archive != null) archive.Wait(); // changed to .Wait() archive = ArchiveAsync(document.Result); // added .Result } } 

我已经用Task.Result取代了第一个await实际需要的值,第二个用Task.Wait()等待,其中等待实际发生。 function是(1)已经实现, (2)在语义上更接近代码中实际发生的事情。

我意识到一个async方法被重写为一个状态机,类似于迭代器,但是我也看不到会带来什么好处。 任何需要另一个线程操作(如下载)的代码仍然需要另一个线程,任何不能读取的代码(如读取文件)仍然可以利用TPL来处理单个线程。

我显然在这里失去了一些东西。 有人能帮我理解这个好一点吗?

我认为这里产生了误会:

看起来await关键字有两个不同的用途。 第一次出现(FetchAsync)似乎意味着:“如果此值稍后在方法中使用,且其任务未完成,请等到它完成后再继续。 第二个例子(档案)似乎意味着:“如果这个任务尚未完成,请立即等待,直到完成。” 如果我错了,请纠正我。

这实际上是完全不正确的。 这两个都有相同的含义。

在你的第一个案例中:

 var document = await FetchAsync(urls[i]); 

这里发生的事情是运行时显示“开始调用FetchAsync,然后返回当前执行点到调用此方法的线程”。 在这里没有“等待” – 相反,执行返回到调用同步上下文,事情不断搅动。 在将来的某个时候,FetchAsync的任务将会完成,在这一点上,这段代码将在调用线程的同步上下文中恢复,下一个语句(分配文档variables)将会发生。

然后执行将继续,直到第二个等待调用 – 在这个时间,同样的事情会发生 – 如果Task<T> (档案)不完整,执行将释放到调用上下文 – 否则,存档将被设置。

在第二种情况下,事情是非常不同的 – 在这里,你明确的阻塞,这意味着调用同步上下文将永远不会有机会执行任何代码,直到你的整个方法完成。 当然,仍然是asynchronous的,但asynchronous完全包含在这个代码块中 – 除非你的所有代码完成,否则这个粘贴代码之外的任何代码都不会发生在这个线程上。

这是个很大的差异:

Wait()阻塞, await不会阻塞。 如果在GUI线程上运行asynchronous版本的ArchiveDocuments() ,GUI将在提取和归档操作正在运行时保持响应。 如果您使用Wait()的TPL版本,您的GUI将被阻止。

请注意, asyncpipe理这样做,而不引入任何线程 – 在await点,控制简单地返回到消息循环。 一旦被等待的任务完成,方法的其余部分(continuation)就会在消息循环中被排队,GUI线程将继续运行ArchiveDocuments

安德斯在9频道现场采访中将其简化为一个非常简洁的答案。 我强烈推荐它

新的Async和await关键字允许您在应用程序中协调并发 。 他们实际上并没有在你的应用程序中引入任何并发。

TPL和更具体的任务是一种可以用来同时实际执行操作的方法。 新的async和await关键字允许您以“同步”或“线性”方式组合这些并发操作。

所以你仍然可以在你的程序中编写一个线性的控制stream程,而实际的计算可能不会同时发生。 当计算同时发生时,await和async允许你组合这些操作。

将控制程序stream程转化为状态机的能力,使得这些新的关键字成为可能。 把它看作是控制权 ,而不是价值观。

看看这个9频道的安德斯谈论新function。

这里的问题是ArchiveDocuments的签名是误导性的。 它有void的显式返回,但实际上返回的是Task 。 对我来说void意味着同步,因为没有办法“等”它完成。 考虑函数的替代签名。

 async Task ArchiveDocuments(List<Url> urls) { ... } 

对我来说,这样写的时候差别就更明显了。 ArchiveDocuments函数不是同步完成的,而是稍后完成的。

FetchAsync()的调用仍然会阻塞,直到它完成(除非调用中的语句await ?)关键是控制返回给调用者(因为ArchiveDocuments方法本身被声明为async )。 因此,调用者可以愉快地继续处理UI逻辑,响应事件等。

FetchAsync()完成时,它会中断调用者完成循环。 它命中ArchiveAsync()和块,但ArchiveAsync()可能只是创build一个新的任务,启动它,并返回任务。 这允许第二个循环开始,而任务正在处理。

第二个循环命中FetchAsync()并阻塞,将控制权返回给调用者。 当FetchAsync()完成时,它再次中断调用者继续处理。 然后它会await archive ,这会将控制权返回给调用者,直到循环1中创build的Task完成。 一旦这个任务完成,调用者再次中断,第二个循环调用ArchiveAsync() ,它获得一个启动的任务,并开始循环3,重复广告的恶心

关键是在重型升降机正在执行时将控制权返回给主叫方。

await关键字不会引入并发。 它就像yield关键字一样,它告诉编译器将你的代码重构成由状态机控制的lambda。

看看等待代码看起来像什么没有“等待”看到这个优秀的链接: http : //blogs.msdn.com/b/windowsappdev/archive/2012/04/24/diving-deep-with-winrt-and-await的.aspx

Interesting Posts