dart 비동기 프로그래밍 (async programming)

ssh·2023년 12월 13일
0

dart

목록 보기
19/22

동기(sync) 프로그래밍

  • 코드가 순서대로 실행된다.
  • 작업이 완료될 때까지 프로그램이 중단될 수 없다.
  • 모든 작업은 이전 작업의 실행이 완료될 때까지 기다려야 한다.

비동기(async) 프로그래밍

  • 임의의 순서로 동시에 작업이 실행될 수 있다.
  • 비동기를 처리하는 방법에는 콜백, Future, async -await 방식이 있다.
    • 코드
      void main() {
        addNumbers(1, 1);
        addNumbers(2, 2);
      }
      
      // 1 + 1 = 2
      // 2 + 2 = 4
      void addNumbers(int number1, int number2) {
        print('계산중: $number1 + $number2');
      
        print('계산 완료: ${number1 + number2}');
      }

콜백 함수

  • 비동기는 현재 코드의 실행 결과를 받지 않고 이후 코드를 수행하는 기업이다.
  • 컴퓨팅 자원을 효율적으로 사용하는 기법이지만 정확한 순서를 지켜 수행해야 하는지를 고려해야 한다.
  • 비동기 코드를 순서대로 실행하는 가장 일반적인 방안으로 콜백(Callback)이 있다.
  • 콜백은 실행 가능한 함수를 인자로 전달하여, 특정 상황이 발생할 때 호출되게 하는 방식이다.
    • 현실 세계에서 콜백의 예시
      • 음식을 주문하고 진동벨을 가지고 기다리면 음식이 준비되면 손님을 호출(Callback)하는 상황

작성예시

  • 회원가입의 3단계
    1. 회원가입 API 호출
    2. DB에 저장
    3. 이메일 전송
    4. 성공 메시지 출력

예제

  • 코드
    final List database = [];
    
    void main(List<String> arguments) {
      final result = register({
        'email': 'abc@abc.com',
        'password': '123456',
        'name': 'John Doe',
      });
      print(result.call());
      print(database);
    }
    
    Function register(user) {
      return saveDb(user, (user) {
        return sendEmail(user, (user) {
          return getResult(user);
        });
      });
    }
    
    Function saveDb(user, callback) {
      print('saving $user to db');
      return callback(user);
    }
    
    Function sendEmail(user, callback) {
      print('sending email to $user');
      database.add(user);
      return callback(user);
    }
    
    Function getResult(user) {
      return () => 'success register $user';
    }

Future

  • Future 클래스란 미래에 받아올 값을 의미하며 제네릭으로 어떤 미래의 값을 받아올지를 정할 수 있다.
  • 비동기 프로그래밍은 시간이 오래 걸리는 작업을 기다린 후 결과값을 받아와야 하기 때문에 미래값을 표현하는 Future 클래스가 필요하다.
  • javascript의 promise에 대응
  • Future는 ‘미래’에 받아올 값을 의미

