CountDownLatch not freeing thread

2019-07-20 00:17发布

问题:

I have a method, which loading image from Firebase Storage. It's called in background thread, and I need to block it, until image is loaded (to avoid callback hell). Here is the code (in Kotlin)

override fun fromNet(): Bitmap? {
    Log.wtf(TAG, "$name loading from firebase")
    var result: Bitmap? = null
    val lock = CountDownLatch(1)
    try {
        FirebaseStorage.getInstance().getReferenceFromUrl(FIRE_STORAGE).child(ctx.getKGL().famkey)
            .child(name).getBytes(524288L)
            .addOnFailureListener {
                Log.wtf(TAG, "$name load failure")
                lock.countDown()
            }
            .addOnSuccessListener { bytes ->
                Log.wtf(TAG, "$name loaded")
                val b = BitmapFactory.decodeByteArray(bytes, 0, bytes.size).scale(ctx.dip(64))
                result = b
                lock.countDown()
                ctx.saveToCache(name, b)
            }
            .addOnCompleteListener {
                Log.wtf(TAG, "on complete")
                lock.countDown()
            }
    } catch (ignored: NullPointerException) { lock.countDown() }
    lock.await()
    return result
}

But thread stays blocked forever

Logcat:

A/MemberPhoto: xvd6z67gZfMCLG4c9mkGXKe9ML53 load failure
A/MemberPhoto: on complete

UPD: May the cause be, that Firebase code is Java, and my code is in Kotlin?

回答1:

If you want to be sure that lock.await() won't make your current thread wait forever, you need to ensure that lock.countDown() is called whatever happens so here you should surround with a try/finally block the code of your listeners in order to call lock.countDown() within a finally block.

Indeed otherwise with your current code if for example BitmapFactory.decodeByteArray(bytes, 0, bytes.size).scale(ctx.dip(64)) fails, lock.countDown() will never be called which will make the thread calling lock.await() wait forever.

For example in case of a success the code of your listener should rather be:

.addOnSuccessListener { bytes ->
    try {
        Log.wtf(TAG, "$name loaded")
        val b = BitmapFactory.decodeByteArray(bytes, 0, bytes.size).scale(ctx.dip(64))
        result = b
    } finally {
        lock.countDown()
    }        
    ctx.saveToCache(name, b)
}