The JavaScript worker API is exposed to Kotlin and I can easily create web workers with it. Can I also write the worker’s code in Kotlin?
Yes, you can. Why not?
Thank you for your response! After some more thinking I think my problems are the following:
- I don’t know how to put my code into the right context. How can I tell the compiler that
self
has a methodaddEventListener
and a methodpostMessage
? - Would my project then have two
main
functions, one for the main web page and one for the worker thread? If so, how can I configure IntelliJ to compile the two main functions into two separate output files?
As I’m typing this, I think that I could probably solve problem 1 with inline JavaScript that just expects these variables to be set.
I don’t know how to put my code into the right context. How can I tell the compiler that self has a method addEventListener and a method postMessage?
external val self: ServiceWorkerGlobalScope
Would my project then have two main functions, one for the main web page and one for the worker thread? If so, how can I configure IntelliJ to compile the two main functions into two separate output files?
There are number of ways. First, you don’t necessarily need main
function, the following trick also works fine:
private val dummy = start()
fun start() {
// do something
}
Second, you don’t need main
function in non-worker script at all. You can define a function and call it, say, from another <script>
tag or from <body onload="...">
.
Third, you can put worker in a separate module.
Maybe this is a bit of an old thread, but I found the perfect solution for me:
external val self: ServiceWorkerGlobalScope
fun runAsync(run: () -> Unit) {
var worker: Worker? = null
fun terminate() = worker!!.terminate()
worker = Worker(
URL.createObjectURL(
Blob(
arrayOf(
{
self.onmessage = {
run()
terminate()
}
}()
),
jsObject { type = "text/javascript" }
)
)
).apply {
postMessage(null)
}
}
This function can be used like this:
runAsync {
println("hello from async")
}
This can be improved as I do get a reference error that “kotlin isn’t found”, but the code successfully executes, so I think we can just catch this exception.
Anyways, hope this helps you!
Edit: actually, this doesn’t work perfectly… We need a way to pass in parameters to make it thread safe
Edit 2: Okay, I’ve been looking into it, but it’s harder than expected. I tried converting GitHub - boneVidy/inline-webworker: inline webworker this typescript library to Kotlin, but I ran into some issues. For instance, the tasks function task: Function
needs a reference to self: InlineWorker
so it can run postMessage (compared to the TS I see it like this typealias Function = (self: InlineWebWorker) -> Unit
), but when I provide task()
with this
to put in the blob array arrayOf({ task(self = this) }())
, then the task function already gets executed on this thread instead of on the second thread like how it’s supposed to work.
Basically, the only way my example above works is because I assign the function to self.onmessage
and thus the function only gets executed when onmessage
fires on the second thread. So yeah, any ideas would be welcome.
Edit 3: Added terminate() to example
Edit 4: I don’t think this is working at all as my page still gets stuck executing it…
There is this GitHub - korlibs/kworker: Portable Workers for Multiplatform Kotlin 1.3 library but it’s not finished and doesn’t work on browser only without removing the cluster dependency in the code. It works, but only for small stuff. See, only certain types are allowed to be pushed as messages, only Ints, Doubles, Strings and ByteArrays are allowed. So to push a large number of objects, they need to be converted to JSON or something, making it slower than simply running it on the main thread… There is a way to use transferable objects in JS that somehow passes the reference to an object in a message, but KWorker doesn’t support this I think. Plus this is hard already in JS, let alone Kotlin/JS.
This just needs an easy to use lbrary or something as it’s getting ridiculous how difficult this is.
Maybe someone can make a wrapper for comlink: David East - Simplify Web Worker code with Comlink
It looks promising, would be awesome if it could work in kotlin It even allows multiple threads to access the same object! I’ll give it a try when/if I have time.
NVM, comlink is breaking my brain… with all the async stuff going on, I’m not sure if it’s even compatible with kotlin.
Greenlet GitHub - developit/greenlet: 🦎 Move an async function into its own thread. also looks interesting. Looks more like coroutines, however I’m not sure if it also destroys Object instances to just their parameters like workers do in postMessage.
I’m reviving this thread since it’s the top google result for “KotlinJS WebWorkers”: I’ve created a demo that shows how to use webworkers in a Kotlin/JS project: GitHub - ethanmdavidson/KotlinJSWebWorkerDemo: A simple demo that shows how WebWorkers can be used in Kotlin/JS
It works by putting the worker in a separate module, as suggested by Alexey.Andreev
Anyone succeeded in implementing inline web workers? I tried this, but it does not work.
class WebWorkerTest {
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun testWebWorker() = runTest {
println("Starting")
val messageFlow = MutableStateFlow<String?>(null)
val script = {
self.onmessage = {
println("Starting web worker")
self.postMessage(it.data)
}
}
val worker = Worker(
URL.createObjectURL(
Blob(
blobParts = arrayOf(script),
options = BlobPropertyBag("text/javascript")
)
)
)
worker.postMessage("test message")
worker.onmessage = {
messageFlow.value = it.data.toString()
Unit
}
val message = messageFlow.awaitNotNull() // custom function that suspends until the flow collects a non-null value
println("Finished with: $message")
}
}
Here’s an article: multithreaded-web-applications-in-kotlin-with-web-workers
Hope it helps! It’s the second result when you search for “Kotlin webworker” so I assume you’ve already found it when you searched.
EDIT: Oops! Missed that there’s no inline example in that one… My bad.
While the article is surely a good start. I’m explicitly refering to an inlined web worker to avoid creating a module for each worker.
I got it running :-). My mistake was to use a function in the blob instead of a string of the function
external val self: DedicatedWorkerGlobalScope
class WebWorkerTest {
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun testWebWorker() = runTest {
println("Started test")
val messageFlow = MutableStateFlow<String?>(null)
val script: (MessageEvent) -> Unit = {
self.postMessage(it.data)
}
val worker = Worker(
URL.createObjectURL(
Blob(
blobParts = arrayOf("self.onmessage = $script"),
options = BlobPropertyBag("text/javascript")
)
)
)
worker.postMessage("test message")
worker.onmessage = {
messageFlow.value = it.data.toString()
Unit
}
val message = messageFlow.awaitNotNull()
println("Finished with: $message")
}
}
Hey, author of the blog above. I also tried doing this, but I ran into the following problem, which made sense. The Kotlin/JS stdlib is not loaded with any of the web workers and as such you get a lot of problems. I can’t reproduce the above example in the browser context for instance, because it can not find Unit
.
external val self: DedicatedWorkerGlobalScope
fun createWorker(content: (MessageEvent) -> dynamic): Worker {
val parts = arrayOf("self.onmessage = ", content.toString())
val blob = Blob(parts, options = BlobPropertyBag("text/javascript"))
val url = URL.createObjectURL(blob)
return Worker(url)
}
fun main() {
val worker = createWorker {
self.postMessage(it.data)
js("undefined") // this one is needed because otherwise it returns Unit, which doesn't exist in this context
}
worker.onmessage = {
println(it.data)
}
worker.postMessage("Foo")
}
It works, but I don’t see any benefit without the Kotlin/JS library loaded. Also, the whole idea of ‘closures’ is to ‘close over’ code in the scope above it. This is impossible with these web-workers like this.
You are right. The inlined web worker creates a scope without any initiated libraries. That makes it kinda useless unfortunately.
I think we should be able to import all relevant modules in the inlined web worker scope before doing that actual work. I guess kotlinJS is automatically adding this to any transpiled module? However, I don’t know what we exactly need