【问题标题】:Scala Futures and Promises Callback basicsScala Futures 和 Promises 回调基础
【发布时间】:2015-11-20 14:08:08
【问题描述】:

我是 Scala 的初学者Futures/Promises

我正在尝试使用Futures(使用回调)来做到这一点:

  • 获取可用的 Git 存储库。
  • 获取每个存储库的贡献者。
  • 获取每个存储库的 README 文件。
  • 最后为每个存储库创建一个 GitData 响应,其中包含存储库 URL、贡献者和自述文件。

代码如下:

// Final write container to hold the result.
val promise = Promise[List[GitData]]()

val repositoriesF = Future {
  GitDriver.repositoriesOf(request.user)
}

val gitRepositories = Promise[List[GitRepository]]()

repositoriesF onSuccess {
  case repositories => {
    val contributors = Promise[List[Contributor]]()
    val contributorsF = Future(repositories.map(GitDriver.contributors))
    val readMe = Promise[List[String]]
    val readMeF = Future(repositories.map(GitDriver.readme))

    contributorsF.onSuccess {
      case contrib => contributors.success(contrib.flatten)
    }

    readMeF.onSuccess {
      case r => readMe.success(r)
    }

    val extractedContributors = contributors.future.value
    val extractedReadme = readMe.future.value
    println(extractedContributors.size)
    println(extractedReadme.size)
  }
}

Await.ready(repositoriesF, Duration.Inf)

贡献者和自述文件的大小始终为零。

我解决这个问题的方法是,我可以通过Futures 并行提取贡献者和自述文件,因为它们不相互依赖。他们所需要的只是一个存储库对象。

我在这里缺少什么?我确信有更优雅的解决方案用于理解、地图、dsls 等。但只是好奇地想深入了解这一点!谢谢。

【问题讨论】:

  • 虽然方法可能听起来不错,但请记住 github has limitations on the amount of requests 您可以这样做(不是说这是问题的最初原因)
  • 是的,明白这一点 - 但我肯定在配额之内。 :-)

标签: scala asynchronous concurrency promise future


【解决方案1】:

你正在做一些奇怪的事情。根据您的问题,主要问题是您正在打印此块内的值:

repositoriesF onSuccess {
   case repositories => {
      ...

所以从某种意义上说,您只是在等待第一个异步计算,并且只为您需要的其他信息(自述文件和贡献者)设置了回调。设置回调后,那些还没有完成,你没有结果。

第二个奇怪的事情是你在第一个未来而不是其他计算上做Await()。您很可能希望等待全部信息可用。

你正在做的第三件奇怪的事情是使用了太多的 Promise。你使用 Promise 不是在你想得到一些结果的时候,而是当你必须将 Future 返回给其他人并且需要一种方法来完成这个未来时,当你不再能够访问未来本身时。

至于如何解决您的问题,我会使用 for 理解来链接期货:

val futureGitData = for {
  repos <- Future { GitDriver.repositoriesOf(request.user) }
  repo <- repos
  readme <- Future { GitDriver.readme(repo) } 
  contributors <- Future { GitDriver.contributors(repo) } 
} yield (repo, readme, contributors)

请注意,这具有更好的并行性,因为我正在为每个存储库创建不同的未来。你在哪里做什么,Future(repositories.map(GitDriver.readme)) 会同步做地图并返回一个未来。

请注意,我的设备中没有 Scala,因此代码可能并不完美,但应该能给您一个想法。

【讨论】:

    【解决方案2】:

    在您打印贡献者的大小和自述文件的那一刻,未来可能还没有完成,所以contributors.future.value 返回 None。

    你可以试试这样的:

    val repositoriesF = Future {
      GitDriver.repositoriesOf(request.user)
    }
    
    val contributorsAndReadmeFuture = for {
      repositories <- repositoriesF
      contributors <- Future(repositories.map(GitDriver.contributors))
      readme <- Future(repositories.map(GitDriver.readme))
    } yield (contributors, readme)
    
    contributorsAndReadmeFuture onSuccess {
      case (constributors, readme) => 
        println(constributors.size)
        println(readme.size)
    }
    

    【讨论】:

      【解决方案3】:

      您的future.valueprintln 行在Futures(显式Future(...)onSuccess 中设置的Promise.future)完成之前执行。

      Future的值还是None,比较一下:

      val p = Promise[Int]()
      p.future.value
      // Option[scala.util.Try[Int]] = None
      p.future.value.size
      // Int = 0
      

      既然你想继续你的解决方案,你可以这样做(没有多余的Promises):

      repositoriesF onSuccess {
        case repositories =>
          val contributorsF = Future(repositories.flatMap(GitDriver.contributors))
          val readMeF = Future(repositories.map(GitDriver.readme))
      
          contributorsF zip readMeF onSuccess {
            case (contrib, readme) => 
              println(contrib.size)
              println(readme.size)
          }
      }
      

      【讨论】:

      • 在此,我将如何创建我的响应(即 GitData) - 这是每个存储库。 case class GitData(repoUrl: String, owner: Owner, readmeContents: String, contributors: List[Contributor], programmingLanguages: List[String])
      • 这会很困难,因为你不能从回调函数中返回一些东西。我建议仔细看看 Daniel L 的回答。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-02-19
      • 1970-01-01
      • 1970-01-01
      • 2017-03-27
      • 2023-04-07
      • 2019-09-28
      • 1970-01-01
      相关资源
      最近更新 更多