Dart Programmer 되기 [25]

< Dart 기초문법 – Asynchrony Support >

프로그램이 동작하다 보면, 시간이 많이 소요되는 작업이 있습니다. 대표적인 경우가, 대량의 데이타를 읽고 처리하거나, 아니면 저속의 입출력 장치를 다루는 경우입니다. Dart 언어의 Async 기능은 이런 시간이 많이 걸리는 작업이 완료될 때까지 기다리지 않고 바로 다음 동작을 이어 나갈수 있도록 합니다. 이런 기능이 포함된 함수들은 리턴 값으로 Future 혹은 Stream 객체를 반환 합니다. 그리고 이런 기능이 가능하도록 하는 문법은 async와 await 입니다.

async, wait and Future Syntax

darttutorial-25-01.dart에서 heavyLoadFunction()을 보면, 반복문을 수행하면서 제법 큰 숫자에 대한 덧셈을 수행합니다. 만약 프로그램이 덧셈을 수행하는 시간 동안 다른 작업을 할 필요가 없다면, 단순히 기다리면 됩니다. 하지만, 만약 덧셈을 수행하는 동안에 다른 작업을 동시에 진행하는 것이 효과적이라면, 덧셈을 수행하는 동안에 다른 작업이 동시에 이루어 지도록 할 수 있습니다. 이를 위해서,
heavyLoadFunction()를 호출하는 부분을 보면, takeTime() 안 인 것을 볼 수 있습니다. 그런데, heavyLoadFunction()를 호출하는 앞에 새로운 문법이 있으니, await 입니다. 영어 그대로 해석하면 기다리라는 의미입니다. 즉, takeTime()은
heavyLoadFunction()를 실행하되, 이 함수의 실행이 시작되면 (이 함수가 완료되기 전에) 결과가 리턴되기를 기다리지만, takeTime()을 호출한 쪽은 이 결과를 기다리지 않고 일단 다른 작업을 수행하도록 합니다. 이런 의미에서 await 구문이 들어 있는 함수의 리턴 값은 Future라는 타입입니다. 이 의미는 “미래에 결과가 리턴될거야. 기다리다가 결과가 만들어 지면, 미래에 알려줄테니, 일단은 동시에 해야할 일이 있으면 수행하는게 좋겠어”라는 의미입니다. 이렇게 await 구문을 사용하여 미래에 결과를 리턴하는 함수는 async 함수라고 하며, async 구문을 함수 이름과 { … } 사이에 기입해야 합니다. 프로그램 소스코드를 보면, main()도 async 함수 인 것을 볼 수 있습니다. takeTime()과 비슷한 구조로서, takeTime()을 await로 호출하고, 이의 결과가 오기를 기다리는 작업을 하는 형태인 것을 볼 수 있습니다.

// darttutorial-25-01.dart

heavyLoadFunction() {
  print("[heavyLoadFunction(): started.");
  var sum = 0;
  for(var tmp = 0; tmp < 100000000; tmp++) {
    sum += tmp;
  }
  print("[heavyLoadFunction(): completed.");
  return sum;
}

Future takeTime() async {
  print("[takeTime(): started.");
  var status = await heavyLoadFunction();
  print("[takeTime(): completed and result is $status.");
  return status;
}

Future main() async {
  print("[main(): started.");
  var status = await takeTime();
  print("[main(): result is $status.");
}

darttutorial-25-01.dart 프로그램의 결과가 아래와 같이 나타나 있습니다. 수행 결과를 이해하면 다음과 같습니다. 먼저, main()가 실행하면서, takeTime()를 호출합니다. 단, 결과가 리턴될때까진 기다리겠다는 의미에서 await 구문을 사용합니다. 따라서, main()의 수행은 일단 이지점에서 멈추며, 프로그램의 수행은 takeTime()함수로 이동합니다. 순차적으로 heavyLoadFunction()이 호출되는 것을 볼수 있으며, 마찬가지로 await 상태를 유지합니다. 이 함수의 동작이 종료되면, await에 의해서 결과를 기다리는 takeTime()으로 복귀하며, 다시 이 함수의 결과를 기다리던 main()으로 돌아온 후, 가져온 결과를 화면에 출력하고, 프로그램을 종료 하게 됩니다.