작성

  • Dart 및 Flutter의 많은 API는 Future타입을 리턴한다.
    • 비동기 코드를 시뮬레이트 하기 위해 5초 후에 실행되는 Future 함수 정의
      var delay = Future.delayed(Duration(seconds: 5));
    • 2초 미래에 동작하는 코드
      void main() {
        myFunction();
      }
      
      void myFunction() {
        print('start');
      
        Future.delayed(Duration(seconds: 2), () {
          print('2 seconds');
        });
        print('end');
      }
  • 예제
    • 코드1
      void main() {
        var delay = Future.delayed(Duration(seconds: 3));
      
        delay
            .then((value) => print('3초 지남')) // 오래걸리는 작업 처리
            .catchError((e) => print(e)); // 에러처리
      
        hello().then((value) => print(value));
      }
      
      Future<String> hello() async {
        await Future.delayed(Duration(seconds: 3));
        return 'Hello';
      }
    • 코드2
      void main() {
        // Future - 미래
        // 미래에 받아올 값
        Future<String> name = Future.value('코드팩토리');
        Future<int> number = Future.value(1);
        Future<bool> isTrue = Future.value(true);
      
           print('함수 시작');
        
        // 2개의 파라미터
        // delayed - 지연되다.
        // 1번 파라미터 - 지연할 기간 (얼마나 지연할 건지) Duration
        // 2번 파라미터 - 지연 시간이 지난 후 실행할 함수
         Future.delayed(Duration(seconds: 2), () {
           print('Delay 끝');
      //   });
      }
    • 코드3
      void main() {
        // Future - 미래
        // 미래에 받아올 값
        Future<String> name = Future.value('코드팩토리');
        Future<int> number = Future.value(1);
        Future<bool> isTrue = Future.value(true);
      
        addNumbers(1, 1);
      }
      
      void addNumbers(int number1, int number2) {
        print('계산 시작 : $number1 + $number2');
      
        // 서버 시뮬레이션
        Future.delayed(Duration(seconds: 2), () {
          print('계산 완료:  $number1 + $number2 = ${number1 + number2}');
        });
      
        print('함수완료');
      }
    • 코드4
      void main() {
        // Future - 미래
        // 미래에 받아올 값
        Future<String> name = Future.value('코드팩토리');
        Future<int> number = Future.value(1);
        Future<bool> isTrue = Future.value(true);
      
        addNumbers(1, 1);
        addNumbers(2, 2);
      }
      
      void addNumbers(int number1, int number2) {
        print('계산 시작 : $number1 + $number2');
      
        // 서버 시뮬레이션
        Future.delayed(Duration(seconds: 2), () {
          print('계산 완료:  $number1 + $number2 = ${number1 + number2}');
        });
      
        print('함수완료');
      }

then()

  • Future 함수는 결과를 then() 함수를 통해서 받을 수 있다.
  • then() 함수로 전달되는 콜백 함수에 다음에 실행할 코드를 작성하면 된다.
  • 다음 코드가 Future라면 계속해서 then() 을 이어서 결과를 전달받을 수 있다.
  • 불필요한 인자는 _ 를 쓰는 것이 관례이다.
    • 코드
      void main() {
        final user = {
          'email': 'abc@abc.com',
          'password': '123456',
          'name': 'John Doe',
        };
      
        saveDb(user)
            .then((_) => sendEmail(user))
            .then((_) => getResult(user))
            .then((value) => print(value));
      }
  • Future는 성공(then)일 수도 있고 오류(catchError)일 수도 있다.
    delay
    		.then((value) => print('I have been waiting'))
    		.catchError((err) => print(err));
  • Future 예외처리
    • catchError()함수를 사용하면 에러 처리를 할 수 있다.
    • 에러를 발생시킬 때는 Future.error()를 사용한다.
      • 코드
        import 'callback_test.dart';
        
        void main() {
          final user = {
            'email': 'abc@abc.com',
            'password': '123456',
            'name': 'John Doe',
          };
        
          saveDb(user)
              .then((_) => sendEmail(user))
              .catchError((err) => print('save db error: $err')) // 예외 발생
              .then((_) => getResult(user))
              .then((value) => print(value)));
        }
        
        Future<void> saveDb(user) async {
          print('saving $user to db');
        }
        
        Future<void> sendEmail(user) async {
          await Future.delayed(Duration(seconds: 2));
          await Future.error(Exception('error save db'));
          print('sending email to $user');
          database.add(user);
        }
        
        Future<String> getResult(user) async {
          return 'success register $user';
        }
    • 예외를 발생시키기
      • throw Exception()으로 예외를 발생시킬 수 있다.
        • 코드
          import 'callback_test.dart';
          
          void main() {
            final user = {
              'email': 'abc@abc.com',
              'password': '123456',
              'name': 'John Doe',
            };
          
            saveDb(user)
                .then((_) => sendEmail(user))
                .catchError((err) => print('save db error: $err')) // 예외 발생
                .catchError((err) =>
                    throw Exception('error send email')) // throw Exception()으로 예외를 발생
                .then((_) => getResult(user))
                .then((value) => print(value));
          }
          
          Future<void> saveDb(user) async {
            print('saving $user to db');
          }
          
          Future<void> sendEmail(user) async {
            await Future.delayed(Duration(seconds: 2));
            await Future.error(Exception('error save db'));
            print('sending email to $user');
            database.add(user);
          }
          
          Future<String> getResult(user) async {
            return 'success register $user';
          }
    • 비동기 처리의 성공, 실패 관계없이 실행할 수 있게 해 주는 함수
      • whenComplete()는 성공, 실패 관계없이 실행된다.
        • 코드
          import 'callback_test.dart';
          
          void main() {
            final user = {
              'email': 'abc@abc.com',
              'password': '123456',
              'name': 'John Doe',
            };
          
            saveDb(user)
                .then((_) => sendEmail(user))
                // .catchError((err) => print('save db error: $err')) // 예외 발생
                .catchError((err) =>
                    throw Exception('error send email')) // throw Exception()으로 예외를 발생
                .then((_) => getResult(user))
                .then((value) => print(value))
                .whenComplete(() => print('완료')); // whenComplete()는 성공, 실패 관계없이 실행
          }
          
          Future<void> saveDb(user) async {
            print('saving $user to db');
          }
          
          Future<void> sendEmail(user) async {
            await Future.delayed(Duration(seconds: 2));
            await Future.error(Exception('error save db'));
            print('sending email to $user');
            database.add(user);
          }
          
          Future<String> getResult(user) async {
            return 'success register $user';
          }
  • then() 사용의 문제점
    • 확실히 콜백 보다는 편하지만 동기식 코드 보다는 결과 예측이 어렵다.
    • 단계가 많아지면 then() 을 연결하는 체이닝 방식을 사용하는 것이 어렵다.
    • 로직이 복잡해 지면 적절한 예외처리가 쉽지 않다.

async - await

  • async - await를 사용하면 코드의 가독성을 높일 수 있다.
  • await 키워드는 해당 Future가 끝날 때까지 함수 실행을 기다린다.
  • 함수를 async로 지정하고 대기하고 싶은 비동기 함수를 실행할 때 await 키워드를 사용하여 코드를 작성한 순서대로 실행한다.
    Future<String> runInTheFuture() async {
    	var data = await Future.value('world');
    
    	return 'hello $data';
    }
  • await 키워드 뒤에는 반드시 Future 타입이 와야 한다.
  • await 키워드는 async 키워드가 있는 함수에서만 사용할 수 있다.
  • Future함수는 함수 본문 앞에 async 키워드를 지정해야 한다.
  • 대기하고 싶은 비동기 함수를 실행할 때 await 키워드를 사용해주면 코드가 작성한 순서대로 실행된다.
    • 코드1
      void main() {
        // Future - 미래
        // 미래에 받아올 값
        Future<String> name = Future.value('코드팩토리');
        Future<int> number = Future.value(1);
        Future<bool> isTrue = Future.value(true);
      
        addNumbers(1, 1);
      }
      
      void addNumbers(int number1, int number2) async {
        print('계산 시작 : $number1 + $number2');
      
        // 서버 시뮬레이션
        await Future.delayed(Duration(seconds: 2), () {
          print('계산 완료:  $number1 + $number2 = ${number1 + number2}');
        });
      
        print('함수 완료 : $number1 + $number2');
      }
    • 코드2
      void main() {
        // Future - 미래
        // 미래에 받아올 값
        Future<String> name = Future.value('코드팩토리');
        Future<int> number = Future.value(1);
        Future<bool> isTrue = Future.value(true);
      
        addNumbers(1, 1);
        addNumbers(2, 2);
      }
      
      void addNumbers(int number1, int number2) async {
        print('계산 시작 : $number1 + $number2');
      
        // 서버 시뮬레이션
        await Future.delayed(Duration(seconds: 2), () {
          print('계산 완료:  $number1 + $number2 = ${number1 + number2}');
        });
      
        print('함수 완료 : $number1 + $number2');
      }
    • 코드3
      void main() async {
        // Future - 미래
        // 미래에 받아올 값
        Future<String> name = Future.value('코드팩토리');
        Future<int> number = Future.value(1);
        Future<bool> isTrue = Future.value(true);
      
      	await addNumbers(1, 1);
      	await addNumbers(2, 2);
      }
      
      Future<void> addNumbers(int number1, int number2) async {
        print('계산 시작 : $number1 + $number2');
      
        // 서버 시뮬레이션
        await Future.delayed(Duration(seconds: 2), () {
          print('계산 완료:  $number1 + $number2 = ${number1 + number2}');
        });
      
        print('함수 완료 : $number1 + $number2');
      }
    • 코드4
      void main() async {
        // Future - 미래
        // 미래에 받아올 값
        Future<String> name = Future.value('코드팩토리');
        Future<int> number = Future.value(1);
        Future<bool> isTrue = Future.value(true);
      
        final result1 = await addNumbers(1, 1);
        final result2 = await addNumbers(2, 2);
      
        print('result1: $result1');
        print('result2: $result2');
        print('result1+result2: ${result1 + result2}');
      }
      
      Future<int> addNumbers(int number1, int number2) async {
        print('계산 시작 : $number1 + $number2');
      
        // 서버 시뮬레이션
        await Future.delayed(Duration(seconds: 2), () {
          print('계산 완료:  $number1 + $number2 = ${number1 + number2}');
        });
      
        print('함수 완료 : $number1 + $number2');
      
        return number1 + number2;
      }
    • 코드5
      Future<void> saveDb(user) async {
        print('saving $user to db');
      }
      
      Future<void> sendEmail(user) async {
        await Future.delayed(Duration(seconds: 2));
        print('sending email to $user');
        database.add(user);
      }
      
      Future<String> getResult(user) async {
        return 'success register $user';
      }
    • 코드6 (async - await를 사용한 이메일 보내기)
      import 'callback_test.dart';
      
      void main(List<String> arguments) async {
        final user = {
          'email': 'abc@abc.com',
          'password': '123456',
          'name': 'John Doe',
        };
      
        saveDb(user)
            .then((_) => sendEmail(user))
            .catchError((err) => print('save db error: $err')) // 예외 발생
            .catchError((err) =>
                throw Exception('error send email')) // throw Exception()으로 예외를 발생
            .then((_) => getResult(user))
            .then((value) => print(value))
            .whenComplete(() => print('완료')); // whenComplete()는 성공, 실패 관계없이 실행
      
        final String result = await register(user);
        print(result);
      }
      
      Future<void> saveDb(user) async {
        print('saving $user to db');
      }
      
      Future<void> sendEmail(user) async {
        await Future.delayed(Duration(seconds: 2));
        // await Future.error(Exception('error save db'));
        print('sending email to $user');
        database.add(user);
      }
      
      Future<String> getResult(user) async {
        return 'success register $user';
      }
      
      Future<String> register(user) async {
        await saveDb(user);
        await sendEmail(user);
        return await getResult(user);
      }
    • 코드7
      void main(List<String> arguments) async {
        Stopwatch stopwatch = Stopwatch()..start();
      
        await printInts();
      
        print(stopwatch.elapsed);
      }
      
      Future<void> printInts() async {
        List<int> resultList = [
          await getInt1(),
          await getInt2(),
          await getInt3(),
          await getInt4(),
          await getInt5(),
        ];
        print(resultList);
      }
      
      Future<int> getInt1() async {
        await Future.delayed(Duration(seconds: 1));
        return 1;
      }
      
      Future<int> getInt2() async {
        await Future.delayed(Duration(seconds: 1));
        return 2;
      }
      
      Future<int> getInt3() async {
        await Future.delayed(Duration(seconds: 1));
        return 3;
      }
      
      Future<int> getInt4() async {
        await Future.delayed(Duration(seconds: 1));
        return 4;
      }
      
      Future<int> getInt5() async {
        await Future.delayed(Duration(seconds: 1));
        return 5;
      }

병렬처리

  • 병렬처리는 동시에 여러가지 일을 진행하는 것이다.
  • Future 함수는 await없이 사용하면 동시에 여러개를 실행할 수 있다.
    • 코드1
      void main(List<String> arguments) async {
        Stopwatch stopwatch = Stopwatch()..start();
      
        await printInts();
      
        print(stopwatch.elapsed);
      
      }
      
      Future<void> printInts() async {
        List<int> resultList = [
          await getInt1(),
          await getInt2(),
          await getInt3(),
          await getInt4(),
          await getInt5(),
        ];
        print(resultList);
      }
      
      Future<int> getInt1() async {
        await Future.delayed(Duration(seconds: 1));
        return 1;
      }
      
      Future<int> getInt2() async {
        await Future.delayed(Duration(seconds: 1));
        return 2;
      }
      
      Future<int> getInt3() async {
        await Future.delayed(Duration(seconds: 1));
        return 3;
      }
      
      Future<int> getInt4() async {
        await Future.delayed(Duration(seconds: 1));
        return 4;
      }
      
      Future<int> getInt5() async {
        await Future.delayed(Duration(seconds: 1));
        return 5;
      }
    • 코드2 (future.await)
      void main(List<String> arguments) async {
        Stopwatch stopwatch = Stopwatch()..start();
      
       await printParallelInts();
        print(stopwatch.elapsed);
      }
      
      Future<void> printParallelInts() async {
        List<Future<int>> futureList = [
           getInt1(),
           getInt2(),
           getInt3(),
           getInt4(),
           getInt5(),
        ];
        List<int> resultList = await Future.wait(futureList);
      
        print(resultList);
      }
      
      Future<int> getInt1() async {
        await Future.delayed(Duration(seconds: 1));
        return 1;
      }
      
      Future<int> getInt2() async {
        await Future.delayed(Duration(seconds: 1));
        return 2;
      }
      
      Future<int> getInt3() async {
        await Future.delayed(Duration(seconds: 1));
        return 3;
      }
      
      Future<int> getInt4() async {
        await Future.delayed(Duration(seconds: 1));
        return 4;
      }
      
      Future<int> getInt5() async {
        await Future.delayed(Duration(seconds: 1));
        return 5;
      }

Stream

  • Stream은 하나의 함수를 실행시키지만 함수가 한번만 호출되서 끝나는 게 아닌 계속해서 반환값으로 처리된 값이나 변화되는 값을 return과 비슷한 yield 키워드에 적힌 값을 반환하는 형태의 객체다.
  • Stream은 지속적으로 값을 반환 받을 때 사용한다.
    • Future는 반환값을 한번 받을 때 사용한다.
  • **async***
    • 반환값이 Stream 형태인 함수는 async*를 달아야 한다.
    • 옆에 별표가 붙은 것은 Future의 async 키워드와 차이를 주기 위해서 그렇다.
  • **yield**
    • 하나의 값을 return처럼 반환할 값에 쓰이는 키워드
  • **yield***
    • yield* 키워드를 이용해 Iterable 또는 Stream 함수를 재귀적으로 사용할 수 있다.
      • 코드
        void main() async {
          await for(int i in testStream(0))
            print(i);
        }
        
        Stream<int> testStream(int i) async* {
            yield i;
            if (i < 10)
              yield* testStream(i + 1);
        }
  • 데이터 흐름 표현
    • 코드1
      void main() {
        var stream = Stream.fromIterable([1, 2, 3]);
      
        stream.listen((event) {
          print(event);
        });
      }
    • 코드2
      import 'dart:async';
      
      void main() {
        final controller = StreamController();
        final stream = controller.stream;
      
        final streamListener1 = stream.listen((val) {
          print('Listener 1 : $val');
        });
      
      //   final streamListener2 = stream.listen((val) {
      //     print('Listener 2 : $val');
      //   });
      
        controller.sink.add(1);
        controller.sink.add(2);
        controller.sink.add(3);
        controller.sink.add(4);
        controller.sink.add(5);
      }
    • 코드3
      import 'dart:async';
      
      void main() {
        final controller = StreamController();
        final stream = controller.stream.asBroadcastStream();
      
        final streamListener1 = stream.listen((val) {
          print('Listener 1 : $val');
        });
      
        final streamListener2 = stream.listen((val) {
          print('Listener 2 : $val');
        });
      
        controller.sink.add(1);
        controller.sink.add(2);
        controller.sink.add(3);
        controller.sink.add(4);
        controller.sink.add(5);
      }
    • 코드4
      import 'dart:async';
      
      void main() {
        final controller = StreamController();
        final stream = controller.stream.asBroadcastStream();
      
        final streamListener1 = stream.where((val) => val % 2 == 0).listen((val) {
          print('Listener 1 : $val');
        });
      
        final streamListener2 = stream.where((val) => val % 2 == 1).listen((val) {
          print('Listener 2 : $val');
        });
      
        controller.sink.add(1);
        controller.sink.add(2);
        controller.sink.add(3);
        controller.sink.add(4);
        controller.sink.add(5);
      }
    • 코드5
      import 'dart:async';
      
      void main() {
        cal(1).listen((val) {
          print('cal(1) : $val');
        });
      }
      
      // Future<int> cal(int num) async{
      //   for (int i = 0; i < 5; i++) {
      //     return i * num;
      //   }
      // }
      
      Stream<int> cal(int num) async* {
        for (int i = 0; i < 5; i++) {
          yield i * num;
        }
      }
    • 코드6
      import 'dart:async';
      
      void main() {
        cal(2).listen((val) {
          print('cal(2) : $val');
        });
      
      }
      
      Stream<int> cal(int num) async* {
        for (int i = 0; i < 5; i++) {
          yield i * num;
      
          await Future.delayed(Duration(seconds: 1));
        }
      }
    • 코드7
      import 'dart:async';
      
      void main() {
        cal(2).listen((val) {
          print('cal(2) : $val');
        });
      
        cal(4).listen((val) {
          print('cal(4) : $val');
        });
      }
      
      Stream<int> cal(int num) async* {
        for (int i = 0; i < 5; i++) {
          yield i * num;
      
          await Future.delayed(Duration(seconds: 1));
        }
      }
    • 코드8
      import 'dart:async';
      
      void main() {
        playAllStream().listen((val) {
          print(val);
        });
      }
      
      Stream<int> playAllStream() async* {
        yield* cal(1);
        yield* cal(1000);
      }
      
      Stream<int> cal(int num) async* {
        for (int i = 0; i < 5; i++) {
          yield i * num;
      
          await Future.delayed(Duration(seconds: 1));
        }
      }

0개의 댓글

관련 채용 정보