Foggy day

[Dart] Future 사용법(동기/비동기 작업 클래스) 본문

Flutter/Dart 문법

[Dart] Future 사용법(동기/비동기 작업 클래스)

jinhan38 2023. 4. 28. 00:23

 

이번 포스팅에서는 Future의 사용법에 대해 알아보겠습니다. 

Flutter에서는 Future와 Stream을 이용해 비동기 프로그래밍을 구현할 수 있습니다. 비동기와 동기를 간략하게 구분해 보자면 동기는 직렬적으로 작업들을 실행하는 것이고, 비동기는 병렬적/직렬적으로 작업들을 실행시키는 것입니다.

 

비동기 작업은 네트워크 통신을 할 때 많이 사용합니다. 특정 api를 호출해서 받아온 데이터를 이용해 또다른 api를 호출하는 로직이 많기 때문입니다. 여기서는 비동기에 대해서는 간략하게 언급만 하고, 예제와 함께 Future의 사용법에 대해 알아보겠습니다.

 

 

1. 문법의 형태

2. Future.microtask

3. Future.delayed

4. Future.any

5. Future.doWhile

6. Future.forEach

7. Future.wait

8. Future.sync

9. Future.value

 

 

 

1. 문법의 형태

Future를 사용하기 위해서는 async, await 키워드와 then 함수를 알아야 합니다.

async와 await을 이용해서는 코드를 동기적으로 실행할 수 있습니다. 다시 말해서 await을 붙인 Future는 해당 Future의 작업이 완료된 후에 다음 코드로 진행하 수 있습니다. 

  /// 함수 내에서 await을 이용하기 위해서는 async 키워드를 중괄호 앞에 반드시 붙여야 합니다.
  /// 그리고 동기적으로 실행하려는 Future 앞에 await을 붙여줍니다.
  void futureBasic() async {
    // await 키워드가 있는 Future가 완료된 후에 다음 코드로 진행됨 
    await Future.delayed(const Duration(seconds: 1)); // 1초 동안 Future.delayed 함수가 실행됨
    
    print("future basic");
  }

 

then은 Future 클래스에 있는 함수로 Future의 결과가 완료되면 호출되는 함수입니다. await과는 다르게 동기방식으로 코드의 진행을 막지 않습니다. 

  void futureThen() {
    print("futureThen");
    Future.delayed(const Duration(seconds: 1)).then((value) {
      print('1초 후 delay 완료');
    });
    print("future delay 이후 ");
  }
  
  
  
  Console
  ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
  futureThen
  future delay 이후 
  1초 후 delay 완료

 

 

 

2. Future.microtask

microtask는 다른 Future함수들보다 먼저 작업이 완료됩니다. 그래서 간단한 계산 작업을 비동기식으로 완료하려는 경우에 적합합니다.

  void futureMicroTask() async{
    Future(()=>print("future A"));
    Future(()=>print("future B"));
    Future.microtask(() => print("microtask A"));
    Future.microtask(() => print("microtask B"));
  }
  
  
Console
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
microtask A
microtask B
future A
future B

 

 

 

3. Future.delayed

Future.delayed는 이름 그대로 지연시간(delay)를 주고 싶을 때 사용합니다. 함수에는 지연 시간, 지연 시간이 완료된 경우 실행할 함수(computation)를 인자로 받습니다. Stopwatch는 시간 경과를 체크할 수 있는 클래스입니다. delay가 정상적으로 됐는지를 체크하기 위해 추가했습니다. 

  void futureDelay() async {
    final Stopwatch stopwatch = Stopwatch();
    stopwatch.start();
    print('futureDelay 시작 : ${stopwatch.elapsed}');
    await Future.delayed(const Duration(milliseconds: 1000));
    print('futureDelay 1 : ${stopwatch.elapsed}');
    await Future.delayed(const Duration(milliseconds: 2000));
    print('futureDelay 2 : ${stopwatch.elapsed}');
    await Future.delayed(const Duration(milliseconds: 3000));
    print('futureDelay 끝 : ${stopwatch.elapsed}');
    stopwatch
      ..stop()
      ..reset();

    Future.delayed(
      const Duration(milliseconds: 1000),
      () => print('delay in'),
    ).then(
      (value) => print('delay out'),
    );
  }
  
  
  
  Console 
  ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
  futureDelay 시작 : 0:00:00.000100
  futureDelay 1 : 0:00:01.883701
  futureDelay 2 : 0:00:04.883400
  futureDelay 끝 : 0:00:08.883201
  delay in
  delay out

 

 

 

4. Future.any

Future.any는 Future 함수를 배열로 받습니다. 그리고 가장 먼저 완료된 Future의 결과를 반환 합니다. 

