Skip to content

Commit

Permalink
m1l5 - Coroutines
Browse files Browse the repository at this point in the history
  • Loading branch information
dgovorukhin authored and evgnep committed Sep 16, 2023
1 parent af8cebf commit 3b998a5
Show file tree
Hide file tree
Showing 25 changed files with 484 additions and 1 deletion.
6 changes: 5 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@
kotlin.code.style=official
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8

kotlinVersion=1.8.10
kotlinVersion=1.8.10
coroutinesVersion=1.6.4

jacksonVersion=2.14.1
okhttpVersion=4.9.3
17 changes: 17 additions & 0 deletions m1l5-coroutines/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
plugins {
kotlin("jvm")
}

val coroutinesVersion: String by project
val jacksonVersion: String by project
val okhttpVersion: String by project

dependencies {
implementation(kotlin("stdlib"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:$jacksonVersion") // from string to object

implementation("com.squareup.okhttp3:okhttp:$okhttpVersion") // http client

testImplementation(kotlin("test-junit"))
}
20 changes: 20 additions & 0 deletions m1l5-coroutines/src/main/kotlin/continuation/DecompileMePlease.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package continuation

import kotlinx.coroutines.delay

/**
* How to decompile file in IDEA
*
* 1. Select class: build -> classes -> kotlin -> main -> continuation -> DecompileMePleaseKt.class
* 2. Decompile: Tools -> Kotlin -> Decompile to Java
*/

suspend fun myFunction() {
println("Before")
var counter = 0

delay(1000) // suspending

counter++
println("After: $counter")
}
25 changes: 25 additions & 0 deletions m1l5-coroutines/src/main/kotlin/continuation/HowItWorks.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package continuation

import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.suspendCoroutine

fun main() = runBlocking {
println("Start")

// launch {
// delay(1000)
// repeat(5) {
// delay(500)
// println("Working....")
// }
// println("Done")
// }
//
// suspendCoroutine<Unit> { continuation ->
//// continuation.resume(Unit)
// }

println("Finish")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
//package continuation
//
//import kotlinx.coroutines.delay
//import kotlin.coroutines.Continuation
//import kotlin.coroutines.CoroutineContext
//
//fun myFunction(continuation: Continuation<Unit>): Any {
// val continuation = continuation as? MyFunctionContinuation
// ?: MyFunctionContinuation(continuation)
//
// var counter = continuation.counter
//
// when (continuation.label) {
// 0 -> {
// println("Before")
// counter = 0
// continuation.counter = counter
// continuation.label = 1
// if (delay(1000, continuation) == IntrinsicsKt.COROUTINE_SUSPENDED){
// return IntrinsicsKt.COROUTINE_SUSPENDED
// }
// }
// 1 -> {
// counter = (counter as Int) + 1
// println("After: $counter")
// return Unit
// }
// else -> error("Impossible")
// }
// return Unit
//}
//
//class MyFunctionContinuation(
// val completion: Continuation<Unit>
//) : Continuation<Unit> {
// override val context: CoroutineContext
// get() = completion.context
//
// var label = 0
// var result: Result<Any>? = null
//
// var counter = 0
//
// override fun resumeWith(result: Result<Unit>) {
// this.result = result
// val res = try {
// val r = myFunction(this)
// if (r == IntrinsicsKt.COROUTINE_SUSPENDED) return
// Result.success(r as Unit)
// } catch (e: Throwable) {
// Result.failure(e)
// }
// completion.resumeWith(res)
// }
//}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package coroutinescope

import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext

fun CoroutineScope.suspendingCall(ctx: CoroutineContext) =
launch(ctx) {
println("Start_delay")
delay(500)
println("foo bar")
}

fun CoroutineScope.blockingCall(ctx: CoroutineContext) =
launch(ctx) {
runBlocking {
println("Taking delay")
delay(500)
println("foo bar")
}
}

@OptIn(DelicateCoroutinesApi::class)
fun main() {
println("Main start")
val ctx = newSingleThreadContext("MyOwnThread")
runBlocking {
repeat(5) {
suspendingCall(ctx)
// blockingCall(ctx)
}
}
println("Main end")
}
31 changes: 31 additions & 0 deletions m1l5-coroutines/src/main/kotlin/dispatchers/main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package dispatchers

import kotlinx.coroutines.*

fun main() = runBlocking {
launch { // context of the parent, main runBlocking coroutine
println("main runBlocking : I'm working in thread ${Thread.currentThread().name}")
}.join()
launch(Dispatchers.Unconfined) { // not confined to any thread -- will work with main thread now, and later on different
println("Unconfined : I'm working in thread ${Thread.currentThread().name}")
delay(50L)
println("Unconfined : I'm working in thread ${Thread.currentThread().name} after delay()")
}.join()
launch(Dispatchers.Default) { // will get dispatched to DefaultDispatcher -> shared background pool of threads
println("Default : I'm working in thread ${Thread.currentThread().name}")
}.join()
launch(newSingleThreadContext("MyOwnThread")) { // will get its own new thread
println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
}.join()
}

// When launch { ... } is used without parameters, it inherits the context (and thus dispatcher) from the CoroutineScope it is being launched from. In this case, it inherits the context of the main runBlocking coroutine which runs in the main thread.
//
// The Dispatchers.Unconfined coroutine dispatcher starts a coroutine in the caller thread, but only until the first suspension point. After suspension it resumes the coroutine in the thread that is fully determined by the suspending function that was invoked. The unconfined dispatcher is appropriate for coroutines which neither consume CPU time nor update any shared data (like UI) confined to a specific thread.
//
// On the other side, the dispatcher is inherited from the outer CoroutineScope by default. The default dispatcher for the runBlocking coroutine, in particular, is confined to the invoker thread, so inheriting it has the effect of confining execution to this thread with predictable FIFO scheduling.
// The unconfined dispatcher is an advanced mechanism that can be helpful in certain corner cases where dispatching of a coroutine for its execution later is not needed or produces undesirable side-effects, because some operation in a coroutine must be performed right away. The unconfined dispatcher should not be used in general code.
//
// The default dispatcher that is used when no other dispatcher is explicitly specified in the scope. It is represented by Dispatchers.Default and uses a shared background pool of threads.
//
// newSingleThreadContext creates a thread for the coroutine to run. A dedicated thread is a very expensive resource. In a real application it must be either released, when no longer needed, using the close function, or stored in a top-level variable and reused throughout the application.
47 changes: 47 additions & 0 deletions m1l5-coroutines/src/main/kotlin/homework/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Easy

`findNumberInList(numberToFind, listOfNumbers)` имитурет долгий поиска числа `numberToFind` в коллекции чисел `listOfNumbers`. Каждый
запрос в метод заставляет остановить текущий поток на 2 секунды и после найти слово. Возвращает `-1` если число найти не удалось или
возвращает самое число, если найдено.

`foundNumbers` собирает все найденные числа в коллекци.
В конце функции `dispatchers.main` в консоль выводится результат поиска:
> Your number $number found! // если функция `findNumberInList` вернула число != -1
> "Not found number $toFind || $toFindOther" если не смог найти одно из запрошенных чисел
**Задача**: необходимо реализовать код таким образом, чтобы поиск чисел в списке происходил паралелльно. Результатом решения будет
вывод на печать результат поиска каждого запрошенного числа.
Суммарное время выполнения текущего решения равна 4 секундам. В случае верного решения, скорость поиска должна увеличиться.

**Условие**: использование корутин

# Hard

В данном примере происходит поиск слова со всеми его значениями из открытого api `https://api.dictionaryapi.dev`

Данный api принимает в качестве аргумента `locale` и `слово` для поиска.
Например:

> https://api.dictionaryapi.dev/api/v2/entries/en/kill
найдет слово `kill` в английском словаре.

`HttpClient`, который реализует поиск не асинхронный.

Текущий код синхронно ищет каждое слово в цикле.

`FileReader` считывает из файла `words.txt` слова из которых формируется список уникальных слов для поиска.

`findWords` принимает список слов для поиска и в цикле обращается к методу, который с помощью api ищет значение данных слов, сохраняя
все это в объект `Dictionary`

`Dictionary` содержит слово и массив всех найденных значений
`Meaning` значение слова, содержит описание слова и примеры использования слова в реальных текстах.

**Задача**: Асинхронно реализовать поиск значений каждого слова, меняя только код, а не `HttpClient`

**Подсказки**:

1. Может произойти ошибка поиска слова в api, поиск других слов не должен прерваться. Данное слово можно просто проигнорировать;
2. Запрос в api можно реализовать в диспетчере `Dispatchers.IO`
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package homework.easy

fun findNumberInList(toFind: Int, numbers: List<Int>): Int {
Thread.sleep(2000L)
return numbers.firstOrNull { it == toFind } ?: -1
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package homework.easy

fun generateNumbers() = (0..10000).map {
(0..100).random()
}
20 changes: 20 additions & 0 deletions m1l5-coroutines/src/main/kotlin/homework/easy/main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package homework.easy

fun main() {
val numbers = generateNumbers()
val toFind = 10
val toFindOther = 1000

val foundNumbers = listOf(
findNumberInList(toFind, numbers),
findNumberInList(toFindOther, numbers)
)

foundNumbers.forEach {
if (it != -1) {
println("Your number $it found!")
} else {
println("Not found number $toFind || $toFindOther")
}
}
}
30 changes: 30 additions & 0 deletions m1l5-coroutines/src/main/kotlin/homework/hard/DictionaryApi.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package homework.hard

import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue
import homework.hard.dto.Dictionary
import okhttp3.Response

class DictionaryApi(
private val objectMapper: ObjectMapper = jacksonObjectMapper()
) {

fun findWord(locale: Locale, word: String): Dictionary { // make something with context
val url = "$DICTIONARY_API/${locale.code}/$word"
println("Searching $url")

return getBody(HttpClient.get(url).execute()).first()
}


private fun getBody(response: Response): List<Dictionary> {
if (!response.isSuccessful) {
throw RuntimeException("Not found word")
}

return response.body?.let {
objectMapper.readValue(it.string())
} ?: throw RuntimeException("Body is null by some reason")
}
}
12 changes: 12 additions & 0 deletions m1l5-coroutines/src/main/kotlin/homework/hard/HttpClient.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package homework.hard

import okhttp3.OkHttpClient
import okhttp3.Request

object HttpClient : OkHttpClient() {
fun get(uri: String) =
Request.Builder().url(uri).build()
.let {
newCall(it)
}
}
6 changes: 6 additions & 0 deletions m1l5-coroutines/src/main/kotlin/homework/hard/Locale.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package homework.hard

enum class Locale(val code: String) {
EN("en_US"),
RU("ru")
}
3 changes: 3 additions & 0 deletions m1l5-coroutines/src/main/kotlin/homework/hard/constants.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package homework.hard

internal const val DICTIONARY_API = "https://api.dictionaryapi.dev/api/v2/entries"
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package homework.hard.dto

import com.fasterxml.jackson.annotation.JsonIgnoreProperties

@JsonIgnoreProperties(ignoreUnknown = true)
data class Dictionary(
val word: String,
val meanings: List<Meaning>
)
14 changes: 14 additions & 0 deletions m1l5-coroutines/src/main/kotlin/homework/hard/dto/Meaning.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package homework.hard.dto

import com.fasterxml.jackson.annotation.JsonIgnoreProperties

@JsonIgnoreProperties(ignoreUnknown = true)
data class Meaning(
val definitions: List<Definition>
)

@JsonIgnoreProperties(ignoreUnknown = true)
data class Definition(
val definition: String,
val example: String? = ""
)
25 changes: 25 additions & 0 deletions m1l5-coroutines/src/main/kotlin/homework/hard/main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package homework.hard

import java.io.File

fun main() {
val dictionaryApi = DictionaryApi()
val words = FileReader.readFile().split(" ", "\n").toSet()

val dictionaries = findWords(dictionaryApi, words, Locale.EN)

dictionaries.map { dictionary ->
print("For word ${dictionary.word} i found examples: ")
println(dictionary.meanings.map { definition -> definition.definitions.map { it.example } })
}
}

private fun findWords(dictionaryApi: DictionaryApi, words: Set<String>, locale: Locale) = // make some suspensions and async
words.map {
dictionaryApi.findWord(locale, it)
}

object FileReader {
fun readFile(): String =
File(this::class.java.classLoader.getResource("words.txt")?.toURI() ?: throw RuntimeException("Can't read file")).readText()
}
Loading

0 comments on commit 3b998a5

Please sign in to comment.