[Error] cannot access database on main thread in LifecycleScope

Minji Jeong·2022년 6월 13일
0

Troubleshooting

목록 보기
12/20
post-thumbnail

문제

fragment에서 lifecycleScope를 사용해 데이터베이스에 접근하던 중 다음과 같은 오류가 발생했다.

class ModifyDialogFragment() : DialogFragment() {

    private lateinit var binding : ModifyDialogBinding
    private val viewModel : ViewModel by inject()
    private var job : Job? = null
    private var serialNum: Int = 0

    constructor(serialNum: Int) : this() {
        this.serialNum = serialNum
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = ModifyDialogBinding.inflate(inflater)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        ...
    }

    override fun onStart() {
        super.onStart()
        job = lifecycleScope.launch(Dispatchers.IO) {
        	val schedule = viewModel.getSchedule(serialNum)  
        }
    }

    override fun onStop() {
        super.onStop()
        job?.cancel()
    }
}
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.

이 오류는 메인 쓰레드에서 로컬 데이터베이스에 직접 접근할 때 봤던 오류다. 평상시 그냥 coroutineScope를 쓰다가 fragment의 생명주기 관리를 위해 lifecycleScope를 사용한건데, coroutineScope를 사용할 때는 아무 문제가 없다가 lifecycleScope를 사용하니 이런 문제가 발생했다. 두개는 용도만 다르지, 비동기 작업을 할 수 있는 것에선 동일한 코루틴 스코프가 아니였나? 😥 두 스코프의 차이를 구체적으로 보면 다음과 같다.

CoroutineScope
코루틴이 실행되는 범위로, 코루틴을 실행하고 싶은 lifecycle에 따라 원하는 scope를 생성해서 코루틴이 실행될 작업 범위를 지정할 수 있다. CoroutineScope가 취소되면 해당 스코프에 속한 모든 작업들도 취소된다.

LifecycleScope
Lifecycle 객체(Activity, Fragment, Serivce)를 대상으로 하며, LifeCycleOwner의 lifecycle에 엮여있으므로 lifecycle이 destroyed되면 코루틴 작업도 자동으로 취소된다. 기본적으로 Dispatcher.Main(메인 쓰레드)를 사용한다.

해결

lifecycleScope.launch{}는 메인 쓰레드 내에서 작업을 수행한다. 따라서 비동기 작업을 요하는 데이터베이스 관련 작업을 lifecycleScope에서 수행하면 안되는게 당연했다. 따라서 withContext()를 사용해서 작업이 실행될 쓰레드를 변경해주니 문제가 해결되었다.


override fun onStart() {
	super.onStart()
    job = lifecycleScope.launch() {
    	withContext(Dispatchers.IO){
        	val schedule = viewModel.getSchedule(serialNum)
       }
    }
}

References

https://stackoverflow.com/questions/65224524/why-does-lifecyclescope-launch-in-a-fragment-block-the-ui-thread
https://0391kjy.tistory.com/49
https://stackoverflow.com/questions/59718077/lifecyclescope-launch-vs-coroutine-inside-onviewcreated

profile
Mobile Software Engineer

1개의 댓글

comment-user-thumbnail
2022년 7월 16일

withContext(Dispatchers.IO){
val schedule = viewModel.getSchedule(serialNum)
}

안녕하세요 :)
포스트 잘 보고 있습니다.
데이터베이스 관련 IO 작업을 Fragment에서 작업하시는 이유가 뭘까요?

답글 달기