如何在Scala中使用线程安全异步包装器包装库异步方法?

时间:2019-12-18 21:48:35

标签: scala concurrency locking

我正在使用一个库,该库的方法如下所示。

  trait LibraryDependency {
    /**
     * This method if called while previous future is not resolved will return a failed future
     * @return
     */
    def foo(): Future[Boolean]
  }

如果foo()返回的以前的将来尚未完成,则方法foo()将返回失败的将来。由于我无法修改特征库的实现,因此我尝试用自己的包装器将其包装,以提供所需的行为。

我需要的行为是,如果同时存在对foo()的调用,则其他期货也会阻塞,直到第一个期货解决。我试图做这样的事情。

  class ThreadSafeLibraryWrapper(delegate: LibraryDependency) extends LibraryDependency {
    private val lock: Object = new Object
    private implicit val ec: ExecutionContext = ExecutionContext.Implicits.global

    /**
     * This one will block the other concurrent calls to foo()
     * @return
     */
    override def foo(): Future[Boolean] = {
      val promise = Promise[Boolean]()

      lock.synchronized {
        val result = delegate.foo()
        promise.completeWith(result)

        result.onComplete { _ =>
          lock.notify()
        }

        lock.wait()
      }

      promise.future
    }
  }

我遇到了以下问题,我不确定如何阻止正在调用此方法的线程,并完成原始的将来,所以我得到了IllegalMonitorStateException

编辑:我已经通过使用Await

解决了这个问题
  class ThreadSafeLibraryWrapper(delegate: LibraryDependency) extends LibraryDependency {
    private val lock: Object = new Object
    private implicit val ec: ExecutionContext = ExecutionContext.Implicits.global

    /**
     * This one will block the other concurrent calls to foo()
     * @return
     */
    override def foo(): Future[Boolean] = Future {
      lock.synchronized {
        Await.result(delegate.foo(), Duration.Inf)
      }
    }
  }

我仍然不确定如何避免使用Await

1 个答案:

答案 0 :(得分:0)

如果我正确理解了您的问题,则您的依赖项可以同时在Future上运行,因此您希望使用包装器来限制对foo方法的访问,以免将来返回失败的结果。如果是这样,则看起来您需要排队下一个调用,直到上一个调用完成。 好吧,我做了一些原型设计,希望对您有所帮助:

import java.time.LocalTime.now

import scala.collection.immutable.Queue
import scala.concurrent.{ExecutionContext, Future, Promise}
import scala.util.{Failure, Success, Try}

object Concurrency {
  trait LibraryDependency {
    /**
     * This method if called while previous future is not resolved will return a failed future
     * @return
     */
    def foo(): Future[Boolean]
  }

  class DummyLibraryDependency(implicit ec: ExecutionContext) extends LibraryDependency {
    override def foo(): Future[Boolean] = {
      Future {
        println(s"${now()} - started dependency execution")
        Thread.sleep(1000)
        println(s"${now()} - finished dependency execution")
        true
      }
    }
  }

  class SafeLibraryDependency(delegate: LibraryDependency)(implicit ec: ExecutionContext) {
    private type OnComplete = Try[Boolean] => Unit

    private var currentlyRunning: Option[Future[Boolean]] = None
    private var queue: Queue[Promise[Boolean]] = Queue[Promise[Boolean]]()

    def foo: Future[Boolean] = {
      this.synchronized {
        currentlyRunning.fold(startDelegateTask(onRunningComplete))(_ => enqueueNextTask())
      }
    }

    private def enqueueNextTask(): Future[Boolean] = {
      val promise = Promise[Boolean]()
      queue = queue enqueue promise
      promise.future
    }

    private def onRunningComplete(result: Try[Boolean]): Unit = {
      this.synchronized {
        currentlyRunning = None
        if(queue.nonEmpty) {
          val (promise, newQueue) = queue.dequeue
          queue = newQueue
          startDelegateTask { result =>
            promise.complete(result)
            onRunningComplete(result)
          }
        }
      }
    }

    private def startDelegateTask(f: OnComplete): Future[Boolean] = {
      val task = delegate.foo()
      task.onComplete(f)
      currentlyRunning = Some(task)
      task
    }
  }

  def main(args: Array[String]): Unit = {
    import scala.concurrent.ExecutionContext.Implicits.global
    val dummyLibraryDependency = new DummyLibraryDependency
    val safeLibraryDependency = new SafeLibraryDependency(dummyLibraryDependency)

    safeLibraryDependency.foo.onComplete(result => println(s"${now()} - #1 complete with result: $result"))
    safeLibraryDependency.foo.onComplete(result => println(s"${now()} - #2 complete with result: $result"))
    safeLibraryDependency.foo.onComplete(result => println(s"${now()} - #3 complete with result: $result"))

    Thread.sleep(5000)
    println("Done")
  }
}

所以SafeLibraryDependency-这是包装器,将调用仅限制为一次运行的Future

在我的机器上的输出是下一个:

19:30:43.666 - started dependency execution
19:30:44.679 - finished dependency execution
19:30:44.681 - started dependency execution
19:30:44.679 - #1 complete with result: Success(true)
19:30:45.681 - finished dependency execution
19:30:45.681 - started dependency execution
19:30:45.681 - #2 complete with result: Success(true)
19:30:46.682 - finished dependency execution
19:30:46.682 - #3 complete with result: Success(true)
Done

希望这对您有帮助!