본문 바로가기

Language Syntax/Dart

[Dart] 동기(Synchronous) / 비동기(Asynchronous)

1. 개요

Dart 에서는 비동기로 구현할 수 있는 문법이 존재한다. 서비스를 개발할 때 수없이 구현해왔지만 사용방법만 익혔을 뿐 그 의미를 정확히 알고 사용하지 않았기에 이번 기회에 확실하게 파악해보고자 한다.

2. 개념 이해

2.1. 동기와 비동기

동기와 비동기는 다음을 의미한다.

  • 동기(Synchronous): 작업이 순차적으로 실행되며, 하나의 작업이 완료된 후 다음 작업을 시작
  • 비동기(Asynchronous): 여러 작업을 동시에 진행할 수 있으며, 한 작업이 완료되기를 기다리지 않고 다음 작업을 수행

BOJ 문제를 해결하는 코드나 계산 작업을 하는 코드 등은 충분히 동기적으로도 구현 가능하지만 다양한 사용자가 존재하고 지속적으로 서버와 소통해야 하는 서비스를 개발할 때는 특정 작업의 결과값을 즉시 얻지 못하는 경우가 많다.

  • 서버에 사용자 id 값을 요청하는 경우
  • API 호출에 대한 응답값을 받는 경우
  • 사용자 입력값을 받는 경우

위와 같은 경우가 이에 해당하며 이를 비동기적으로 구현하여 원하는 결과에 도달할 수 있다.

2.2. Future

Future 는 미래의 어떤 시점에 완료될 작업의 결과를 나타내는 객체를 나타낸다. 함수의 반환값으로 주로 사용되고 Future<T> 와 같이 나타내어 반환값의 자료형을 특정할 수 있다.

2초 후에 1 부터 99 사이의 임의의 값을 반환하는 함수는 다음과 같이 구현할 수 있다.

Future<int> getRandomNumberAfter2Seconds() {
  return Future.delayed(Duration(seconds: 2), () {
    return Random().nextInt(100);
  });
}

3. 구현

3.1. 동기

void main() {
  print('작업1 시작');
  print('작업1 종료');

  print('작업2 시작');
  print('작업2 종료');
}
작업1 시작
작업1 종료
작업2 시작
작업2 종료

동기적 구현 코드에서는 작업1 과 작업2가 순차적으로 실행된다.

3.2. 비동기

void main() async {
  print('작업1 시작');
  Future.delayed(Duration(seconds: 1), () => print('작업1 종료'));
  print('작업2 시작');
  Future.delayed(Duration(seconds: 1), () => print('작업2 종료'));
}
작업1 시작
작업2 시작
(1초 후)
작업1 종료
작업2 종료

비동기적 구현 코드에서는 작업1 이 종료되기 전에 작업2 가 먼저 시작된다. 즉, 작업1 과 작업2 를 동시에 처리할 수 있게 되는 것이다.

4. 비동기 구현 예시

4.1. then() 함수

Future<UserModel> getUserModel(String id) {
  return _database.getUser(id);
}

void main() {
  getUserModel(id).then((user) {
    print('사용자 이름: ${user.name}');
  });
}

위와 같이 then() 함수를 사용하여 사용자 모델을 반환받은 후 해당 객체에 접근하도록 구현할 수 있다.

4.1.1. 콜백 지옥

다만 then() 함수만을 이용하여 구현할 경우, then() 함수가 연속적으로 중첩되는 형태로 인해 복잡한 코드가 생성될 수 있다. 아래와 같은 코드를 콜백 지옥이라고 한다.

void main() {
  getUserModel(id).then((user) {
    getReservationModel(user.reservationId).then((reservation) {
      getPaymentInfoModel(reservation.paymentId).then((payment) {
        getShippingDetailsModel(payment.shippingId).then((shipping) {
          getOrderConfirmationModel(shipping.confirmationId).then((confirmation) {
            print(confirmation.hasConfirmed);
          });
        });
      });
    });
  });
}

너무 많은 들여쓰기를 요하고 가독성도 떨어지게 된다.

4.2. asyncawait

async await 를 사용하여 나타낼 수도 있다.

Future<UserModel> getUserModel() async {
  return await _database.getUser();
}

void main() async {
  UserModel user = await getUserModel();
  print('사용자 아이디: ${user.id}');
}

위의 then() 메소드를 사용한 예시와 같은 동작을 수행한다. 다만 await 예약어를 통해 비동기 코드를 동기적으로 구현할 수 있다는 점에서 의미가 있다.

4.2.1. 콜백 지옥 해결

asyncawait 를 사용하면 콜백 지옥을 해결할 수 있다.

void main() async {
  final user = await getUserModel(id);
  final reservation = await getReservationModel(user.reservationId);
  final payment = await getPaymentInfoModel(reservation.paymentId);
  final shipping = await getShippingDetailsModel(payment.shippingId);
  final confirmation = await getOrderConfirmationModel(shipping.confirmationId);
  print(confirmation.hasConfirmed);
}

겉보기에도 훨씬 깔끔해진 것을 확인할 수 있다.

5. 의의

비동기 프로그래밍은 네트워크 통신, 사용자 입력 처리, 파일 입출력 등의 시간이 오래 걸리는 작업을 효율적으로 처리하기 위해 필수적이다. 이러한 작업에 있어 비동기 프로그래밍은 다음의 의의를 가진다.

  1. 동기적 코드와 비슷하게 구현
  2. 성능 최적화
  3. 콜백 지옥 해결
  4. 오류 처리 단순화
728x90

'Language Syntax > Dart' 카테고리의 다른 글