아래 예제에서는 delay를 1초, 2초, 3초 주는 Future 함수가 있습니다. 그리고 3개의 delay함수를 Future.any에 넣어서 실행했습니다. 넣은 delay함수는 2초, 1초, 3초 딜레이 순서입니다. Future.any의 결과 값인 result 변수에는 delay1000 함수의 결과가 입력됩니다. 왜냐하면 Future.any는 가장 먼저 완료된 Future함수의 결괏값만 리턴하기 때문입니다. 그래서 result에는 delay1000 함수의 리턴값인 String 타입의 "delay1000 result"라는 문자가 할당됐습니다.

Future<void> futureAny() async {
  final Stopwatch stopwatch = Stopwatch();
  stopwatch.start();
  final result = await Future.any([
    delay2000(),
    delay1000(),
    delay3000(),
  ]);
  print(result);
  print('doSomething() executed in ${stopwatch.elapsed}');
  stopwatch
    ..stop()
    ..reset();
}

Future<String> delay1000() async {
  await Future.delayed(const Duration(milliseconds: 1000));
  print('delay1000 time : ${DateTime.now()}');
  return "delay1000 result";
}

Future<String> delay2000() async {
  await Future.delayed(const Duration(milliseconds: 2000));
  print('delay2000 time : ${DateTime.now()}');
  return "delay2000 result";
}

Future<String> delay3000() async {
  await Future.delayed(const Duration(milliseconds: 3000));
  print('delay3000 time : ${DateTime.now()}');
  return "delay3000 result";
}


Console
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
delay1000 time : 2023-04-27 23:35:01.987
delay1000 result
doSomething() executed in 0:00:01.880800
delay2000 time : 2023-04-27 23:35:02.993
delay3000 time : 2023-04-27 23:35:03.991

 

 

 

5. Future.doWhile

Future.doWhile은 while문을 동기, 비동기 방식으로 사용할 수 있습니다. Future.doWhile 앞에 await 키워드를 사용하면 Future.doWhile이 종료된 후 다음 코드가 진행됩니다(동기). 하지만 await 키워드가 없다면 비동기 방식으로 진행되며, Future.doWhile이 종료되지 않더라도 다음 코드가 실행됩니다. true를 리턴하면 계속 진행되고, false를 리턴하면 종료됩니다. 

  Future<void> futureDoWhile() async {
    final Stopwatch stopwatch = Stopwatch();

    stopwatch.start();
    int count = 0;
    Future.doWhile(() async {
      count++;
      if (count == 10) {
        stopwatch
          ..stop()
          ..reset();
        return false;
      }
      await Future.delayed(
        const Duration(milliseconds: 1000),
        () => print('duration : ${stopwatch.elapsed}'),
      );
      return true;
    }).then((value) => print('Future.doWhile result : $value'));
    print('doSomething() executed in ${stopwatch.elapsed}');
  }
  
  
  Console 
  ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
  doSomething() executed in 0:00:00.001200
  duration : 0:00:01.384500
  duration : 0:00:03.375500
  duration : 0:00:05.384200
  duration : 0:00:06.388100
  duration : 0:00:08.384100
  duration : 0:00:10.381900
  duration : 0:00:12.376800
  duration : 0:00:14.374400
  duration : 0:00:16.380700
  Future.doWhile result : null

 

 

 

6. Future.forEach

Future.forEach는 for문과 비동기 처리를 같이 사용하고 싶을 때 사용합니다. 

Future.forEach 함수에는 제네릭 타입<T>과 Iterable <T> 타입의 인자, 그리고 action(callback) 함수를 받습니다. Iterable 인자의 값들이 순서대로 action 함수로 전달되고, action 함수는 비동기로 실행할 수 있습니다. 

void main() {
  futureForEachTest();
}
  final Stopwatch stopwatch = Stopwatch();

  Future<void> futureForEachTest() async {
    List<int> valueList = [1, 2, 3];
    stopwatch.start();
    dynamic result = await Future.forEach<int>(valueList, (element) async {
      // return delay1000();
      if (element == 1) {
        print(await delay1000());
      } else if (element == 2) {
        print(await delay2000());
      } else if (element == 3) {
        print(await delay3000());
      }
    });
    print('result : $result');
    print('doSomething() executed in ${stopwatch.elapsed}');
    stopwatch
      ..stop()
      ..reset();
  }


  Future<String> delay1000() async {
    await Future.delayed(const Duration(milliseconds: 1000));
    print('delay1000 time : ${stopwatch.elapsed}');
    return "delay1000 result";
  }

  Future<String> delay2000() async {
    await Future.delayed(const Duration(milliseconds: 2000));
    print('delay2000 time : ${stopwatch.elapsed}');
    return "delay2000 result";
  }

  Future<String> delay3000() async {
    await Future.delayed(const Duration(milliseconds: 3000));
    print('delay3000 time : ${stopwatch.elapsed}');
    return "delay3000 result";
  }
  
  
  
  Console
  ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
  delay1000 time : 0:00:01.800200
  delay1000 result
  delay2000 time : 0:00:04.809400
  delay2000 result
  delay3000 time : 0:00:08.807700
  delay3000 result
  result : null
  doSomething() executed in 0:00:08.807900

 

 

 

