前言Swift内置的并发系统的好处之一是可以更轻松地并行执行多个异步任务,从而使我们能够显着加快任务的速度可以分解成单独的部分操作。在本文中,我们将了解几种不同的方法,以及每种技术在何时特别有用。从异步到并发首先,假设我们正在开发某种形式的购物应用程序来展示各种产品,我们已经实现了一个ProductLoader应用程序,它允许我们使用一系列异步API加载不同的产品集合,如下所示:ProductLoader{...funcloadFeatured()asyncthrows->[Product]{...}funcloadFavorites()asyncthrows->[Product]{...}funcloadLatest()asyncthrows->[Product]{...}}虽然大多数时候上述每个方法都可能被单独调用,但假设在我们应用程序的某些部分,我们还想形成一个组合的推荐模型,其中包含这三个ProductLoader方法的所有结果:extensionProduct{structRecommendations{varfeatured:[Product]varfavorites:[Product]varlatest:[Product]}}一种方法是使用await关键字来调用每个加载方法,然后使用这些调用的结果来创建我们的实例Recommendations模型的-看起来像这样:.Recommendations(featured:精选,favorites:favorites,latest:latest)}}上面的实现确实有效——然而,尽管我们的三个加载操作都是完全异步的,但它们目前是顺序执行的,一个接一个。因此,尽管我们的顶级loadRecommendations方法与我们的应用程序相关的其余代码是并发执行的,但它实际上并没有利用并发来执行其内部操作集。由于我们的产品加载方法不以任何方式相互依赖,因此确实没有理由按顺序执行它们,所以让我们看看如何让它们完全并发执行。关于如何做到这一点的最初想法可能是将上述代码简化为单个表达式,这将允许我们使用单个await关键字来等待我们的每个操作完成:extensionProductLoader{funcloadRecommendations()asyncthrows->Product.Recommendations{tryawaitProduct.Recommendations(featured:loadFeatured(),favorites:loadFavorites(),latest:loadLatest())}}但是,即使我们的代码现在看起来是并发的,它实际上仍然会像以前一样运行按顺序执行完全相同。相反,我们需要利用Swift的asynclet绑定来告诉并发系统并行执行我们的每个加载操作。使用此语法使我们能够在后台启动异步操作,而无需我们立即等待它完成。如果我们在实际使用时(即形成模型时)将加载的数据与单个关键字Recommendations结合使用,我们将获得并行执行加载操作的所有好处,而不必担心状态管理或数据竞争等问题:扩展ProductLoader{funcloadRecommendations()异步抛出->产品。建议{asyncletfeatured=loadFeatured()asyncletfavorites=loadFavorites()asyncletlatest=loadLatest()returntryawaitProduct.Recommendations(featured:favorites,,latest:latest)}}很整洁!因此,asynclet提供了一种内置方法,可以在我们要执行一组已知的有限任务时同时运行多个操作。但如果情况并非如此呢?任务组现在假设我们正在开发一个ImageLoader工具,它允许我们通过网络加载图像。要从给定的URL加载单个图像,我们可以使用这样的方法:classImageLoader{...funcloadImage(fromurl:URL)asyncthrows->UIImage{...}}为了加载一系列图像变得简单,我们还创建了一个方便的API,它接受一组URL并异步返回以下载图像的URL为键的图像字典:extensionImageLoader{funcloadImages(fromurls:[URL])asyncthrows->[URL:UIImage]{varimages=[URL:UIImage]()forurlinurls{images[url]=tryawaitloadImage(from:url)}returnimages}}现在让我们像ProductLoader之前那样说,我们希望上面的loadImages方法并发执行,而不是按顺序下载每个图像(目前是这种情况,因为我们在调用时直接在for循环中等待loadImage)。然而,这一次我们将无法使用asynclet,因为我们需要执行的任务数量在编译时是未知的。值得庆幸的是,Swift并发工具箱中还有另一个工具可以让我们并行执行动态数量的任务——任务组。要形成一个任务组,我们可以调用withTaskGroup或withThrowingTaskGroup,这取决于我们是否希望在我们的任务中有抛出错误的选项。在本例中,我们将选择后者,因为我们的底层loadImage方法标有throws关键字。然后我们将遍历每个URL,就像以前一样,只是这次我们将每个图像加载任务添加到我们的组中,而不是直接等待它完成。相反,我们将在添加每个任务后单独等待结果,这将允许我们的图像加载操作完全并发执行:extensionImageLoader{funcloadImages(fromurls:[URL])asyncthrows->[URL:UIImage]{tryawaitwithThrowingTaskGroup(of:(URL,UIImage).self){groupinforurlinurls{group.addTask{让图像=尝试awaitself.loadImage(from:url)return(url,image)}}varimages=[URL:UIImage]()fortryawait(url,image)ingroup{images[url]=image}returnimages}}}要了解有关上述tryawait语法和一般异步序列的更多信息,请参阅“异步序列,流和组合”。与asynclet一样,以我们的操作不直接改变任何状态的方式编写并发代码的巨大好处之一是这样做可以让我们完全避免任何类型的数据竞争问题,同时也不需要我们引入任何混合在一起的锁定或序列化代码。await因此,在可能的情况下,让我们的每个并发操作返回一个完全独立的结果,然后依次返回这些结果以形成我们最终的数据集,通常是一个好主意。在以后的文章中,我们将更仔细地研究避免数据竞争的其他方法(例如使用Swift的新角色类型)。结论重要的是要记住,仅仅因为一个给定的函数被标记为异步并不一定意味着它会同时完成它的工作。相反,如果这是我们想要做的,我们必须有意地让我们的任务并行运行,这只有在执行一组可以独立运行的操作时才有意义。
