簡體   English   中英

協程中的非掛起函數是否仍然在另一個線程上運行?

[英]Does a non suspend function inside a coroutine still run on another thread?

我用ROOM打開了一個協程將數據插入到我的數據庫中,插入方法被掛起。 當我在協程中單獨執行此操作時,一切正常。 然后我想獲取一個位圖並將其存儲在本地,並將該本地路徑添加到數據庫對象。 聽起來很容易,所以我開始構建 url 連接,它在協程或異步任務之外無法工作,所以我把它放在同一個協程中,就像我將數據插入房間數據庫時一樣,我不得不做Dispatchers.IO 使其工作。 位圖下載並存儲在外部存儲中,但是數據沒有存儲到房間數據庫中,它一直給我一個異常“工作被取消”。 所以我刪除了房間呼叫的掛起修飾符,它起作用了......

所以我的問題是,當我添加其他 url 連接輸入流時,為什么它不能與掛起功能一起使用? 是連接本身、Dispatchers.IO 還是其他? 並且房間插入方法現在不是掛起方法,它是否仍然在 IO 線程上運行,而是作為一個阻塞函數,而不是在主 UI 線程上? 謝謝你的幫助

附上協程請求和DAO

        viewModelScope.launch(Dispatchers.IO) {
            try {
                val inputStream = url.openConnection().getInputStream()
                val image = BitmapFactory.decodeStream(inputStream)
                val imagesPath = File("$storagePath/RecordImages/")
                imagesPath.mkdirs()
                val imageFile = File(imagesPath, imageName)
                val outputStream = FileOutputStream(imageFile)
                image.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
                outputStream.flush()
                outputStream.close()
                val record = Record(recordPosition, title, artist, imageName)
                recordRepository.insert(record)
            } catch (exception: Exception) {
                exception.localizedMessage?.let { Log.d(SearchResultsAdapter.TAG, it) }
            }
        }

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertRecord(word: Record)

通過在DAO函數上使用 suspend 關鍵字,您只是對房間編譯器說這項工作將異步發生,因此沒有機會在主線程或沒有協程的情況下完成它。

當你使用suspend關鍵字時,房間編譯器知道這個函數只能從協程中調用,所以不管你使用哪個調度程序,房間都會在內部將此操作移動到Dispacthers.IO

可能通過使用withContext(Dispachters.IO)

根據此方法的文檔,如果父協程被取消,它將拋出CancellationExeption ,所以我認為您啟動的協程在返回insert之前被取消。

當您不使用suspend時,我認為房間無法使用正確的調度程序/使用協程功能來為您執行此操作,因為此函數可能會或可能不會從協程調用,因此withContext(Dispacters.IO)將不在生成的代碼,這就是你沒有得到異常的原因。

為了更深入的了解,你可以看這個

正確的協程行為是,當取消它們運行的​​ CoroutineScope 時,所有當前正在運行的協程都被取消。 viewModelScope專門用於在關聯 ViewModel 被銷毀時應取消的協程。

當協程被取消時,協程中的一個函數要被取消,它必須配合取消 大多數表現良好的掛起函數都應該這樣做,事實上,Room 數據庫庫中的掛起函數確實可以很好地配合取消。

當您使用阻塞函數時,它與取消不合作,因此即使在協程被取消后它也會繼續運行。 所以當你使用阻塞函數時,你不小心顛覆了預期的協程行為。 當您不想取消協程時,這不是一個很好的解決方案。 它很脆弱,因為在阻塞調用之前很容易放入一些與取消合作的其他函數調用,然后一開始就永遠無法到達。 它還傳達了與您想要的不同的意圖。 viewModelScope中調用代碼意味着您希望在 ViewModel 被銷毀時取消它。

正確的解決方案是使用具有適當生命周期的 CoroutineScope 來執行您要執行的操作。

您可以公開為存儲庫或應用程序子類創建的 CoroutineScope。 或者,如果您不介意編譯器警告,您可以使用預先存在的 GlobalScope。

如果您在存儲庫中創建自己的存儲庫,則是否要取消它取決於您。 如果您的存儲庫是單例,那么我認為取消它是沒有意義的。 如果您在應用程序類中創建一個,也是如此。

(順便說一句,您錯過了關閉輸入流。我使用use調用解決了這個問題,這是使用 Closeables 的慣用方式。在發生錯誤的情況下,您也未能關閉輸出流。這也修復了use調用。)

class MyRepository(val context:Context) {

    //...

    /** A CoroutineScope that lives as long as the Repository. */
    val repositoryScope = MainScope() + CoroutineName("MyRepository")

    //...
}
// In ViewModel:
    recordRepository.launch(Dispatchers.IO) {
        try {
            val image = url.openConnection().getInputStream().use { inputStream ->
                BitmapFactory.decodeStream(inputStream)
            }
            val imagesPath = File("$storagePath/RecordImages/")
            imagesPath.mkdirs()
            val imageFile = File(imagesPath, imageName)
            FileOutputStream(imageFile).use { outputStream ->
                image.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
            }
            val record = Record(recordPosition, title, artist, imageName)
            recordRepository.insert(record)
        } catch (exception: Exception) {
            exception.localizedMessage?.let { Log.d(SearchResultsAdapter.TAG, it) }
        }
    }

對於其他行為,例如,如果您只想在您正在執行的圖像操作完成時插入數據庫,但是一旦您啟動它,您希望保證它完成,您可以在另一個范圍內運行最后一部分:

// In ViewModel:
    viewModelScope.launch(Dispatchers.IO) {
        try {
            val image = url.openConnection().getInputStream().use { inputStream ->
                BitmapFactory.decodeStream(inputStream)
            }
            val imagesPath = File("$storagePath/RecordImages/")
            imagesPath.mkdirs()
            val imageFile = File(imagesPath, imageName)
            FileOutputStream(imageFile).use { outputStream ->
                image.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
            }
            val record = Record(recordPosition, title, artist, imageName)
            recordRepository.repositoryScope.launch {
                recordRepository.insert(record)
            }.join() // join call is optional. It's if you want to do something 
                     // back in the ViewModel scope after the insert is complete.
        } catch (exception: Exception) {
            exception.localizedMessage?.let { Log.d(SearchResultsAdapter.TAG, it) }
        }
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM