如何取消withContext中运行的协程?

时间:2020-05-29 11:29:48

标签: android kotlin coroutine kotlin-coroutines withcontext

我的存储库定义如下。

class StoryRepository {
    private val firestore = Firebase.firestore

    suspend fun fetchStories(): QuerySnapshot? {
        return try {
            firestore
                .collection("stories")
                .get()
                .await()
        } catch(e: Exception) {
            Log.e("StoryRepository", "Error in fetching Firestore stories: $e")
            null
        }
    }
}

我也有这样的ViewModel。

class HomeViewModel(
    application: Application
) : AndroidViewModel(application) {
    private var viewModelJob = Job()
    private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
    private val storyRepository = StoryRepository()

    private var _stories = MutableLiveData<List<Story>>()
    val stories: LiveData<List<Story>>
        get() = _stories

    init {
        uiScope.launch {
            getStories()
        }
        uiScope.launch {
            getMetadata()
        }            
    }

    private suspend fun getStories() {
        withContext(Dispatchers.IO) {
            val snapshots = storyRepository.fetchStories()
            // Is this correct?
            if (snapshots == null) {
                cancel(CancellationException("Task is null; local DB not refreshed"))
                return@withContext
            }
            val networkStories = snapshots.toObjects(NetworkStory::class.java)
            val stories = NetworkStoryContainer(networkStories).asDomainModel()
            _stories.postValue(stories)
        }
    }

    suspend fun getMetadata() {
        // Does some other fetching
    }

    override fun onCleared() {
        super.onCleared()
        viewModelJob.cancel()
    }
}

如您所见,有时StoryRepository().fetchStories()可能会失败并返回null。如果返回值为null,则在检查snapshots是否为null块之后,我不想继续进行后续操作。因此,我想取消该特定的协程(运行getStories()的协同程序,而不取消另一个协程(运行getMetadata()的协程)。如何实现这一目标,return-是withContext的不当行为?

1 个答案:

答案 0 :(得分:2)

尽管您的方法是正确的,但您始终可以进行一些改进以使其更简单或更惯用(特别是当您对自己的代码不满意时)。

这些只是您可能需要考虑的一些建议:

您可以使用Kotlin范围函数,或更具体地说,使用let函数,如下所示:

private suspend fun getStories() = withContext(Dispatchers.IO) {
    storyRepository.fetchStories()?.let { snapshots ->
        val networkStories = snapshots.toObjects(NetworkStory::class.java)
        NetworkStoryContainer(networkStories).asDomainModel()
    } ?: throw CancellationException("Task is null; local DB not refreshed")
}

这样,您将返回数据或如果CancellationException抛出null

在ViewModel中使用协程时,如果将此依赖项添加到gradle文件中,则可以使用CoroutineScope:

androidx.lifecycle:lifecycle-viewmodel-ktx:{version}

因此您可以使用viewModelScope来构建协程,该协程将在主线程上运行:

init {
    viewModelScope.launch {
        _stories.value = getStories()
    }

    viewModelScope.launch {
        getMetadata()
    }
}

由于Job具有生命周期意识,因此您可以忘记在onCleared期间取消其viewModelScope

现在剩下要做的就是使用try-catch块或invokeOnCompletion构建器返回的Job上应用的launch函数来处理异常。 / p>