Kotlin协程管道与Data Binding遇ClosedReceiveChannelException视图不更新
Great question! The root issue here lies in how Kotlin coroutines handle ClosedReceiveChannelException—this exception is a subclass of CancellationException, which carries a special meaning for coroutines: it signals that the coroutine should be cancelled.
What's happening differently?
When you throw a regular IllegalStateException, your catch block runs in an active coroutine—the coroutine isn't marked for cancellation, so any updates to your text property (backed by LiveData/ObservableField for Data Binding) work as expected.
But when processedTextPipe.receive() throws ClosedReceiveChannelException, even though you catch Throwable, the coroutine is already marked as cancelled. Some Data Binding updates might fail to propagate in a cancelled coroutine context, especially if your property updates rely on the coroutine's active state (or if the cancellation subtly interferes with how Data Binding observes changes).
Fixes to try:
1. Run the update in a new active coroutine
Since the original coroutine is cancelled, spin up a new one using viewModelScope to ensure the update happens in an active context:
fun onProcessStart(cachedImageId: String) = viewModelScope.launch { text = "Started for image: $cachedImageId" try { val extractedTextPipe = extractText(cachedImageId) val processedTextPipe = processText(extractedTextPipe) val processedText = processedTextPipe.receive() text = "Finished for image: $cachedImageId" } catch (t: Throwable) { Log.d("TEST", "${t.message} $t") // Launch a new active coroutine to update the text viewModelScope.launch { text = "Failed for image: $cachedImageId with code: ${t.message}" } } }
2. Force the update on the Main thread directly
Bypass the coroutine context entirely by using a Handler to post the update to the Main thread (where Data Binding operates):
import android.os.Handler import android.os.Looper fun onProcessStart(cachedImageId: String) = viewModelScope.launch { text = "Started for image: $cachedImageId" try { val extractedTextPipe = extractText(cachedImageId) val processedTextPipe = processText(extractedTextPipe) val processedText = processedTextPipe.receive() text = "Finished for image: $cachedImageId" } catch (t: Throwable) { Log.d("TEST", "${t.message} $t") Handler(Looper.getMainLooper()).post { text = "Failed for image: $cachedImageId with code: ${t.message}" } } }
3. Handle channel closure explicitly before cancellation
If you control the channel logic, you can check if the channel is closed before calling receive(), avoiding the cancellation exception altogether:
fun onProcessStart(cachedImageId: String) = viewModelScope.launch { text = "Started for image: $cachedImageId" try { val extractedTextPipe = extractText(cachedImageId) val processedTextPipe = processText(extractedTextPipe) // Check if channel is open before receiving if (!processedTextPipe.isClosedForReceive) { val processedText = processedTextPipe.receive() text = "Finished for image: $cachedImageId" } else { text = "Failed for image: $cachedImageId with code: Channel closed" } } catch (t: Throwable) { Log.d("TEST", "${t.message} $t") text = "Failed for image: $cachedImageId with code: ${t.message}" } }
Any of these approaches should ensure your Data Binding updates trigger correctly even when handling channel closure exceptions.
内容的提问来源于stack exchange,提问作者Cativail

