简体   繁体   English

C#5.0的异步等待功能与TPL有何不同?

[英]How does C# 5.0's async-await feature differ from the TPL?

I don't see the different between C#'s (and VB's) new async features, and .NET 4.0's Task Parallel Library . 我没有看到C#(和VB)的新异步功能和.NET 4.0的任务并行库之间存在差异。 Take, for example, Eric Lippert's code from here : 举个例子来说,埃里克利珀的代码从这里

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);
    }
}

It seems that the await keyword is serving two different purposes. 似乎await关键字有两个不同的用途。 The first occurrence ( FetchAsync ) seems to mean, "If this value is used later in the method and its task isn't finished, wait until it completes before continuing." 第一次出现( FetchAsync )似乎意味着, “如果稍后在方法中使用此值并且其任务未完成,请等到它完成后再继续。” The second instance ( archive ) seems to mean, "If this task is not yet finished, wait right now until it completes." 第二个实例( archive )似乎意味着, “如果此任务尚未完成,请立即等待直到完成。” If I'm wrong, please correct me. 如果我错了,请纠正我。

Couldn't it just as easily be written like this? 难道不能像这样容易写吗?

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
    }
}

I've replaced the first await with a Task.Result where the value is actually needed, and the second await with Task.Wait() , where the wait is actually occurring. 我已经用实际需要值的Task.Result替换了第一个await ,第二个awaitTask.Wait() ,其中等待实际发生。 The functionality is (1) already implemented, and (2) much closer semantically to what is actually happening in the code. 功能是(1)已经实现, (2)在语义上与代码中实际发生的内容更接近。

I do realize that an async method is rewritten as a state machine, similar to iterators, but I also don't see what benefits that brings. 我确实意识到async方法被重写为状态机,类似于迭代器,但我也看不出它带来了什么好处。 Any code that requires another thread to operate (such as downloading) will still require another thread, and any code that doesn't (such as reading from a file) could still utilize the TPL to work with only a single thread. 任何需要另一个线程运行的代码(例如下载)仍然需要另一个线程,任何不需要的代码(例如从文件中读取)仍然可以利用TPL只使用一个线程。

I'm obviously missing something huge here; 我显然遗失了一些巨大的东西; can anybody help me understand this a little better? 任何人都可以帮助我更好地理解这一点吗?

I think the misunderstanding arises here: 我认为这里出现了误解:

It seems that the await keyword is serving two different purposes. 似乎await关键字有两个不同的用途。 The first occurrence (FetchAsync) seems to mean, "If this value is used later in the method and its task isn't finished, wait until it completes before continuing." 第一次出现(FetchAsync)似乎意味着,“如果稍后在方法中使用此值并且其任务未完成,请等到它完成后再继续。” The second instance (archive) seems to mean, "If this task is not yet finished, wait right now until it completes." 第二个实例(存档)似乎意味着,“如果此任务尚未完成,请立即等待直到完成。” If I'm wrong, please correct me. 如果我错了,请纠正我。

This is actually completely incorrect. 这实际上是完全错误的。 Both of these have the same meaning. 这两者具有相同的含义。

In your first case: 在你的第一个案例中:

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

What happens here, is that the runtime says "Start calling FetchAsync, then return the current execution point to the thread calling this method." 这里发生的是运行时说“开始调用FetchAsync,然后将当前执行点返回给调用此方法的线程。” There is no "waiting" here - instead, execution returns to the calling synchronization context, and things keep churning. 这里没有“等待” - 相反,执行返回到调用同步上下文,并且事情继续搅动。 At some point in the future, FetchAsync's Task will complete, and at that point, this code will resume on the calling thread's synchronization context, and the next statement (assigning the document variable) will occur. 在将来的某个时刻,FetchAsync的任务将完成,此时,此代码将在调用线程的同步上下文中恢复,并且将发生下一个语句(分配文档变量)。

Execution will then continue until the second await call - at which time, the same thing will happen - if the Task<T> (archive) isn't complete, execution will be released to the calling context - otherwise, the archive will be set. 执行将继续,直到第二次等待调用 - 此时,同样的事情将发生 - 如果Task<T> (存档)未完成,执行将被释放到调用上下文 - 否则,存档将被设置。

In the second case, things are very different - here, you're explicitly blocking, which means that the calling synchronization context will never get a chance to execute any code until your entire method completes. 在第二种情况下,事情是非常不同的 - 在这里,你明确地阻塞,这意味着在整个方法完成之前,调用同步上下文永远不会有机会执行任何代码。 Granted, there is still asynchrony, but the asynchrony is completely contained within this block of code - no code outside of this pasted code will happen on this thread until all of your code completes. 当然,仍然存在异步,但是异步完全包含在这个代码块中 - 在此线程之外的代码之外不会发生任何代码,直到所有代码完成。

There is a huge difference: 这是个很大的差异:

Wait() blocks, await does not block. Wait()块, await不阻塞。 If you run the async version of ArchiveDocuments() on your GUI thread, the GUI will stay responsive while the fetching and archiving operations are running. 如果在GUI线程上运行ArchiveDocuments()的异步版本,GUI将在提取和归档操作运行时保持响应。 If you use the TPL version with Wait() , your GUI will be blocked. 如果将TPL版本与Wait() ,则会阻止您的GUI。

