C++ Task

|

Task

C++ 에서는 Thread 외에도 비동기적으로 작업을 수행할 수 있는 태스크(Task를 지원합니다. 태스크는 <future> 헤더를 필요하며 Promise, Future 두 개의 컴포넌트로 구성됩니다. 이 두 컴포넌트는 서로 데이터 채널을 통해 연결됩니다.


Thread와 Task 예제

#include <iostream>
#include <future>
#include <thread>

using namespace std;

int main() {
  cout << "Hello, World!" << endl;

  // Thread
  int count = 100;
  thread t([&] { count = count + 100; });
  t.join();
  cout << "count: " << count << endl;
  
  // Task
  auto f = async([&] { return count * 2; });
  cout << "count: " << f.get() << endl;

  return 0;
}

위의 예제로 볼 수 있는 Thread와 Task의 차이는 다음과 같습니다.

기준 Thread Task
컴포넌트 생성자와 자식 Thread Promise와 Future
통신 공유 변수 채널
동기화 join()을 이용한 대기 get()을 이용한 블록킹 호출
Exception 발생시 Thread 및 부모 프로그램 종료됨 Promise에서 Future로 Exception을 throw 함

Thread에서 데이터 통신을 위한 공유 변수는 Mutex 등으로 안전하게 보호해야 하지만, Task에서는 통신 채널이 이미 보호를 받고 있는 상태이기 때문에 Mutex 등을 사용할 필요가 없습니다.

Thread에서 예외가 발생하면, 해당 Thread는 종료가 되며 Thread 생성자 및 전체 프로그램도 종료가 됩니다. 하지만, Task에서는 Exception을 Future에게 발생시켜 예외 처리를 하도록 합니다.


Future 및 Promise 예제

#include <iostream>
#include <future>
#include <thread>

using namespace std;

void add(promise<int> &&resultPromise, int a, int b) {
  resultPromise.set_value(a + b);
}

int main() {
  cout << "Hello, World!" << endl;

  promise<int> sumPromise;
  auto sumFuture = sumPromise.get_future();

  int a = 100;
  int b = 200;
  thread sumThread(add, move(sumPromise), a, b);

  cout << "a + b = " << sumFuture.get() << endl;

  sumThread.join();

  return 0;
}

MacOS에서 Sandbox Permission 추가하기

|

MacOS 프로그램들은 기본적으로 Sandbox에서 동작하고 있습니다. 따라서 네트워크나 공유 자원 등에 접근할 때 Permission을 획득해야 사용할 수 있는 경우가 있습니다.

대표적인 예로 이미지를 네트워크를 통해 가져오는 Image.network 함수를 사용할 경우 다음 오류가 발생합니다.

SocketException: Connection failed (OS Error: Operation not permitted, errno = 1)

이 경우 macos/Runner/DebugProfile.entitlements 파일에 다음 권한을 추가해주면 됩니다.

    <key>com.apple.security.network.client</key>
    <true/>


MacOS의 Permission 리스트

MacOS에서 요구하는 Permission 리스트는 여기에서 확인하실 수 있습니다.

또한 런타임 중에 확인해야 하는 Hardened Runtime 리스트는 여기에서 확인 가능합니다.

Flutter BottomNavigationBar 예제

|

하단 탭을 이용한 네비게이션 예제 코드입니다.

main.dart

import 'package:flutter/material.dart';

void main() => runApp(SnowDeerApp());

class SnowDeerApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SnowDeer App',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primaryColor: Colors.deepPurple,
      ),
      home: MainWidget(),
    );
  }
}

class MainWidget extends StatefulWidget {
  @override
  State createState() => _MainWidgetState();
}

class _MainWidgetState extends State<MainWidget> {
  var _index = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Hello Snowdeer'),
      ),
      body: Center(
        child: Text('Page Index: $_index'),
      ),
      bottomNavigationBar: BottomNavigationBar(
          currentIndex: _index,
          onTap: (index) {
            setState(() {
              _index = index;
            });
          },
          items: [
            BottomNavigationBarItem(
              title: Text('Home'),
              icon: Icon(Icons.home),
            ),
            BottomNavigationBarItem(
              title: Text('DoAction'),
              icon: Icon(Icons.email),
            ),
            BottomNavigationBarItem(
              title: Text('DoBehavior'),
              icon: Icon(Icons.favorite),
            ),
          ]),
    );
  }
}


실제 페이지 이동하는 예제

import 'package:flutter/material.dart';

void main() => runApp(SnowDeerApp());

class SnowDeerApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SnowDeer App',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primaryColor: Colors.deepPurple,
      ),
      home: MainWidget(),
    );
  }
}

class MainWidget extends StatefulWidget {
  @override
  State createState() => _MainWidgetState();
}

