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

How do I cancel a coroutine run inside a 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() 可能会失败,return null。如果 return 值为 null,我不想在检查 snapshotsnull 块后继续进行后续操作。因此,我想取消那个特定的协程(运行 getStories() 的协程而不取消另一个协程(运行 getMetadata() 的协程)。我如何实现这一点并且是 return-从 withContext 中学习是一种不良做法?

虽然您的方法是正确的,但您始终可以进行一些改进以使其更简单或更符合习惯(尤其是当您对自己的代码不满意时)。

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

您可以使用 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")
}

如果 null.

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

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

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

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

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

    viewModelScope.launch {
        getMetadata()
    }
}

您可以忘记在 onCleared 期间取消它的 Job,因为 viewModelScope 是生命周期感知的。

现在您要做的就是使用 try-catch 块或 launch 构建器返回的 Job 上应用的 invokeOnCompletion 函数来处理异常.