[main(): started.
[takeTime(): started.
[heavyLoadFunction(): started.
[heavyLoadFunction(): completed.
[takeTime(): completed and result is 4999999950000000.
[main(): result is 4999999950000000.

Asynchronous Operation

Async 동작을 설명하고 있지만, 첫번쨰로 설명한 darttutorial-25-01.dart 프로그램은 main() – takeTime() – heavyLoadFunction()이 꼬리에 꼬리를 물면서 결과를 기다리기에 사실상 async 동작이기 보다는 sync 동작인 동기화된 방법이라고 볼수 있습니다. 만약 main()에서 굳이 takeTime()의 결과를 취하지 않는 다면 어떨까요? takeTime()이 알아서 작업을 하고, 독자적으로 동작하되, takeTime()이 독립적인 작업을 하는 동안 main()은 다른 동작을 동시에 한다고 생각해 봅니다. 이렇게 수정한 프로그램이 darttutorial-25-02.dart 입니다. 변경된 부분은 main() 뿐입니다. 앞서 darttutorial-25-01.dart에서는 main()이 takeTime()에서 결과를 리턴할때까지 대기를 했지만, 지금은 takeTime() 함수를 호출할 뿐, await로 대기 하지 않습니다. 따라서, main()은 takeTime()을 호출해서 heavyLoadFunction()을 통한 긴 작업을 시작하지만, 이와 함께 main()에서도 다른 작업을 동시에 수행할 수 있도록 작업을 이어 갑니다.

// darttutorial-25-02.dart

heavyLoadFunction() {
  print("[heavyLoadFunction(): started.");
  var sum = 0;
  for(var tmp = 0; tmp < 100000000; tmp++) {
    sum += tmp;
  }
  print("[heavyLoadFunction(): completed.");
  return sum;
}

Future takeTime() async {
  print("[takeTime(): started.");
  var status = await heavyLoadFunction();
  print("[takeTime(): completed and result is $status.");
  return status;
}

main() {
  print("[main(): started.");
  var status = takeTime();
  print("[main(): result is $status.");
}

darttutorial-25-02.dart 프로그램의 수행결과가 아래와 같이 나타나 있습니다. 달라진 부분은, takeTime()이 종료되기전에, main()으로 프로그램의 수행이 이어지면서, takeTime()의 리턴 값이 Future 타입인 점입니다. 그리고 나서, 시간이 흐른후 takeTime() 함수가 종료되는 것을 볼수 있습니다. 이와 같이, main()이 시작하면, 독립적인 프로세스/함수(들)의 동작을 시작시키고, 서로가 독립적이며 병렬적으로 동작하게 하는 것이 가능합니다.

[main(): started.
[takeTime(): started.
[heavyLoadFunction(): started.
[heavyLoadFunction(): completed.
[main(): result is Instance of 'Future<dynamic>'.
[takeTime(): completed and result is 4999999950000000.

Multiple await Operation

async 함수 안에 await 구문을 하나만 사용하였지만, darttutorial-25-03.dart 프로그램 처럼, 복수의 await 구문의 사용도 가능합니다. takeTime() 함수에 연이어 await 구문이 두개 나타나 있으며, 첫번째 await 구문의 완료후, 이어서 두번째 await 구문이 실행됩니다.

// darttutorial-25-03.dart

heavyLoadFunction1() {
  print("[heavyLoadFunction1(): started.");
  var sum = 0;
  for(var tmp = 0; tmp < 100000000; tmp++) {
    sum += tmp;
  }
  print("[heavyLoadFunction1(): completed.");
  return sum;
}

heavyLoadFunction2() {
  print("[heavyLoadFunction2(): started.");
  var sum = 0;
  for(var tmp = 0; tmp < 1000000; tmp++) {
    sum += tmp;
  }
  print("[heavyLoadFunction2(): completed.");
  return sum;
}

Future takeTime() async {
  print("[takeTime(): started.");
  var status1 = await heavyLoadFunction1();
  var status2 = await heavyLoadFunction2();
  print("[takeTime(): completed and result is $status1 $status2.");
  return status1 + status2;
}

Future main() async {
  print("[main(): started.");
  var status = await takeTime();
  print("[main(): result is $status.");
}

darttutorial-25-03.dart 프로그램의 수행결과가 아래에 나타나 있습니다. main()은 darttutorial-25-01.dart 처럼 async 결과를 기다리는 구조로 되어 있기에, Future 타입이 아닌, 제대로된 결과를 출력하는 것을 볼수 있습니다. 또한, takeTime() 함수를 통해서 heavyLoadFunction1()과 heavyLoadFunction2()가 연이어 수행되는 것을 확인 할 수 있습니다.

[main(): started.
[takeTime(): started.
[heavyLoadFunction1(): started.
[heavyLoadFunction1(): completed.
[heavyLoadFunction2(): started.
[heavyLoadFunction2(): completed.
[takeTime(): completed and result is 4999999950000000 499999500000.
[main(): result is 5000499949500000.

Simultaneous Asynchronous Operations

이제 제대로 main()에서 복수의 작업을 동시에 수행시키는 방식으로 프로그램을 수정해 보겠습니다. darttutorial-25-04.dart 프로그램을 보면, 두개의 takeTimeA()와 takeTimeB()가 있습니다. 각각은 앞서와 유사하게 많은 계싼을 하는 함수들을 순차적으로 수행합니다. 하고자 하는 것은 takeTimeA()와 takeTimeB()가 독립적으로 동시에 동작하는 것이며, 이어서 설명할 수행 결과를 보면, async 함수가 아닌 main()을 통해서 각각을 호출하는 것을 볼 수 있습니다.

// darttutorial-25-04.dart

heavyLoadFunction1() {
  print("[heavyLoadFunction1(): started.");
  var sum = 0;
  for(var tmp = 0; tmp < 100000000; tmp++) {
    sum += tmp;
  }
  print("[heavyLoadFunction1(): completed.");
  return sum;
}

heavyLoadFunction2() {
  print("[heavyLoadFunction2(): started.");
  var sum = 0;
  for(var tmp = 0; tmp < 1000000; tmp++) {
    sum += tmp;
  }
  print("[heavyLoadFunction2(): completed.");
  return sum;
}

heavyLoadFunction3() {
  print("[heavyLoadFunction3(): started.");
  var sum = 0;
  for(var tmp = 0; tmp < 10000000; tmp++) {
    sum += tmp;
  }
  print("[heavyLoadFunction3(): completed.");
  return sum;
}

heavyLoadFunction4() {
  print("[heavyLoadFunction4(): started.");
  var sum = 0;
  for(var tmp = 0; tmp < 100000; tmp++) {
    sum += tmp;
  }
  print("[heavyLoadFunction4(): completed.");
  return sum;
}

Future takeTimeA() async {
  print("[takeTimeA(): started.");
  var status1 = await heavyLoadFunction1();
  var status2 = await heavyLoadFunction2();
  print("[takeTimeA(): completed and result is $status1 $status2.");
}

Future takeTimeB() async {
  print("[takeTimeB(): started.");
  var status1 = await heavyLoadFunction3();
  var status2 = await heavyLoadFunction4();
  print("[takeTimeB(): completed and result is $status1 $status2.");
}

main() {
  print("[main(): started.");
  takeTimeA();
  takeTimeB();
  print("[main(): completed.");
}

darttutorial-25-04.dart의 수행결과가 아래와 같이 나타납니다. main()의 시작과 함께 takeTimeA()를 실행하였는데, 이를 통한 heavyLoadFunction1()과 heavyLoadFunction2()가 모두 마쳐지기 전에, takeTimeB()도 시작을 하는 것을 볼 수 있습니다. 따라서, 당분간 takeTimeA()와 takeTimeB()가 동시에 동작하는 시간이 존재함을 볼 수 있습니다. 더불어, main()이 async 함수가 아니기에 마지막 구문을 실행해 버리는 것을 볼 수 있습니다. 이후 동시에 수행 중인 작업들이 각각 마쳐지고, 계산한 각자의 결과를 화면에 출력후 프로그램을 종료하는 것을 볼수 있습니다.

[main(): started.
[takeTimeA(): started.
[heavyLoadFunction1(): started.
[heavyLoadFunction1(): completed.
[takeTimeB(): started.
[heavyLoadFunction3(): started.
[heavyLoadFunction3(): completed.
[main(): completed.
[heavyLoadFunction2(): started.
[heavyLoadFunction2(): completed.
[heavyLoadFunction4(): started.
[heavyLoadFunction4(): completed.
[takeTimeA(): completed and result is 4999999950000000 499999500000.
[takeTimeB(): completed and result is 49999995000000 4999950000.

Synchronous Operations using await Syntax

darttutorial-25-05.dart 프로그램은 다시 main()에 await 구문을 삽입함으로, takeTimeA()와 takeTimeB()의 작업을 마쳐야지 main()을 종료하도록 조정하였습니다. 이렇게 함으로써, 독립적으로 동시에 수행되지 않고, 순차적으로 수행하도록 변경한 것을 볼 수 있습니다.

// darttutorial-25-05.dart

heavyLoadFunction1() {
  print("[heavyLoadFunction1(): started.");
  var sum = 0;
  for(var tmp = 0; tmp < 100000000; tmp++) {
    sum += tmp;
  }
  print("[heavyLoadFunction1(): completed.");
  return sum;
}

heavyLoadFunction2() {
  print("[heavyLoadFunction2(): started.");
  var sum = 0;
  for(var tmp = 0; tmp < 1000000; tmp++) {
    sum += tmp;
  }
  print("[heavyLoadFunction2(): completed.");
  return sum;
}

heavyLoadFunction3() {
  print("[heavyLoadFunction3(): started.");
  var sum = 0;
  for(var tmp = 0; tmp < 10000000; tmp++) {
    sum += tmp;
  }
  print("[heavyLoadFunction3(): completed.");
  return sum;
}

heavyLoadFunction4() {
  print("[heavyLoadFunction4(): started.");
  var sum = 0;
  for(var tmp = 0; tmp < 100000; tmp++) {
    sum += tmp;
  }
  print("[heavyLoadFunction4(): completed.");
  return sum;
}

Future takeTimeA() async {
  print("[takeTimeA(): started.");
  var status1 = await heavyLoadFunction1();
  var status2 = await heavyLoadFunction2();
  print("[takeTimeA(): completed and result is $status1 $status2.");
}

Future takeTimeB() async {
  print("[takeTimeB(): started.");
  var status1 = await heavyLoadFunction3();
  var status2 = await heavyLoadFunction4();
  print("[takeTimeB(): completed and result is $status1 $status2.");
}

Future main() async {
  print("[main(): started.");
  await takeTimeA();
  await takeTimeB();
  print("[main(): completed.");
}

수행 결과가 아래와 같이 나타나 있으며, 한번에 하나의 기능 만을 수행하면서, 프로그램이 물 흐르듯이 진행되어 종료되는 것을 볼 수 있습니다.

[main(): started.
[takeTimeA(): started.
[heavyLoadFunction1(): started.
[heavyLoadFunction1(): completed.
[heavyLoadFunction2(): started.
[heavyLoadFunction2(): completed.
[takeTimeA(): completed and result is 4999999950000000 499999500000.
[takeTimeB(): started.
[heavyLoadFunction3(): started.
[heavyLoadFunction3(): completed.
[heavyLoadFunction4(): started.
[heavyLoadFunction4(): completed.
[takeTimeB(): completed and result is 49999995000000 4999950000.
[main(): completed.

마무리

Dart 언어는 await, async, Future 문법을 사용함으로써, 지연이 발생하는 작업이 있는 경우, 이의 수행을 진행하면서, 이에 독립적 일수 있는 다른 작업의 수행을 이어가는 것이 가능합니다. 예제에서 살펴보지는 않았지만, async 함수안에서 입출력과 관련한 반복적인 작업이 필요한 경우는 async for 구문을 사용하는 것이 필요할 수 있습니다. 추후 async 기능의 개발에서 오류가 발생하는 경우에는 해당 문법을 살펴보기 바랍니다.

Creative Commons License (CC BY-NC-ND)

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다