일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 백준
- 코드포스
- Coroutines
- ProGuard
- Python
- relay
- 코루틴
- Gradle
- Coroutine
- Codeforces
- AWS
- Rxjava
- activity
- boj
- pandas
- 프로그래머스
- 쿠링
- MyVoca
- textfield
- Compose
- 암호학
- GitHub
- architecture
- livedata
- Kotlin
- android
- androidStudio
- Hilt
- MiTweet
- TEST
- Today
- Total
이동식 저장소
[Android] 백그라운드 작업을 하나씩 처리하는 방법 본문
백그라운드 작업 ``MealWork``와 ``ScheduleWork``를 정의했고, 현재 실행되고 있는 백그라운드 작업의 수를 세기 위해 Preferences DataStore에 정수형 값 ``RUNNING_WORKS_COUNT``를 정의했다. ``MealWork``와 ``ScheduleWork``가 실행될 때 count를 1 증가시키고, 작업이 끝날 때 count를 1 감소시키는 코드를 작성했다. 대략 다음과 같다.
// MealWorker, ScheduleWorker
override suspend fun doWork(): Result {
preferencesRepository.increaseRunningWorkCount()
// do background work
preferencesRepository.decreaseRunningWorkCount()
// return result
}
// PreferencesRepository
suspend fun increaseRunningWorkCount() {
val currentWorksCount = userPreferencesFlow.first().runningWorksCount
updateRunningWorkCount(currentWorksCount + 1)
}
suspend fun decreaseRunningWorkCount() {
val currentWorksCount = userPreferencesFlow.first().runningWorksCount
updateRunningWorkCount(currentWorksCount - 1)
}
마지막으로 ``MainScreenViewModel``에서 count 값을 받아 적절히 처리한다. 값이 제대로 계산됐는지 확인하기 위해 ViewModel에서 로그를 찍어 보았다.
Count의 초기값은 0이며, 두 개의 Work가 실행되고 종료된다. 따라서 내 의도대로라면 count가 ``0 → 1 → 2 → 1 → 0`` 순서로 바뀌어야 한다. 그런데 실행 결과는 다음과 같았다.
2022-11-07 15:14:10.037 7332-7380 MainViewModel D current background works: -4
2022-11-07 15:14:13.604 7332-7380 MainViewModel D current background works: -3
2022-11-07 15:14:15.225 7332-7372 MainViewModel D current background works: -4
2022-11-07 15:14:16.658 7332-7381 MainViewModel D current background works: -5
값이 1 증가한 후 2 감소하였다. 왜 두 번 증가하지 않는 거지?
Why
``PreferencesRepository``의 ``increaseRunningWorkCount`` 함수는 ``count``를 가져온 후 ``count+1``로 업데이트한다. 그런데 Work 2개가 동시에 시작되기 때문에 같은 count 값을 2번 가져오게 되고, 결과적으로 1을 두 번 더하는 게 아니라 ``count+1``로 두 번 업데이트하게 되는 것이다.
값을 감소시킬 때 문제가 없었던 이유는 ``MealWork``가 유의미하게 늦게 끝나기 때문이다. 따라서 값을 감소시킬 때는 같은 값을 두 번 가져오는 상황이 발생하지 않는다.
How
어떻게 해야 값을 올바르게 업데이트할 수 있을까? 거시적으로 생각해 보면 ``increaseRunningWorkCount``와 ``decreaseRunningWorkCount``가 동시에 값을 가져오지 못하게 해야 한다. 동시에 하나의 함수만 실행될 수 있게 하면 더 좋다.
Mutex?
Mutex로 문제를 풀 수 있을까?
private suspend fun edit(action: (MutablePreferences) -> Unit) {
mutex.withLock {
dataStore.edit {
action(it)
}
}
}
2022-11-07 15:35:04.676 7600-7640 MainViewModel D current background works: -6
2022-11-07 15:35:09.959 7600-7642 MainViewModel D current background works: -5
2022-11-07 15:35:11.552 7600-7646 MainViewModel D current background works: -6
2022-11-07 15:35:13.622 7600-7651 MainViewModel D current background works: -7
해결되지 않았다. ``increaseRunningWorkCount``에 mutex를 적용해도 해결되지 않는다. 근본적으로 동시에 하나의 작업만 실행할 수 있는 방법을 찾아야 한다.
Channel!
해결법을 찾아보던 도중 아래 글을 읽게 되었다.
But since we're queuing things up, maybe it would be better to use something like the BlockingQueue class in Java? I'm still investigating that part, but spoilers: Channels
Channel을 어떻게 사용해 볼까.. 값을 증가시키고 싶다면 Channel에 1을 보내고, 감소시키고 싶다면 -1을 보낸 후, Channel을 소비하는 함수에서 count를 갱신하면 한 번에 하나의 업데이트만 실행될 것이다. 이렇게 하면 모든 갱신을 atomic하게 처리할 수 있다.
// PreferencesRepository (implements CoroutineScope)
private val requestUpdateWorkCounts = Channel<Int>()
suspend fun increaseRunningWorkCount() {
requestUpdateWorkCounts.send(1)
}
suspend fun decreaseRunningWorkCount() {
requestUpdateWorkCounts.send(-1)
}
private fun consumeUpdateWorkCountRequests() = launch {
for (diff in requestUpdateWorkCounts) {
updateRunningWorkCount(diff)
}
}
private suspend fun updateRunningWorkCount(diff: Int) {
edit {
val currentCount = it[PreferenceKeys.RUNNING_WORKS_COUNT] ?: 0
it[PreferenceKeys.RUNNING_WORKS_COUNT] = currentCount + diff
}
}
2022-11-07 15:48:28.825 8357-8419 MainViewModel D current background works: 0
2022-11-07 15:48:28.864 8357-8410 MainViewModel D current background works: 1
2022-11-07 15:48:28.878 8357-8410 MainViewModel D current background works: 2
2022-11-07 15:48:30.438 8357-8420 MainViewModel D current background works: 1
2022-11-07 15:48:31.464 8357-8409 MainViewModel D current background works: 0
값이 제대로 갱신된다. 이처럼 백그라운드 작업을 한번에 하나씩 실행하고 싶다면 Channel을 사용해볼 수 있다.
매번 Flow만 쓰다가 Channel을 처음으로 프로덕션 코드에 처음으로 사용해 보았다. 역시 뭐든 알아두면 좋다니까.
'Primary > Android' 카테고리의 다른 글
[Android Studio] Electric Eel 신기능 정리 (0) | 2022.12.04 |
---|---|
[Android] 모듈화 가이드 (0) | 2022.11.18 |
[Android] 반응형 앱 구현 방법론 (2) (0) | 2022.10.18 |
[Android] 반응형 UI 구현 방법론 (0) | 2022.10.17 |
[Android] AAB 더 알아보기 (0) | 2022.10.14 |