7. Future.wait

Future.wait은 여러개의 Future를 비동기 방식으로 호출하고 싶을 때 사용합니다. 개개의 Future가 then 함수를 구현하면, Future.wait의 리턴 값은 null이 됩니다. then을 구현하지 않는다면 모든 Future가 완료 됐을 때 각 Future들의 결괏값이 Future.wait의 결괏값에 배열로 들어옵니다. 

아래 예제에서 Future.wait의 결과값은 [delay1000 result, null, delay3000 result]입니다. 두 번째로 입력한 Future 함수인 delay2000은 then 함수를 사용했기 때문에 결괏값 배열의 두 번째는 null이 됐습니다.

void main() {
  futureWaitTest();
}

final Stopwatch stopwatch = Stopwatch();

 Future<void> futureWaitTest() async {
  stopwatch.start();
  var list = await Future.wait([
    delay1000(),
    delay2000().then((value) {
      print('delay2000 완료');
      return "hello world";
    }),
    delay3000(),
  ]);
  print('list : $list');
  print('doSomething() executed in ${stopwatch.elapsed}');
  stopwatch
    ..stop()
    ..reset();
}


Future<String> delay1000() async {
  await Future.delayed(const Duration(milliseconds: 1000));
  print('delay1000 time : ${stopwatch.elapsed}');
  return "delay1000 result";
}

Future<String> delay2000() async {
  await Future.delayed(const Duration(milliseconds: 2000));
  print('delay2000 time : ${stopwatch.elapsed}');
  return "delay2000 result";
}

Future<String> delay3000() async {
  await Future.delayed(const Duration(milliseconds: 3000));
  print('delay3000 time : ${stopwatch.elapsed}');
  return "delay3000 result";
}

 

여러 개의 비동기 처리를 좀 더 쉽게 할 수 있는 패키지를 하나 추천합니다.

완료된 Future들 먼저 리턴해주는 콜백이 있고, 모든 Future가 완료 됐을 때 결괏값을 배열로 리턴 받을 수 있습니다. 

https://pub.dev/packages/future_bundle

 

future_bundle | Flutter Package

You can now process multiple Futures at once.

pub.dev

 

 

 

8. Future.sync

Future.sync는 computation 콜백을 바로 호출하는 future를 반환합니다. 만약 Future 타입을 반환하고 싶지만 바로 실행이 필요한 경우 사용하면 될 것 같습니다.

예제의 콘솔을 보면 Future.sync의 print가 동기 방식으로 바로 호출된 것을 볼 수 있습니다. 

  void futureSync() {
    print("aaa");
    Future.sync(() => print('future.sync value'));
    print("bbb");
  }
  
  
  Console
  ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
  aaa
  future.sync value
  bbb

 

 

 

9. Future.value

Future.value는 특정 값을 반환하는 Future 함수입니다.

  Future<void> futureValueTest() async {
    Future.value("future value test").then((value) => print("value : $value"));
    var result = await Future.value(50);
    print('result : $result');
  }
  
  
  Console
  ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
  value : future value test
  result : 50

 

 

 

 

참고하면 좋은 자료

 

https://web.archive.org/web/20170704074724/https://webdev.dartlang.org/articles/performance/event-loop

 

The Event Loop and Dart

Learn how Dart handles the event queue and microtask queue, so you can write better asynchronous code with fewer surprises.

web.archive.org

https://web.archive.org/web/20170704074724/https://webdev.dartlang.org/articles/performance/event-loop#darts-event-loop-and-queues

 

The Event Loop and Dart

Learn how Dart handles the event queue and microtask queue, so you can write better asynchronous code with fewer surprises.

web.archive.org

https://medium.com/dartlang/dart-asynchronous-programming-isolates-and-event-loops-bffc3e296a6a

 

Dart asynchronous programming: Isolates and event loops

Dart, despite being a single-threaded language, offers support for futures, streams, background work, and all the other things you need to…

medium.com

https://www.youtube.com/watch?v=vl_AaCgudcY