数日間、猫の効果とIOに頭を抱えてきました。そして、私はこの効果についていくつかの誤解を持っているか、単にそのポイントを逃したと感じています。
IO.shift
を使用?IO.async
を使用?IO.delay
同期ですか、非同期ですか?このAsync[F].delay(...)
のようなコードで一般的な非同期タスクを作成できますか?または、IOをunsafeToAsync
またはunsafeToFuture
で呼び出すと非同期になりますか?私はそれらの猫効果のドキュメントを理解するのに失敗し、インターネットはそれほど役に立たなかったので、これらのいくつかについて明確にしていただければ幸いです...
IOがScalaのFutureを置き換えることができる場合、非同期を作成するにはどうすればよいですかIOタスク
まず、非同期タスクの意味を明確にする必要があります。通常asyncは「OSスレッドをブロックしない」ことを意味しますが、Future
について言及しているため、少しぼやけています。たとえば、私が書いた場合:
_Future { (1 to 1000000).foreach(println) }
_
asyncにはなりません。これはブロッキングループとブロッキング出力であるためですが、暗黙のExecutionContextによって管理されているため、別のOSスレッドで実行される可能性があります。同等の猫効果コードは次のようになります。
_for {
_ <- IO.shift
_ <- IO.delay { (1 to 1000000).foreach(println) }
} yield ()
_
(それは短いバージョンではありません)
そう、
IO.shift
_は、スレッド/スレッドプールを変更するために使用されます。 Future
はすべての操作でそれを行いますが、パフォーマンスの面で自由ではありません。IO.delay
_ {...}(別名_IO { ... }
_)は[〜#〜] not [〜#〜]何でも非同期にして[〜#〜 ] not [〜#〜]スレッドを切り替えます。同期副作用APIから単純なIO
値を作成するために使用されますでは、true asyncに戻りましょう。ここで理解することはこれです:
すべての非同期計算は、コールバックを受け取る関数として表すことができます。
Future
を返すAPIを使用していても、JavaのCompletableFuture
を使用していても、NIO CompletionHandler
のようなものを使用していても、すべてコールバックに変換できます。これが_IO.async
_の目的です。コールバックを取得する関数を取るコールバックをIO
に変換できます。そして、次のような場合:
_for {
_ <- IO.async { ... }
_ <- IO(println("Done"))
} yield ()
_
Done
は、_...
_の計算がコールバックした場合(およびその場合)にのみ出力されます。緑のスレッドをブロックしているが、OSスレッドはブロックしていないと考えることができます。
そう、
IO.async
_はすでに非同期計算をIO
に変換するためのものです。IO.delay
_は完全同期計算をIO
に変換するためのものです。Future
sを使用する場合の最も近い例は、_scala.concurrent.Promise
_を作成して_p.future
_を返すことです。
または、IOをunsafeToAsyncまたはunsafeToFutureで呼び出すと、非同期が発生しますか?
ちょっと。 IO
では、これらのいずれかを呼び出す(またはIOApp
を使用する)場合を除き、-nothingが発生します。ただし、IOは、_IO.shift
_または_IO.async
_で明示的に要求しない限り、別のOSスレッドまたは非同期で実行することを保証しません。
いつでもスレッドの切り替えを保証できます。 _(IO.shift *> myIO).unsafeRunAsyncAndForget()
_。これは、myIO
が_val myIO
_であるか_def myIO
_であるかにかかわらず、要求されるまで実行されないためです。
ただし、ブロック操作を非ブロックに魔法のように変換することはできません。 Future
もIO
もそれは不可能です。
猫効果における非同期と同時のポイントは何ですか?なぜ分離されているのですか?
Async
とConcurrent
(およびSync
)は型クラスです。これらは、プログラマが_cats.effect.IO
_にロックされるのを避け、monix TaskやScalaz 8 ZIOなどの選択したものをサポートするAPI、または_OptionT[Task, *something*]
_などのモナドトランスフォーマタイプさえ提供できるように設計されています。 fs2、monix、http4sなどのライブラリは、それらを使用して、何を使用するかについてより多くの選択肢を提供します。
Concurrent
はAsync
の上に追加のものを追加します。それらの最も重要なものは_.cancelable
_と_.start
_です。これらはFuture
と直接の類似点はありません。これはキャンセルをまったくサポートしていないためです。
_.cancelable
_は_.async
_のバージョンであり、ラップしている操作をキャンセルするロジックも指定できます。一般的な例はネットワークリクエストです。結果に興味がなくなった場合は、サーバーの応答を待たずに、リクエストを中止することができます。応答の読み取りでソケットや処理時間を無駄にしないでください。直接使用することは決してないかもしれませんが、場所があります。
しかし、キャンセルできない場合のキャンセル可能な操作とは何でしょうか?ここで重要なのは、操作をそれ自体からキャンセルできないことです。他の誰かがその決定を行わなければなりません、そしてそれは起こります同時に操作自体(これは型クラスがその名前を取得する場所です)。それが_.start
_の出番です。要するに、
_.start
_は緑のスレッドの明示的な分岐です。
_someIO.start
_を実行することは、val t = new Thread(someRunnable); t.start()
を実行することと似ていますが、今は緑色です。そしてFiber
は基本的にThread
APIのストリップバージョンです:_.join
_を実行できますが、これはThread#join()
に似ていますが、OSスレッドをブロックしません。および_.cancel
_、これは.interrupt()
の安全なバージョンです。
グリーンスレッドをフォークする他の方法があることに注意してください。たとえば、並列操作を行う場合:
_val ids: List[Int] = List.range(1, 1000)
def processId(id: Int): IO[Unit] = ???
val processAll: IO[Unit] = ids.parTraverse_(processId)
_
すべてのIDの処理をフォークしてグリーンスレッドにしてから、それらすべてを結合します。または_.race
_を使用:
_val fetchFromS3: IO[String] = ???
val fetchFromOtherNode: IO[String] = ???
val fetchWhateverIsFaster = IO.race(fetchFromS3, fetchFromOtherNode).map(_.merge)
_
フェッチを並行して実行し、最初の結果を完了させ、遅いフェッチを自動的にキャンセルします。したがって、_.start
_を実行し、Fiber
を使用することは、より多くのグリーンスレッドをフォークする唯一の方法ではなく、最も明示的なものだけです。そして、それは答えます:
IO緑のスレッドですか?はいの場合、猫効果にファイバーオブジェクトがあるのはなぜですか?私が理解しているように、ファイバーは緑のスレッドですが、ドキュメントではIOを緑のスレッドと見なすことができると主張しています。 。
IO
はグリーンスレッドのようなものです。つまり、OSスレッドのオーバーヘッドなしに多くのスレッドを並行して実行でき、for-comprehensionのコードは、結果の計算がブロックされているかのように動作します。
Fiber
は、明示的にforkされた(完了を待つかキャンセルする)緑のスレッドを制御するためのツールです。