While I appreciate the compactness of code with some of the coroutines constructs, I still find that it's very easy to run heavy operations on the UI thread. For example, let's say when I click a button in Swing, I want to run some IO to read the disk content and then display the file list in a panel:
button.addActionListener { ev ->
GlobalScope.launch(Dispatchers.Swing) {
myPanel.setList(withContext(Dispatchers.Default) {
FileUtils.getLocalFileSystemContent()
})
}
}
Where FileUtils.getLocalFileSystemContent() is a heavy IO-bound operation. If I make a mistake and pass Dispatchers.Swing to that withContext call, that operation runs on the Swing UI thread, with no compile time or run time warning.
It would be great to have some kind of a mechanism to annotate a Java / Kotlin method to only be allowed to run off UI thread (without peppering the explicit checks in those methods themselves), and then detect places that do run such methods on UI-bound context. Ideally at compile time.
In our team we adopted the following convention: Any public function which needs a specific thread or thread-pool must be marked suspend and use withContext to make sure its content is executed correctly.
Example:
// This function can be called from any thread (including UI)
fun foo() { ... }
// Body of this function may block
suspend fun offUIFunction() = withContext(Dispatchers.IO) { ... }
// Body of this function needs to be executed on Swing
suspend fun uiFunction() = withContext(Dispatchers.Swing) { ... }
This convention brings the following benefits:
withContext won't perform unnecessary thread switch.So in our codebase the code example you provided would like like this:
// Dispatchers.Swing would be the default in Swing views
launch {
// we use subscriptions, which helps to prevent memory leaks, since it will be automaticaly removed at the end of the life of the coroutine scope
button.openActionSubscription().consumeEach { ev ->
// loadLocalFileSystemContent here would have the responsibility to perform necessary thread switches
myPanel.setList(loadLocalFileSystemContent())
}
}
Most helpful comment
In our team we adopted the following convention: Any public function which needs a specific thread or thread-pool must be marked
suspendand usewithContextto make sure its content is executed correctly.Example:
This convention brings the following benefits:
withContextwon't perform unnecessary thread switch.So in our codebase the code example you provided would like like this: