JobCancellationException UndispatchedCoroutine error in Kotlin - android

I'm still new to coroutines (but already love them lots). I've got this error (and crash) and not sure what it means. What is an UndispatchedCoroutine? how it gets cancelled?
kotlinx.coroutines.JobCancellationException: UndispatchedCoroutine was cancelled; job="coroutine#8":UndispatchedCoroutine{Cancelled}#fe7d397
Edit:
To give some context, I'm using an Actor to execute the handling of the message in a coroutine:
#OptIn(kotlinx.coroutines.ObsoleteCoroutinesApi::class)
internal inner class HandlerFPMsg : Handler() {
private val msgActor = coroutineScope.actor<Pair<Int, Long>>(Dispatchers.Default, capacity = Channel.UNLIMITED) {
for(msg in channel)
handleMessageWorker(msg.first, msg.second)
}
override fun handleMessage(msg: Message) {
msgActor.offer(Pair(msg.what, msg.data.getLong("ID",-1L)))
super.handleMessage(msg)
}
private suspend fun handleMessageWorker(what: Int, id: Long) {
if (what != 0x92) Log.d("Messenger123", "Message Received: [" + what.toString(16) + "]")
when (what) {
MsgConstants.MSG_CONSTANT1 -> {
someFunction()
Log.e(TAG, "${e.message}")
}
MsgConstants.MSG_CONSTANT2 -> {
if (id != lastId) return
// -----------------------
if (aSuspendingFunction()) {
msgService?.let { msgr -> aClass.sendAmsg(msgr) }
} else {
// some comment
someFunction()
}
}
}
}
}
if i wrap handleMessageWorker(msg.first, msg.second) with a try catch block seems to work (but i don't know how correct would be the code...)

This is not a definitive answer, but I find that comments are too short for that.
Judging by the fact that this is an inner class, and that you're creating your actor using coroutineScope from the outside, my guess would be that your coroutineScope gets terminated, which also terminates your actor. This is the correct behavior of structured concurrency.
Question is: why your coroutine scope gets terminated.
Two main possibilities:
It's bound to lifecycle of another object. If you're launching this actor from scope of an Android Activity, coroutineScope may get terminated once the activity closes.
Another piece of code that uses the same scope throws an exception. This will also cause scope to terminate, unless it's supervisorScope

Related

Is it necessary to use .flowOn(Dispatchers.IO) inside repository class?

I'm trying to implement One Tap, so I have created this function:
override fun oneTapSgnInWithGoogle() = flow {
try {
emit(Result.Loading)
val result = oneTapClient.beginSignIn(signInRequest).await()
emit(Result.Success(result))
} catch (e: Exception) {
emit(Result.Error(e.message))
}
}
//.flowOn(Dispatchers.IO)
And some programmer told me that I need to add .flowOn(Dispatchers.IO) to the above function, so it can be correct. My code work correct without it. Here is how I call this function in the ViewModel:
fun oneTapSignIn() = viewModelScope.launch {
repo.oneTapSignInWithGoogle().collect { response ->
oneTapSignInResponse = response
}
}
Is it really necessary to do that? I'm really confused.
You're calling beginSignIn which returns a Task, so it does its own stuff in the background. Now Task.await is suspending, not blocking, so it won't block the current thread while waiting for the task.
Therefore, the body of your flow doesn't contain any blocking stuff, so there is no reason to use flowOn(Dispatchers.IO) here.

Android Kotlin Co-routine Exceptions

I'm trying to use Kotlin co-routines to trigger a network request and handle Exceptions. I've looked at a lot of tutorials on co-routines but I'm really struggling to relate what i know to the problem i have.
The problem
Trying to get an exception to be caught in the View, but no Exception is thrown from the ViewModel so the application crashes.
The Code
My Android app has three layers that relate to this issue. I have the View, ViewModel and a Service layer.
In the service layer, request.execute() can throw a UserAuthException.
MyView (View)
private val mViewModel: MyViewModel by viewModels()
private fun getFileId() {
try {
mViewModel.requestFileId()
} catch (e: UserAuthException) {
Timber.i(e)
}
}
MyViewModel (ViewModel)
private val apiService = MyApiService()
fun requestFileId() {
viewModelScope.launch {
ApiService.requestFileId()
}
}
MyApiService (Service Layer)
suspend fun requestFileId(): FileId = withContext(Dispatchers.IO) {
request.execute()
}
Things that i have looked at
I have played around with CoroutineExceptionHandlers, supervisorJobs with no luck, but without the fundamental knowledge of how these things work I'm not really making any progress.
Any help would be appreciated, thanks.
fun requestFileId() {
viewModelScope.launch {
ApiService.requestFileId()
}
}
This is not a suspendable function. It launches a concurrent coroutine and returns right away. Clearly, calling requestFileId() will never throw an exception.
Launching a coroutine is just like starting another thread, it introduces concurrency to your code. If your current code hopes to stay non-concurrent while observing the results of suspendable functions, you may be looking at significant architectural changes to the application to make it behave correctly under concurrency.
In your model, change it to something like this:
fun requestFileId() {
viewModelScope.launch {
try {
ApiService.requestFileId()
} catch (e: Exception) {
// inform your view
}
}
}

Make part of coroutine continue past cancellation

I have a file managing class that can save a big file. The file manager class is an application singleton, so it outlives my UI classes. My Activity/Fragment can call the save suspend function of the file manager from a coroutine and then show success or failure in the UI. For example:
//In MyActivity:
private fun saveTheFile() = lifecycleScope.launch {
try {
myFileManager.saveBigFile()
myTextView.text = "Successfully saved file"
} catch (e: IOException) {
myTextView.text = "Failed to save file"
}
}
//In MyFileManager
suspend fun saveBigFile() {
//Set up the parameters
//...
withContext(Dispatchers.IO) {
//Save the file
//...
}
}
The problem with this approach is that I don't want the save operation to be aborted if the Activity is finished. If the activity is destroyed before the withContext block gets going, or if the withContext block has any suspension points in it, then saving will not be completed because the coroutine will be canceled.
What I want to happen is that the file is always saved. If the Activity is still around, then we can show UI updates on completion.
I thought one way to do it might be to start a new coroutineScope from the suspend function like this, but this scope still seems to get cancelled when its parent job is cancelled.
suspend fun saveBigFile() = coroutineScope {
//...
}
I thought another alternative might be to make this a regular function that updates some LiveData when it's finished. The Activity could observe the live data for the result, and since LiveData automatically removes lifecycle observers when they're destroyed, the Activity is not leaked to the FileManager. I'd like to avoid this pattern if the something less convoluted like the above can be done instead.
//In MyActivity:
private fun saveTheFile() {
val result = myFileManager.saveBigFile()
result.observe(this#MyActivity) {
myTextView.text = when (it) {
true -> "Successfully saved file"
else -> "Failed to save file"
}
}
}
//In MyFileManager
fun saveBigFile(): LiveData<Boolean> {
//Set up the parameters
//...
val liveData = MutableLiveData<Boolean>()
MainScope().launch {
val success = withContext(Dispatchers.IO) {
//Save the file
//...
}
liveData.value = success
}
return liveData
}
You can wrap the bit that you don't want to be cancelled with NonCancellable.
// May cancel here.
withContext(Dispatchers.IO + NonCancellable) {
// Will complete, even if cancelled.
}
// May cancel here.
If you have code whose lifetime is scoped to the lifetime of the whole application, then this is a use case for the GlobalScope. However, just saying GlobalScope.launch is not a good strategy because you could launch several concurrent file operations that may be in conflict (this depends on your app's details). The recommended way is to use a globally-scoped actor, in the role of an executor service.
Basically, you can say
#ObsoleteCoroutinesApi
val executor = GlobalScope.actor<() -> Unit>(Dispatchers.IO) {
for (task in channel) {
task()
}
}
And use it like this:
private fun saveTheFile() = lifecycleScope.launch {
executor.send {
try {
myFileManager.saveBigFile()
withContext(Main) {
myTextView.text = "Successfully saved file"
}
} catch (e: IOException) {
withContext(Main) {
myTextView.text = "Failed to save file"
}
}
}
}
Note that this is still not a great solution, it retains myTextView beyond its lifetime. Decoupling the UI notifications from the view is another topic, though.
actor is labeled as "obsolete coroutines API", but that's just an advance notice that it will be replaced with a more powerful alternative in a future version of Kotlin. It doesn't mean it's broken or unsupported.
I tried this, and it appears to do what I described that I wanted. The FileManager class has its own scope, though I suppose it could also be GlobalScope since it's a singleton class.
We launch a new job in its own scope from the coroutine. This is done from a separate function to remove any ambiguity about the scope of the job. I use async
for this other job so I can bubble up exceptions that the UI should respond to.
Then after launch, we await the async job back in the original scope. await() suspends until the job is completed and passes along any throws (in my case I want IOExceptions to bubble up for the UI to show an error message). So if the original scope is cancelled, its coroutine never waits for the result, but the launched job keeps rolling along until it completes normally. Any exceptions that we want to ensure are always handled should be handled within the async function. Otherwise, they won't bubble up if the original job is cancelled.
//In MyActivity:
private fun saveTheFile() = lifecycleScope.launch {
try {
myFileManager.saveBigFile()
myTextView.text = "Successfully saved file"
} catch (e: IOException) {
myTextView.text = "Failed to save file"
}
}
class MyFileManager private constructor(app: Application):
CoroutineScope by MainScope() {
suspend fun saveBigFile() {
//Set up the parameters
//...
val deferred = saveBigFileAsync()
deferred.await()
}
private fun saveBigFileAsync() = async(Dispatchers.IO) {
//Save the file
//...
}
}

How should I restart/retry Kotlin coroutine after exception in Android

I am really happy that I switched my long running tasks, which constantly produce results to UI thread to coroutines. It improved performance and decreased memory usage by 3 times and all memory leaks disappeared compared to AsyncTask or regular Threads in Android.
The only problem remains is that, I don't know how should I restart my long running operation after exception has occurred at some time...
I feel I did not understand exception handling in coroutines at all after reading tons of article. Let me know how can I achieve desired behaviour.
I have coroutine scope in fragment(will move to VM in near future).
​
lateinit var initEngineJob: Job
override val coroutineContext: CoroutineContext
get() = initEngineJob + Dispatchers.Main
​
Long running task with async/await.
​
fun initWorkEngineCoroutine()
{
launch {
while(true) {
val deferred = async(Dispatchers.Default) {
getResultsFromEngine()
}
val result = deferred.await()
if (result != null) {
//UI thread
draw!!.showResult(result)
}
}
}
}
fun getResultsFromEngine() :Result? {
result = // some results from native c++ engine, which throws exception at some times
return result
}
i don't know where should I put try catch. I tried to surround deferred.await() with try catch, but I could not call same method in catch block to retry long running task. I tried SupervisorJob(), but no success either. I still could not call initWorkEngineCoroutine() again and start new coroutine...
Help to solve this issue finally :)
You should treat your code as linear imperative and try/catch where it makes the most logical sense in your code. With this mindset, your question is probably less about coroutines and more about try/catch retry. You might do something like so:
fun main() {
GlobalScope.launch {
initWorkEngineCoroutine()
}
}
suspend fun initWorkEngineCoroutine() {
var failures = 0
val maxFailures = 3
while(failures <= maxFailures) {
try {
getResultsFromEngine()?.let {
draw!!.showResult(it)
}
} catch (e: Exception) {
failures++
}
}
}
// withContext is like async{}.await() except an exception occuring inside
// withContext can be caught from inside the coroutine.
// here, we are mapping getResultFromEngine() to a call to withContext and
// passing withContext the lambda which does the work
suspend fun getResultsFromEngine() :Result? = withContext(Dispatchers.Default) {
Result()
}
I've included some logic to prevent infinite loop. It's probably not going to fit your requirements, but you might consider some sort of thing to prevent an issue where exceptions are raised immediately by getResultsFromEngine() and end up causing an infinite loop that could result in unexpected behavior and potential stackoverflow.

Kotlin corountines : Note: end time exceeds epoch:

Combine runBlocking and withContext seems to dispatch the message
Note: end time exceeds epoch:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
runBlocking {
withContext(DefaultDispatcher) {
null
}
}
}
}
I use many coroutines like this and the logcat is spammed, any idea to avoid this ? Another way to do this, for example :
var projects: List<ProjectEntity>? = runBlocking {
withContext(DefaultDispatcher) {
//Get the ProjectEntity list
}
}
projects?.let {
onResult(projects)
}
EDIT
I try something based on your comments (thank you), but I can't get a similar result as my example above :
Log.d("Coroutines", "getMostRecent start")
var localeProject: ProjectEntity? = null
launch {
withContext(CommonPool) {
Log.d("Coroutines", "getRecentLocaleProject")
localeProject = getRecentLocaleProject()
}
}
Log.d("Coroutines", "check localeProject")
if (localeProject != null) {
//Show UI
}
In Logcat :
D/Coroutines: getMostRecent start
D/Coroutines: check localeProject
D/Coroutines: getRecentLocaleProject
I want to separate async and sync stuff, there is no way like this ? I really want to avoid all the callbacks things in my repositories when possible.
Markos comment is right, you should not block the UI thread.
You should use launch or async and use withContext to switch back to the UI thread.
You find some examples here: https://github.com/Kotlin/kotlinx.coroutines/blob/master/ui/coroutines-guide-ui.md#structured-concurrency-lifecycle-and-coroutine-parent-child-hierarchy
class MainActivity : ScopedAppActivity() {
fun asyncShowData() = launch { // Is invoked in UI context with Activity's job as a parent
// actual implementation
}
suspend fun showIOData() {
val deferred = async(Dispatchers.IO) {
// impl
}
withContext(Dispatchers.Main) {
val data = deferred.await()
// Show data in UI
}
}
}
Be aware, that the example uses the new coroutine API (>0.26.0), that renamed the Dispatchers. So Dispatchers.Main corresponds to UI in older versions.
var localeProject: ProjectEntity? = null
launch {
withContext(CommonPool) {
localeProject = getRecentLocaleProject()
}
}
if (localeProject != null) {
//Show UI
}
I want to separate async and sync stuff, there is no way like this ?
When you launch a coroutine, semantically it's like you started a thread. Intuition tells you that you can't expect localeProject != null just after you've started the thread that sets it, and this is true for the coroutine as well. It's even stronger: you are guaranteed not to ever see localeProject != null because launch only adds a new event to the event loop. Until your current method completes, that event won't be handled.
So you can forget about top-level vals initialized from async code. Not even lateinit vars can work because you have no guarantee you'll see it already initialized. You must work with the loosest kind: nullable vars.

Categories

Resources