class MainActivity : AppCompatActivity() { val feeds = listOf( "https://www.npr.org/rss/rss.php?id=1001", "https://feeds.foxnews.com/foxnews/politics?format=xml", //"https://rss.cnn.com/rss/cnn_topstories.rss" <- 사이트 변경으로 인하여 오류 발생때문에 주석처리 )
private fun fetchRssHeadlines(feed: String, dispatcher: CoroutineDispatcher) = GlobalScope.async(dispatcher) { val builder = factory.newDocumentBuilder() val xml = builder.parse(feed) val news = xml.getElementsByTagName("channel").item(0) (0 until news.childNodes.length) .map { news.childNodes.item(it) } .filter { Node.ELEMENT_NODE == it.nodeType } .map { it as Element } .filter { "item" == it.tagName } .map { it.getElementsByTagName("title").item(0).textContent } }
val dispatcher = newFixedThreadPoolContext(2,"IO")
asyncFetchHeadlines() 는 서버에서 정보를 가져올 뿐 아니라 파싱도 하기 때문에 풀의 크기를 늘린다.
XML을 파싱하는 오버 헤드는 단일 스레드를 사용하는 경우
-> 성능에 영향을 준다.
때로는 다른 스레드의 파싱이 완료될 때까지 한 피드로부터 정보를 가져오는 것이 지연될 수 있다.
동시에 여러 피드에 요청을 보내기 위해 필요한 모든 것을 갖췄다.
목록에서 각 피드당 하나의 디퍼드를 생성한다.
먼저 asyncLoadNews() 함수를 수정해 대기하는 모든 디퍼드를 추적할 수 있는 목록을 만들어보자.
private fun asyncLoadNews() = GlobalScope.launch(dispatch) { val requests = mutableListOf<Deferred<List<String>>>() // 피드별로 가져온 요소를 피드 목록에 추가한다. feeds.mapTo(requests) { asyncFetchLeadlines(it, dispatch) } // 각 코드가 완료될 때까지 대기하는 코드를 추가한다. requests.forEach{ it.await() } }
val headlines = requests.flatMap { it.getCompleted() }
2 개의 피드에서 동시에 가져온 모든 헤드라인을 포함하는 headlines 변수가 생겼다.
현재 asyncLoadNews()는 두 부분으로 구성돼 있다.
첫 번째는 데이터를 가져오고 구성하는 것이다.
두 번째는 첫 번째 바로 아래 부분으로 헤드 라인의 개수를 UI상에 표시한다.
val newCount = findViewById<TextView>(R.id.newsCount) GlobalScope.launch(Dispatchers.Main) { newCount.text = "Found ${headlines.size} News" }
val newCount = findViewById<TextView>(R.id.newsCount) launch(Dispatchers.Main) { newCount.text = "Found ${headlines.size} News" + "in ${requests.size} feeds" }
private fun asyncLoadNews() = GlobalScope.launch(dispatch) { val requests = mutableListOf<Deferred<List<String>>>() feeds.mapTo(requests) { asyncFetchLeadlines(it, dispatch) } requests.forEach{ it.await() } ... }
↪ 코루틴이 완료될 때까지 await() 를 사용해 대기하므로, 코루틴 내부에서 발생하는 예외는 현재 스레드로 전파된다. 다음과 같이 애플리케이션이 쉽게 중단될 수 있는 두 개의 시나리오가 존재한다는 것을 의미한다.
val feeds = listOf(
"https://www.npr.org/rss/rss.php?id=1001",
"https://feeds.foxnews.com/foxnews/politics?format=xml",
"htt://myNewsFeed"
↪ 이 피드를 가져오자마자 애플리케이션을 중단한다.
private fun asyncLoadNews() = GlobalScope.launch { val requests = mutableListOf<Deferred<List<String>>>() feeds.mapTo(requests) { asyncFetchLeadlines(it, dispatch) } requests.forEach{ it.join() } ... }
애플리케이션을 실행해보면 여전히 중단되는 모습을 확인할 수 있따.
디퍼드를 기다릴 때 예외를 전파하지 않더라도 요청을 읽을 때 예외를 전파해서 이런 일이 발생한다.
디퍼드에서 getCompleted()를 호출하면 디퍼드는 예외로 인해 취소됐기 때문에 예외가 발생한다.
디퍼드가 실패하지 않았을 때만 getCompleted()가 호출되도록 코드를 바꿔야 한다.
val headlines = requests .filter { !it.isCancelled } .flatMap { it.getCompleted() }
<TextView android:id="@+id/warnings" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="20dp" app:layout_constraintTop_toBottomOf="@+id/newsCount" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent"/>
val headlines = requests .filter { !it.isCancelled } .flatMap { it.getCompleted() } val failed = requests .filter { it.isCancelled } .size val newCount = findViewById<TextView>(R.id.newsCount) val warnings = findViewById<TextView>(R.id.warnings) val obtained = requests.size - failed GlobalScope.launch(Dispatchers.Main) { newCount.text = "Found ${headlines.size} News in ${requests.size} feeds" if (failed > 0) { warnings.text = "Failed to fetch $failed feeds" } }