문제 상황
서버에서 데이터를 보내면 안드로이드에서 service를 통해 데이터를 받고, BroadCastReceiver를 통해 해당 데이터를 처리하여, 화면에 '순서대로' 보여주어야 한다.

그러나 서버에서 데이터가 '동시에', '여러개'가 들어오면 제일 처음의 데이터만 나오고 뒤의 데이터들은 나오지 않는 상황이었다. (정확히는 동시에 ui 애니메이션 로직을 호출하여, 앞의것에 전부 가려져있는 상황)
자체적으로 Queue를 만들어서 해결하려 하였으나, 비동기작업을 하는 코루틴블록 함수가 메세지가 동시에 들어오면, 동시에 실행되기 때문에 일종의 원자성을 해쳐 원하는 결과를 얻기 힘들었다.
또한 다수의 코루틴 블록의 경우 join이나 await등으로 순서등을 조정할 수 있지만, 지금은 하나의 코루틴블록을 동시에 실행하는 상황이라 앞서 이미 실행된 경우 뒤의것을 미룰 수 있어야했다.
해결과정
JAVA의 BlockingQueue와 흡사한 Channel을 이용하면 비동기상황에서도 순서를 보장받는 Queue를 만들 수 있다.
private val mMessageReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val message = intent.getStringExtra("body")
lifecycleScope.launch {
message?.let {
channel.send(it)
}
}
...생략
해당 데이터를 화면에 띄워줄 때 역시 비동기 처리를 위해 코루틴 블록을 사용해야하므로, 동시성이 필요한 환경에서 순서를 보장받을 수 있다.
그러나 첫번째로 호출된 비동기블록이 더이상 존재하지도 않는 큐의 데이터에 접근하는것을 막아줬을 뿐, 여전히 코루틴 블록이 '동시에' 실행되는 문제때문에 ui 변경 로직이 동시에 실행되는것을 막는 방법을 찾아야했다.
suspend fun showMessage() = withContext(IO) {
if(!isRunning) {
isRunning = true
for (i in channel) {
withContext(Main) {
binding.achieveTextView.text = i
binding.parentCardview.transitionToEnd()
}
delay(3000L)
withContext(Main) {
binding.parentCardview.transitionToStart()
}
delay(500L)
}
isRunning = false
}
}
어떻게보면 가장 간단한(?) 해결 방법. 새로운 변수를 하나 할당해서, 일종의 Flag로 활용하여 이미 앞서 실행중인 경우 실행을 막는 방법이다.
그러나 이 문제는 데이터가 동시에 여러개 들어왔을때는 문제가 없을 수 있지만, 데이터가 짧은 텀을 주고 여러개가 들어오면, ui 애니메이션을 위해 넣어둔 delay코드 때문에 오히려 뒤에 들어온 데이터가 통째로 무시될 수 있는 상황이었다.
이러한 문제는 Mutex (Mutual Exclusion)를 이용하여 해결하였다.
suspend fun showMessage() = withContext(IO) {
mutex.withLock{
for (i in channel) {
withContext(Main) {
binding.achieveTextView.text = i
binding.parentCardview.transitionToEnd() // 상단에 ui를 보여주는 애니메이션
}
delay(3000L) // 3초간 나타난다
withContext(Main) {
binding.parentCardview.transitionToStart() // ui 없애는 애니메이션
}
delay(500L)
}
}
}
Mutex는 공유자원에 변경이 일어나는 순간에 block을 걸어 race condition이 일어나는것을 막아준다.
( * race condition : 두 개 이상의 프로세스가 공통 자원을 병행적으로 읽거나 쓰는 동작을 할 때, 공용 데이터에 대한 접근이 어떤 순서에 따라 이루어졌는지에 따라 그 실행 결과가 같지 않고 달라지는 상황)
때문에 Mutex를 이용하면 코루틴블록이 동시 실행되어 channel에 동시에 접근하여 데이터를 꺼내는것을 막을 수 있다.

Mutex에 대한 설명은 아래 게시물을 참고하였다.
'Android > 트러블슈팅' 카테고리의 다른 글
| Channel을 이용하여 팝업 메세지 구현하기 ( send & trySend ) (0) | 2022.04.13 |
|---|---|
| 네이버 소셜 로그인 Android12 오류 ( + SDK Version ) (0) | 2021.12.16 |