class _MainWidgetState extends State<MainWidget> {
  var _index = 0;
  var _pages = [
    HomePageWidget(),
    DoActionPageWidget(),
    DoBehaviorPageWidget(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Hello Snowdeer'),
        actions: [
          IconButton(
            icon: Icon(
              Icons.add,
              color: Colors.white,
            ),
            onPressed: () {},
          )
        ],
      ),
      body: _pages[_index],
      bottomNavigationBar: BottomNavigationBar(
          currentIndex: _index,
          onTap: (index) {
            setState(() {
              _index = index;
            });
          },
          items: [
            BottomNavigationBarItem(
              title: Text('Home'),
              icon: Icon(Icons.home),
            ),
            BottomNavigationBarItem(
              title: Text('DoAction'),
              icon: Icon(Icons.email),
            ),
            BottomNavigationBarItem(
              title: Text('DoBehavior'),
              icon: Icon(Icons.favorite),
            ),
          ]),
    );
  }
}

class HomePageWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('Home'),
    );
  }
}

class DoActionPageWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('DoAction'),
    );
  }
}

class DoBehaviorPageWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('DoBehavior'),
    );
  }
}

Flutter 화면 전환(복수 파일 이용)

|

Flutter에서의 화면 전환 예제를 여러 개의 파일로 나누어서 호출하도록 변경했습니다. 다른 파일을 가져올 때 다음과 같이 두 가지 방식으로 import 할 수 있습니다.

import 'first_screen_widget.dart';
import 'package:snowdeer_hello_flutter/second_screen_widget.dart';


main.dart

import 'package:flutter/material.dart';
import 'first_screen_widget.dart';

void main() => runApp(SnowDeerApp());

class SnowDeerApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SnowDeer App',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primaryColor: Colors.deepPurple,
      ),
      home: FirstScreenWidget(),
    );
  }
}


first_screen_widget.dart

import 'package:flutter/material.dart';
import 'package:snowdeer_hello_flutter/second_screen_widget.dart';

class FirstScreenWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First Screen'),
      ),
      body: Container(
          width: double.infinity,
          height: double.infinity,
          color: Colors.amber,
          child: Center(
              child: RaisedButton(
            child: Text('Move to Second Screen'),
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(builder: (context) => SecondScreenWidget()),
              );
            },
          ))),
    );
  }
}


second_screen_widget.dart

import 'package:flutter/material.dart';

class SecondScreenWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Second Screen'),
      ),
      body: Container(
          width: double.infinity,
          height: double.infinity,
          color: Colors.green,
          child: Center(
              child: RaisedButton(
              child: Text('Back to First Screen'),
              onPressed: () {
                Navigator.pop(context);
              },
          ))),
    );
  }
}

Flutter 화면 전환

|

Flutter에서 화면 전환을 하는 예제입니다. Navigator를 이용해서 새로운 화면을 push 또는 pop 하여 화면간 전환을 합니다.

main.dart

import 'package:flutter/material.dart';

void main() => runApp(SnowDeerApp());

class SnowDeerApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SnowDeer App',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primaryColor: Colors.deepPurple,
      ),
      home: FirstScreenWidget(),
    );
  }
}

class FirstScreenWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First Screen'),
      ),
      body: Container(
          width: double.infinity,
          height: double.infinity,
          color: Colors.amber,
          child: Center(
              child: RaisedButton(
            child: Text('Move to Second Screen'),
            onPressed: () {
              Navigator.push(
                context,
                MaterialPageRoute(builder: (context) => SecondScreenWidget()),
              );
            },
          ))),
    );
  }
}

class SecondScreenWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Second Screen'),
      ),
      body: Container(
          width: double.infinity,
          height: double.infinity,
          color: Colors.green,
          child: Center(
              child: RaisedButton(
            child: Text('Back to First Screen'),
            onPressed: () {
              Navigator.pop(context);
            },
          ))),
    );
  }
}


화면간 데이터 전달하는 방법

아래와 같이 사용자 정의 클래스를 만들고 해당 인스턴스를 다음 화면에 전달하는 예제입니다.

class Item {
  int id;
  String name;

  Item({this.id, this.name});
}


전체 코드

import 'package:flutter/material.dart';

void main() => runApp(SnowDeerApp());

class SnowDeerApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SnowDeer App',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primaryColor: Colors.deepPurple,
      ),
      home: FirstScreenWidget(),
    );
  }
}

class Item {
  int id;
  String name;

  Item({this.id, this.name});
}

class FirstScreenWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('First Screen'),
      ),
      body: Container(
          width: double.infinity,
          height: double.infinity,
          color: Colors.amber,
          child: Center(
              child: RaisedButton(
            child: Text('Move to Second Screen'),
            onPressed: () {
              final item = Item(id: 10, name: 'snowdeer');
              Navigator.push(
                context,
                MaterialPageRoute(
                    builder: (context) => SecondScreenWidget(item: item)),
              );
            },
          ))),
    );
  }
}

class SecondScreenWidget extends StatelessWidget {
  final Item item;

  SecondScreenWidget({this.item});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Second Screen"),
      ),
      body: Container(
          width: double.infinity,
          height: double.infinity,
          color: Colors.green,
          child: Center(
            child: Text('id: ${item.id}, name: ${item.name}'),
          )),
    );
  }
}

위에서 두 번째 위젯인 SecondScreenWidget의 생성자를 통해서 item 인스턴스를 속성에 넣어주도록 했습니다.

생성자는 아래와 같이 필수 입력 매개변수로 선언하면 좀 더 안전한 코드가 됩니다.

SecondScreenWidget({@required this.item});