Note that async manages to do this without introducing any threads - at the point of the await , control is simply returned to the message loop. 请注意, async设法在不引入任何线程的情况下执行此操作 - 在await点,控制简单地返回到消息循环。 Once the task being waited for has completed, the remainder of the method (continuation) is enqueued on the message loop and the GUI thread will continue running ArchiveDocuments where it left off. 一旦等待的任务完成,该方法的其余部分(继续)将在消息循环中排队,并且GUI线程将继续运行ArchiveDocuments ,从而停止。

Anders boiled it down to a very succinct answer in the Channel 9 Live interview he did. 在他所做的第9频道直播采访中,安德斯将其归结为一个非常简洁的答案。 I highly recommend it 我强烈推荐它

The new Async and await keywords allow you to orchestrate concurrency in your applications. 新的Async和await关键字允许您在应用程序中协调并发 They don't actually introduce any concurrency in to your application. 它们实际上并没有在您的应用程序中引入任何并发性。

TPL and more specifically Task is one way you can use to actually perform operations concurrently. TPL,更具体地说,Task是一种可以用来同时实际执行操作的方法。 The new async and await keyword allow you to compose these concurrent operations in a "synchronous" or "linear" fashion. 新的async和await关键字允许您以“同步”或“线性”方式组合这些并发操作。

So you can still write a linear flow of control in your programs while the actual computing may or may not happen concurrently. 因此,您仍然可以在程序中编写线性控制流,而实际计算可能会同时发生也可能不会同时发生。 When computation does happen concurrently, await and async allow you to compose these operations. 当计算同时发生时,await和async允许您组合这些操作。

The ability to turn the program flow of control into a state machine is what makes these new keywords intresting. 将程序流程控制转变为状态机的能力使这些新关键词变得有意义。 Think of it as yielding control , rather than values. 把它想象成控制而不是价值。

Check out this Channel 9 video of Anders talking about the new feature. 查看Anders的第9频道视频 ,了解新功能。

The problem here is that the signature of ArchiveDocuments is misleading. 这里的问题是ArchiveDocuments的签名具有误导性。 It has an explicit return of void but really the return is Task . 它有一个明确的void返回,但实际上返回的是Task To me void implies synchronous as there is no way to "wait" for it to finish. 对我来说,虚空意味着同步,因为没有办法“等待”它完成。 Consider the alternate signature of the function. 考虑函数的备用签名。

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

To me when it's written this way the difference is much more obvious. 对我来说,这样写的差别就更明显了。 The ArchiveDocuments function is not one that completes synchronously but will finish later. ArchiveDocuments函数不是同步完成但稍后完成的函数。

The await keyword does not introduce concurrency. await关键字不引入并发。 It is like the yield keyword, it tells the compiler to restructure your code into lambda controlled by a state machine. 它就像yield关键字一样,它告诉编译器将代码重构为由状态机控制的lambda。

To see what await code would look like without 'await' see this excellent link: http://blogs.msdn.com/b/windowsappdev/archive/2012/04/24/diving-deep-with-winrt-and-await.aspx 要看看没有“等待”的代码会是什么样子,请看这个优秀的链接: http//blogs.msdn.com/b/windowsappdev/archive/2012/04/24/diving-deep-with-winrt-and-await的.aspx

The call to FetchAsync() will still block until it completes (unless a statement within calls await ?) The key is that control is returned to the caller (because the ArchiveDocuments method itself is declared as async ). FetchAsync()的调用仍会阻塞,直到它完成(除非调用中的语句await ?)关键是控件返回给调用者(因为ArchiveDocuments方法本身被声明为async )。 So the caller can happily continue processing UI logic, respond to events, etc. 因此调用者可以愉快地继续处理UI逻辑,响应事件等。

When FetchAsync() completes, it interrupts the caller to finish the loop. FetchAsync()完成时,它会中断调用者以完成循环。 It hits ArchiveAsync() and blocks, but ArchiveAsync() probably just creates a new task, starts it, and returns the task. 它命中ArchiveAsync()并阻塞,但ArchiveAsync()可能只是创建一个新任务,启动它并返回任务。 This allows the second loop to begin, while the task is processing. 这允许第二个循环开始,而任务正在处理。

The second loop hits FetchAsync() and blocks, returning control to the caller. 第二个循环命中FetchAsync()并阻塞,将控制权返回给调用者。 When FetchAsync() completes, it again interrupts the caller to continue processing. FetchAsync()完成时,它再次中断调用者以继续处理。 It then hits await archive , which returns control to the caller until the Task created in loop 1 completes. 然后命中await archive ,它将控制返回给调用者,直到在循环1中创建的Task完成。 Once that task is complete, the caller is again interrupted, and the second loop calls ArchiveAsync() , which gets a started task and begins loop 3, repeat ad nauseum . 一旦该任务完成,调用者再次被中断,第二个循环调用ArchiveAsync() ,它获得一个已启动的任务并开始循环3,重复广告恶心

The key is returning control to the caller while the heavy lifters are executing. 关键是在重型升降机执行时将控制权返回给呼叫者。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM