태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.
2019.02.07 11:30


AsyncTask 를 Coroutine 으로 바꿔본 후기


asynctask to coroutine, asynctask vs coroutine, AsyncTask 를 Coroutine 으로 바꿔본 후기, cancel, cancellationexception, coroutine conversion, coroutine 장점, doinbackground, flow, Join, learning curve, local, nullable check, onpostexecute, onpreexecute, return type, shared variable, Val



-

일반적으로 잘 사용하는 아래의 패턴은 꽤 나이스하게 바뀐다.


onPreExecute 에서 progress

doInBackground 에서 bg job

onPostExecute 에서 progress 닫고 UI 작업



-

// AsyncTask
 object : AsyncTask<Void, Void, List<MyAccountItem>>() {
    override fun onPreExecute() {
        showProgress(R.string.loading)
    }

    override fun doInBackground(vararg params: Void): List<MyAccountItem> {
        return getMyAccountItemList();
    }

    override fun onPostExecute(result: List<MyAccountItem>) {
        if (isFinishing) return

        hideProgress()
        updateUI(result)
    }
}.execute()

// Coroutine
GlobalScope.launch(coroutineContext) {
    showProgress(R.string.loading)

    val accountList = async(Dispatchers.Default) { getMyAccountItemList() }.await()

    if (isFinishing) return@launch
    hideProgress()
    refreshUI(accountList)
}


내가 본 장점들은 이렇다. 

1. override 함수들이 제거되어 코드가 짧아진다.

2. coroutine 은 android 종속이 아니라 kotlin 코드를 사용하는 모든 곳에 share 가능한 형태의 코드가 된다.

3. flow 가 더 잘 보인다. (override 함수들의 경우 위치에 따라 따라가기가 어려울 수 있다.)

4. return type 에 대한 변경이 훨씬 자유롭다.

5. shared variable 들의 범위가 local 로 한정된다. 줄어든다. (케바케겠지만..)

6. kotlin 과 함께 val 을 쓰기도 좋고, nullable check 등이 더 유려해진다.



-

그러나.. onCancel 을 처리해야 하는 과정에서는 기존 AsyncTask 에 친숙한 사람들 기준에서 볼 때 가독성 측면에서 불편함이 생긴다.

// AsyncTask
private inner class DeleteContactsTask(private val mAccountItem: MyAccountItem) : AsyncTask<Void, Int, Void>() {
    override fun onPreExecute() {
        showProgress(R.string.deleting){
            cancel(true)
        }
    }

    override fun onCancelled(result: Void?) {
        hideProgress()
        showToast(R.string.toast_msg_delete_canceled)
        refreshUI()
    }

    override fun onProgressUpdate(vararg values: Int?) {
        val deletedCount = values[0]!!
        increaseProgress(deletedCount)
    }

    override fun doInBackground(vararg params: Void): Void? {
        ContactsDBHelper.deleteAllContacts(mAccount) { deletedCount ->
            publishProgress(deletedCount)
        }
        
        ContactsDBHelper.deleteContacts(
            account = mAccountItem.account,
            deletedCallback = { deletedCount -> publishProgress(deletedCount) },
            tackCancelWatcher = { return isCancelled() }
        )
        return null
    }

    override fun onPostExecute(result: Void?) {
        if (isFinishing) return

        hideProgress()
        showToast(R.string.toast_msg_delete_success)
        refreshUI()
    }
}

// Coroutine
GlobalScope.launch(coroutineContext) {
    val job = launch(Dispatchers.Default) {
        ContactsDBHelper.deleteContacts(
                account = myAccount.account,
                progressListener = { deletedCount -> 
                    launch(coroutineContext){}
                        increaseProgress(deletedCount)
                    }
                },
                taskCancelWatcher = { isActive == false )
        )
    }

    showProgress(R.string.deleting) {
        job.cancel()
    }
    
    job.join()

    if (isFinishing) return@launch
    hideProgress()

    if (job.isCancelled) {
        showToast(R.string.toast_msg_delete_canceled)
    } else {
        showToast(R.string.toast_msg_delete_success)
    }
    refreshUI()
}

장점은 앞선 것과 거의 동일하며, 추가적인 내용은 onCancelled 와 onPostExecute 에서 공통 로직으로 처리해야 하는 것들을 함께 쓸 수 있다는 것이 좋다.


단점들이 보이는데..

1. Job 을 cancel 해야 하기 때문에, showProgress 가 뒤로 밀리게 되어 일반적인 AsyncTask 를 쓰는 것에 비해 가독성이 조금 떨어질 수 있다.

2. cancel, join, CancellationException 등의 관계를 잘 알지 못하면 혼란스러울 수 있다.


단점 중 2번은 학습을 통해 어차피 알아야 하는 것이고, 1번이 약간 그렇지만..

장점을 상쇄하고 남는것 같다.



-

끝!




댓글을 달아 주세요


Posted by 돼지왕왕돼지