例外安全と非同期処理

#まえおき
[tomato player](http://www.geocities.jp/notenop/tomato_player/index.html)を作ったときに考え付いた仕組みですが、汎用性が有ると思われるので公開いたします

#例外安全
例外処理はまじめに考えると頭が痛いということを痛感しました

実行時エラーの例外をキャッチするのは簡単ですが、 そのあとが問題で、処理を中断して全てを無かったことにしてプログラムの状態を元の状態に 正しくリカバリーするのは難しいです

そこで、処理を二つのセクションに分けることを考えます

- 前半部：例外を発生させてもよいが、プログラムの本体の状態を書き換えないセクション
- 後半部：プログラムの本体の状態を書き換えてもよいが、例外を発生させないセクション

実行時エラーが発生するかもしれない処理は前半部で行い 全ての成功を確認してから後半部でプログラム本体の状態に適用します

もし後半部の処理中に例外が飛んで来たらプログラム続行不可とみてabortします

しかしこの方法には弱点があって、どのように処理を行儀よく「前半」と「後半」に分けるかという事です

関数内で「前半」と「後半」に分けても上手くいきません

~~~C++
void sub1()
{
	try
	{
		//前半部：失敗するかもしれない処理
	}
	catch(...)
	{
		//処理失敗
		throw;
	}
	//後半部：前半部の処理を適用
}

void sub2()
{
	try
	{
		//前半部：失敗するかもしれない処理
	}
	catch(...)
	{
		//処理失敗
		throw;
	}
	//後半部：前半部の処理を適用
}

void func()
{
	try
	{
		//前半部：失敗するかもしれない処理
		sub1();
		sub2();
	}
	catch(...)
	{
		//処理失敗
		throw;
	}
	//後半部：前半部の処理を適用
}
~~~

このままではだめで、上記のようなコールグラフのプログラムで、`sub1()`が成功して`sub2()`が失敗した場合 既に`sub1()`は成功してプログラムを書き換えた後なので、`sub1()`の処理を無かったことにして プログラムの状態を元に戻すのはとても大変です

そこで`sub1()`、`sub2()`を前半と後半の二つに分解します

~~~C++
void sub1_pre (){ /*前半部：失敗するかもしれない処理*/ }
void sub1_main(){ /*後半部：前半部の処理を適用*/ }

void sub2_pre (){ /*前半部：失敗するかもしれない処理*/ }
void sub2_main(){ /*後半部：前半部の処理を適用*/ }

void func()
{
	try
	{
		//前半部：失敗するかもしれない処理
		sub1_pre();
		sub2_pre();
	}
	catch(...)
	{
		//処理失敗
		throw;
	}
	//後半部：前半部の処理を適用
	sub1_main();
	sub2_main();
}
~~~

ここで、各 `_pre()`と`_main()`の間で処理中のデータを受け渡す必要があるので構造体を定義してデータを受け渡すようにします

~~~C++
struct sub1_work_t{};
void sub1_pre ( sub1_work_t *w ){ /*前半部：失敗するかもしれない処理*/ }
void sub1_main( sub1_work_t *w ){ /*後半部：前半部の処理を適用*/ }

struct sub2_work_t{};
void sub2_pre ( sub2_work_t *w ){ /*前半部：失敗するかもしれない処理*/ }
void sub2_main( sub2_work_t *w ){ /*後半部：前半部の処理を適用*/ }

void func()
{
	sub1_work_t w1
	sub2_work_t w2;
	try
	{
		//前半部：失敗するかもしれない処理
		sub1_pre( &w1 );
		sub2_pre( &w2 );
	}
	catch(...)
	{
		//処理失敗
		throw;
	}
	//後半部：前半部の処理を適用
	sub1_main( &w1 );
	sub2_main( &w2 );
}
~~~

見てわかる通り、もしプログラム全体にわたってこのようなルールを適用すると あっという間にグチャグチャになります

確実に管理不能に陥ります


#非同期処理
ところでタイトルの通り別の要求もあります

それは時間のかかる処理の実行中にGUIスレッドが固まるのは嫌だから非同期処理にしたい、というものです

何の工夫もなく非同期処理を書けば

- 別スレッドに処理を投げる
- 処理が終わるとメインスレッドに通知を投げる
- メインスレッドにて処理続行

となりますが、これだと、ひとまとまりの処理が複数の関数などに分散されて全体の流れが見にくくなります

また、データの受け渡しをなんらかの形で行う必要があります

ところで、時間のかかる処理中にGUIスレッドが固まらないように非同期処理にするまではいいですが、 複数の別の非同期処理が縦横無尽に同時に走ったらプログラムが壊れるので 非同期処理中は他の処理が走らないようにマウスやキーボードからの入力を受け付けないようにする・・・ というのでは同期処理と同じことなので、そうならないように処理のキャンセルを受け付けるようにしなければなりません

複数の非同期処理が走るのは危ないですが、前の処理をキャンセルしてから新たな処理を走らす分には問題ないわけです

ここで例外安全と非同期処理の二つのトピックをよく考えると、類似性が浮かび上がります

それは

- 安全な処理の中断に対応しなければならない
- 時間的に分断され、通常のコールグラフでは表せない分断された処理同士で何らかの形でデータの受け渡しをしなければならない

というものです

#そこで
そこで思い切ってコルーチンを導入します

あまりコルーチンは好きではないのですが、一つのカラクリで二つの別の問題が解決するなら仕方ありません

また、使ってみると(最初は混乱しましたが)ネイティブスレッドと違って明示的なタイミングでしかコンテキストスイッチしないので (ネイティブスレッドと比べて)制御しやすい一面もありました

最終的に大体このような感じに書けるるようにします

~~~C++
void sub1_async()
{
	//前半部：失敗するかもしれない処理

	//↓非同期処理：別スレッドに処理を投げて、すぐさま制御を返す
	a_wait() << [&]
	{
		//別スレッドで実行
	};
	
	barrier();
	//後半部：前半部の処理を適用
}

void sub2_async(){ barrier(); }

void func_async()
{
	//前半部：失敗するかもしれない処理
	a_sync() << [&] //←a_syncでコルーチン作成
	{
		sub1_async();
	};
	a_sync() << [&]　//←a_syncでコルーチン作成
	{
		sub2_async();
	};

	a_wait() << [&]{}; //←非同期処理をしてみたり

	barrier();
	//後半部：前半部の処理を適用
}

int main()
{
	a_sync() << [&]{ func_async(); };
	main_thread_message_loop();
	return 0;
}
~~~

`a_sync()`でコルーチン作成、`a_wait()`で非同期処理
コルーチン内では`a_wait()`が可能
コルーチンからコルーチンを作ると両者のコルーチンは紐づいてコルーチンツリーを構築する
プログラムは`barrier()`に到着するといったん止まり、 コルーチンツリー内の全てのコルーチンが`barrier()`に到着するか正常終了するまで待ってから再開される
コルーチンツリー内のだれかが実行時エラーかキャンセルされるかすると、 全てのコルーチンツリー内のコルーチン内の`barrier()`以降が実行されないようになっています

![except_and_async_1.png](https://qiita-image-store.s3.amazonaws.com/0/176298/8624db5b-0d80-f030-37b0-c327ee47ab2e.png)
このようにbarrierで止まって
![except_and_async_2.png](https://qiita-image-store.s3.amazonaws.com/0/176298/5a62609e-e5d5-11db-cb4c-f61d8632c9a6.png)
全ての処理が成功してからbarrier以降を実行します

ところで実行時エラーはファイルが存在しないなどですが、キャンセルとは何かを考えます

もともと複数の非同期処理がピンポンのように同時並行で走ると危険なので、 どちらか片方をキャンセルして、常に一つの処理しか走らないようにしたいという事でした

ユーザーにしても複数のタスクの同時実行を許したら自分が何を命令したのか混乱すると思われるのでこれでよいわけです

複数のコルーチンツリーが実行されようとしたとき、どちらか片方のコルーチンツリーをキャンセルしたいので、コルーチンツリーに優先順位を設けます

優先順位が高い方のコルーチンツリーが優先され、同じ優先順位の場合は後から作成されたコルーチンツリーの方を優先します

また、どちらのコルーチンツリーもキャンセルしたくない場合もあります

それは、全く関連性のない別々の処理なのに片方をキャンセルするのはロジックの上でおかしいという事なので コルーチンツリーに互いに全く関係ないことを示すためのグループ番号を設けます

違ったグループ番号同士では、キャンセルしあったりしないようにしますが、 違ったグループ番号といえ同時に走ったらまずいことには変わりないので、 先に走ってる別グループ番号のコルーチンツリーが終了するまで、後から実行しようとしているコルーチンの実行を保留させます

このように、時にはキャンセル、時には保留をし、同時に走るコルーチンツリーを常に一つに制限します

追記：2018/08/20
プログラムを一部修正
今まではbarrier()以降を処理中も別スレッドでa_waitの処理が走っていたのを
安全面を考慮してa_waitの処理が終了するまでbarrier()の処理を保留するように修正
保留するといってもその間GUIスレッドが固まるというわけではないです

追記：2018/08/21
プログラムのミスを修正

#サンプルプログラム
以下にサンプルプログラムをおいておきます
[実行ファイルとサンプルコード](http://www.geocities.jp/notenop/omou/except_and_async_source_code.zip)
コルーチンにはWin32のファイバーを利用しています
皆さんの非同期処理に役立てれば幸いです

([tomato player](http://www.geocities.jp/notenop/tomato_player/index.html)から整理せずに持ってきているので煩雑です)

~~~C++
#include <exception>
#include <functional>
#include <vector>
#include <assert.h>
#include <windows.h>
#include <tchar.h>
#include <commctrl.h>
#include <process.h>

#pragma comment(lib, "comctl32.lib" )

struct cs_t
{
	CRITICAL_SECTION cs;
	cs_t()
	{
		::InitializeCriticalSection( &cs );
		return;
	}

	~cs_t()
	{
		::DeleteCriticalSection( &cs );
		return;
	}
};

struct syncer_t
{
	cs_t	*cs = nullptr;
	syncer_t( cs_t *cs_ )
	{
		cs = cs_;
		::EnterCriticalSection( &cs->cs );
		return;
	}

	~syncer_t()
	{
		::LeaveCriticalSection( &cs->cs );
		return;
	}
};

__declspec( thread ) void *g_fiber = nullptr;

static const int FB_GROUP_1 = ( 1 << 0 );
static const int FB_GROUP_2 = ( 1 << 1 );

struct cancel_t :public std::exception{};
struct failed_t :public std::exception{};

struct fiber_t
{
	void		*fiber = nullptr;
	bool		is_using = false;
	bool		is_running = false;
	bool		is_finished = false;
	bool		is_cancel = false;
	bool		is_barrier = false;
	bool		do_barrier = false;
	void		*ret_fiber = nullptr;
	unsigned	group = 0;
	int			priority = 0;
	bool		is_sync = false;
	int			seq = 0;
	std::exception_ptr
				ep;
	DWORD		a_wait_thread_id = 0;
	bool		is_a_waiting = false;

	fiber_t		*next = nullptr;
	fiber_t		*prev = nullptr;

	std::function<void(void)>	a_sync_func;
	std::function<void(void)>	a_wait_func;

	fiber_t()
	{
		fiber = ::CreateFiber( 0, proc, this );
		get_window();
		return;
	}

	~fiber_t()
	{
		if( fiber )
		{
			::DeleteFiber( fiber );
		}
		return;
	}

	fiber_t *head()
	{
		auto f = this;
		for( ; f->prev; f = f->prev );
		return f;
	}

	fiber_t *tail()
	{
		auto f = this;
		for( ; f->next; f = f->next );
		return f;
	}

	void add( fiber_t *fi )
	{
		auto fi_head = fi->head();
		auto fi_tail = fi->tail();
		fi_head->prev = prev;
		fi_tail->next = this;
		if( prev ){ prev->next = fi_head; }
		prev = fi_tail;
		return;
	}

	static void barrier()
	{
		assert( ::IsThreadAFiber() );
		auto f = (fiber_t *)::GetFiberData();

		if( f->is_sync )
		{
			return;
		}
	
		if( f->do_barrier ){ return; }

		if( !f->is_cancel )
		{
			if( !cancel_other_a_sync( f->group, false ) )
			{
				cancel_a_sync();
			}
		}

		f->do_barrier = true;

		if( !f->next && !f->prev ){ return; }

		f->is_barrier = true;
	

		auto h = f->head();

		auto check = [&]() -> bool
		{
			for( auto ff = h; ff; ff = ff->next )
			{
				if( !( ff->is_barrier || ff->is_finished ) ){ return false; }
			}
			return true;
		};

		if( check() )
		{
			::PostMessage( get_window(), WM_APP, 0, 0 );
		}
		yield_return();
		f->is_barrier = false;
		if( f->is_cancel )
		{
			f->is_cancel = false;
			if( f->ep )
			{
				std::rethrow_exception( f->ep );
			}
			else
			{
				throw cancel_t();
			}
		}
		return;
	}

	static bool cancel_other_a_sync( unsigned group_mask = FB_GROUP_1, int priority = 0 )
	{
		fiber_t *fiber = nullptr;

		if( ::IsThreadAFiber() )
		{
			fiber = (fiber_t *)::GetFiberData();
		}

		for( auto f: *fiber_t::fibers() )
		{
			if( !f->is_using ){ continue; }
			if( fiber && ( f->seq == fiber->seq ) ){ continue; }
			if( !( group_mask & f->group ) ){ continue; }
			if( priority < f->priority )
			{
				return false;
			}
		}

		for( auto f: *fiber_t::fibers() )
		{
			if( !f->is_using ){ continue; }
			if( fiber && ( f->seq == fiber->seq ) ){ continue; }
			if( !( group_mask & f->group ) ){ continue; }
			if( priority < f->priority ){ continue; }
			f->is_cancel = true;
		}
		return true;
	}

	static void cancel_a_wait()
	{
		assert( g_fiber );
		throw cancel_t();
		return;
	}

	static void cancel_a_sync()
	{
		assert( ::IsThreadAFiber() );
		assert( g_fiber );
		auto f = (fiber_t *)::GetFiberData();
		assert( !f->do_barrier );
		throw cancel_t();
		return;
	}

	static void failed_a_wait()
	{
		assert( !::IsThreadAFiber() );
		assert( g_fiber );
		throw failed_t();
		return;
	}

	static void failed_a_sync()
	{
		assert( ::IsThreadAFiber() );
		assert( g_fiber );
		auto f = (fiber_t *)::GetFiberData();
		assert( !f->do_barrier );
		throw failed_t();
		return;
	}

	static void yield_return()
	{
		assert( ::IsThreadAFiber() );
		auto f = (fiber_t *)::GetFiberData();
		if( f->ret_fiber )
		{
			::SwitchToFiber( f->ret_fiber );
		}
		return;
	}

	static void switch_to_fiber( fiber_t *f )
	{
		assert( f->fiber );

		fiber_t *from = nullptr;
		if( ::IsThreadAFiber() )
		{
			from = (fiber_t *)::GetFiberData();
		}

		if( from )
		{
			f->ret_fiber = from->fiber;
		}
		else
		{
			f->ret_fiber = ::ConvertThreadToFiber( nullptr );
		}
	
		g_fiber = (void *)f;
		::SwitchToFiber( f->fiber );
		g_fiber = (void *)from;

		if( !from )
		{
			::ConvertFiberToThread();
		}
		return;
	}

	void on_proc()
	{
		for( ; ; )
		{
			is_barrier = false;
			do_barrier = false;
			is_finished = false;
			ep = nullptr;
			try
			{
				if( !is_cancel )
				{
					a_sync_func();
				}
			}
			catch( cancel_t )
			{
				is_cancel = true;
			}
			catch( ... )
			{
				assert( !do_barrier );
				is_cancel = true;
			}
			if( is_cancel )
			{
				for( auto f = this; f; f = f->prev ){ f->is_cancel = true; }
				for( auto f = this; f; f = f->next ){ f->is_cancel = true; }
			}

			if( next || prev )
			{
				if( !do_barrier )
				{
					try
					{
						barrier();
					}
					catch( ... )
					{
					}
				}
			}

			if( next )
			{
				next->prev = prev;
				next = nullptr;
			}

			if( prev )
			{
				prev->next = next;
				prev = nullptr;
			}

			is_finished = true;
			is_using = false;
			is_running = false;
			::SwitchToFiber( ret_fiber );
		}
		return;
	}

	void static CALLBACK proc( void *inst )
	{
		return ( (fiber_t *)inst )->on_proc();
	}

	static std::vector< fiber_t * > *fibers()
	{
		struct inst_t
		{
			std::vector< fiber_t * > fibers;
			~inst_t()
			{
				for( auto f: fibers )
				{
					delete f;
				}
				return;
			}
		};
		static inst_t inst;
		return &inst.fibers;
	}

	static fiber_t *get_fiber()
	{
		syncer_t sync( get_cs() );
		for( auto f: *fibers() )
		{
			if( f->is_using ){ continue; }
			if( f->next || f->prev ){ continue; }
			f->is_using = true;
			return f;
		}
		auto f = new fiber_t();
		fibers()->push_back( f );
		f->is_using = true;
		return f;
	}

	static cs_t *get_cs()
	{
		static cs_t cs;
		return &cs;
	}

	static HWND get_window()
	{
		struct window_t
		{
			HWND	handle = nullptr;
			window_t()
			{
				WNDCLASS winc;
				winc.style			= CS_HREDRAW | CS_VREDRAW;
				winc.lpfnWndProc	= ::DefWindowProc;
				winc.cbClsExtra		= 0;
				winc.cbWndExtra		= 0;
				winc.hInstance		= ::GetModuleHandle( nullptr );
				winc.hIcon			= nullptr;
				winc.hCursor		= LoadCursor( nullptr, IDC_ARROW );
				winc.hbrBackground	= (HBRUSH)GetStockObject( NULL_BRUSH );
				winc.lpszMenuName	= nullptr;
				winc.lpszClassName	= L"fiber_window_t";

				::RegisterClass( &winc );

				handle = ::CreateWindow(
							L"fiber_window_t", L"fiber_window",
							WS_OVERLAPPEDWINDOW,
							0, 0,
							1, 1,
							nullptr, nullptr,
							::GetModuleHandle( nullptr ), nullptr
						);

				::SetWindowSubclass( handle, subclass_proc, 1, (DWORD_PTR)this );
				return;
			}

			LRESULT on_message( UINT msg, WPARAM wp, LPARAM lp )
			{
				switch( msg )
				{
				case WM_APP:
				{
					for( auto f: *fiber_t::fibers() )
					{
						if( f->is_a_waiting )
						{
							return 0;
						}
					}
					auto func = [&]( fiber_t *head ) -> void
					{
						for( auto f = head; f; f = f->next )
						{
							if( !( f->do_barrier || f->is_finished ) ){ return; }
						}
						bool is_cancel = false;
						for( auto f = head; f; f = f->next )
						{
							is_cancel |= f->is_cancel;
						}
						for( auto f = head; f; f = f->next )
						{
							if( is_cancel ){ f->is_cancel = true; }
						}
						for( auto f = head; f; )
						{
							auto next = f->next;
							if( f->is_barrier )
							{
								switch_to_fiber( f );
							}
							f = next;
						}
					};
					for( auto f: *fiber_t::fibers() )
					{
						if( !f->is_using ){ continue; }
						if( f->prev ){ continue; }
						func( f );
					}
					break;
				}
				case WM_APP + 1:
				{
					fiber_t::switch_to_fiber( (fiber_t *)lp );
					break;
				}
				default:
					break;
				}
				return ::DefSubclassProc( handle, msg, wp, lp );
			}

			static LRESULT CALLBACK subclass_proc(
				HWND wnd,
				UINT msg,
				WPARAM wp,
				LPARAM lp,
				UINT_PTR subclass_id,
				DWORD_PTR inst
				)
			{
				return ((window_t *)inst)->on_message( msg, wp, lp );
			}
		};
		static window_t window;
		return window.handle;
	}
};

static int message_loop()
{
	MSG msg;
	for( ; 0<::GetMessage( &msg, nullptr, 0, 0 ); )
	{
		{
			::TranslateMessage( &msg );
			::DispatchMessage( &msg );
		}
	}
	return (int)msg.wParam;
}

static void message_loop( std::function< bool(void) > func )
{
	MSG msg;
	for( ; 0<::GetMessage( &msg, nullptr, 0, 0 ); )
	{
		{
			::TranslateMessage( &msg );
			::DispatchMessage( &msg );
		}
		if( func() ){ break; }
	}
	if( WM_QUIT == msg.message )
	{
		::PostQuitMessage( (int)msg.wParam );
	}
	return;
}

struct a_sync
{
	unsigned		group = FB_GROUP_1;
	int				priority = 0;
	bool			is_sync = false;
	
	a_sync( unsigned group_ = FB_GROUP_1, int priority_ = 0, bool is_sync_ = false )
	{
		group = group_;
		priority = priority_;
		is_sync = is_sync_;
		return;
	}

	void operator <<( std::function<void(void)> func )
	{
		static int seq = 1;
		auto f = fiber_t::get_fiber();
		f->is_cancel = false;
		f->next = nullptr;
		f->prev = nullptr;
		f->ep = nullptr;
		f->a_sync_func = func;
		f->group = group;
		f->priority = priority;
		f->is_sync = is_sync;
		if( g_fiber )
		{
			auto ff = (fiber_t *)g_fiber;
			assert( !ff->do_barrier );
			f->seq = ff->seq;
			f->group = ff->group;
			f->is_sync = ff->is_sync;
			f->priority = ff->priority;
			ff->add( f );
		}
		else
		{
			++seq;
			f->seq = seq;
		}

		auto check = [&]() -> bool
		{
			syncer_t sync( fiber_t::get_cs() );
			if( f->is_cancel )
			{
				f->is_running = true;
				return true;
			}
			auto fs = fiber_t::fibers();
			for( auto ff: *fs )
			{
				if( !ff->is_using ){ continue; }
				if( !ff->is_running ){ continue; }
				if( f->group != ff->group ){ return false; }
			}
			f->is_running = true;
			return true;
		};
		if( !check() )
		{
			assert( !::IsThreadAFiber() );
			auto old_fiber = g_fiber;
			g_fiber = nullptr;
			message_loop( [&]() -> bool{ return check(); } );
			g_fiber = old_fiber;
		}
		fiber_t::switch_to_fiber( f );
		return;
	}
};

struct a_wait
{
	void operator << ( std::function<void(void)> func )
	{
		if( !::IsThreadAFiber() )
		{
			func();
			return;
		}

		auto fiber = (fiber_t *)::GetFiberData();

		assert( fiber );

		if( fiber->is_sync )
		{
			func();
			return;
		}

		assert( !fiber->do_barrier );

		auto ret = fiber_t::cancel_other_a_sync( fiber->group, fiber->priority );
		if( !ret )
		{
			fiber_t::cancel_a_sync();
		}

		fiber->a_wait_func = func;
		fiber->is_cancel = false;
		fiber->is_a_waiting = true;

		//↓デモなので手抜き
		//↓ちゃんとしたスレッドプールにしたほうが良いです
		unsigned id;
		auto thread = (HANDLE)::_beginthreadex( nullptr, 0, thread_main, fiber, 0, &id );
	
		fiber_t::yield_return();
	
		::CloseHandle( thread );

		if( fiber->is_cancel )
		{
			fiber->is_cancel = false;
			if( fiber->ep )
			{
				std::rethrow_exception( fiber->ep );
				fiber->ep = nullptr;
			}
			else
			{
				throw cancel_t();
			}
		}
		return;
	}

	static unsigned __stdcall thread_main( void *data )
	{
		auto fiber = (fiber_t *)data;
		g_fiber = fiber;
		try
		{
			fiber->a_wait_func();
			fiber->is_a_waiting = false;
		}
		catch( ... )
		{
			fiber->is_cancel = true;
			fiber->is_a_waiting = false;
			fiber->ep = std::current_exception();
		}
		::PostMessage( fiber_t::get_window(), WM_APP + 1, 0, (LPARAM)fiber );
		::PostMessage( fiber_t::get_window(), WM_APP, 0, 0 );
		return 0;
	}
};

HWND g_window = nullptr;
HWND g_log_window = nullptr;

void output_log( const wchar_t *text )
{
	int len = ::GetWindowTextLength( g_log_window );
	::SendMessage( g_log_window, EM_SETSEL, len, len );
	::SendMessage( g_log_window, EM_REPLACESEL, FALSE, (LPARAM)text );
	::SendMessage( g_log_window, EM_REPLACESEL, FALSE, (LPARAM)L"\r\n" );
	return;
}

LRESULT WINAPI main_window_proc( HWND wnd, UINT msg, WPARAM wp, LPARAM lp )
{
	switch( msg )
	{
	case WM_CLOSE:
	{
		::PostQuitMessage( 0 );
		break;
	}
	case WM_COMMAND:
	{
		switch( LOWORD( wp ) )
		{
		case 100:
		{
			a_sync( FB_GROUP_1, 0 ) << [&]
			{
				output_log( L"starting task A" );
				a_wait() << [&]{ ::Sleep( 5000 ); };
				fiber_t::barrier();
				output_log( L"finished task A" );
			};
			break;
		}
		case 101:
		{
			a_sync( FB_GROUP_1, 0 ) << [&]
			{
				output_log( L"starting task B" );
				a_sync() << [&]
				{
					output_log( L"starting task BB" );
					a_wait() << [&]{ ::Sleep( 1000 ); };
					fiber_t::barrier();
					output_log( L"finished task BB" );
				};
				a_wait() << [&]{ ::Sleep( 5000 ); };
				fiber_t::barrier();
				output_log( L"finished task B" );
			};
			break;
		}
		case 102:
		{
			a_sync( FB_GROUP_1, 1 ) << [&]
			{
				output_log( L"starting task C" );
				a_wait() << [&]{ ::Sleep( 2000 ); };
				fiber_t::barrier();
				output_log( L"finished task C" );
			};
			break;
		}
		case 200:
		{
			a_sync( FB_GROUP_2, 0 ) << [&]
			{
				output_log( L"starting task D" );
				a_wait() << [&]{ ::Sleep( 1000 ); };
				fiber_t::barrier();
				output_log( L"finished task D" );
			};
			break;
		}
		case 201:
		{
			a_sync( FB_GROUP_2, 0 ) << [&]
			{
				output_log( L"starting task E" );
				a_sync() << [&]
				{
					output_log( L"starting task EE" );
					a_sync() << [&]
					{
						output_log( L"starting task EEE" );
						fiber_t::barrier();
						output_log( L"finished task EEE" );
					};
					a_wait() << [&]{ ::Sleep( 1000 ); };
					fiber_t::barrier();
					output_log( L"finished task EE" );
				};
				a_sync() << [&]
				{
					output_log( L"starting task EE2" );
					fiber_t::barrier();
					output_log( L"finished task EE2" );
				};
				a_wait() << [&]{ ::Sleep( 2000 ); };
				fiber_t::barrier();
				output_log( L"finished task E" );
			};
			break;
		}
		case 202:
		{
			a_sync( FB_GROUP_2, 1 ) << [&]
			{
				output_log( L"starting task F" );
				a_wait() << [&]{ ::Sleep( 1000 ); };
				fiber_t::barrier();
				output_log( L"finished task F" );
			};
			break;
		}
		default:
			break;
		}
		break;
	}
	default:
		break;
	}
	return ::DefWindowProc( wnd, msg, wp, lp );
}

int WINAPI _tWinMain(
	HINSTANCE inst, HINSTANCE prev_inst, 
	wchar_t *cmd_line, int show_cmd )
{
	WNDCLASS winc;
	winc.style			= CS_HREDRAW | CS_VREDRAW;
	winc.lpfnWndProc	= main_window_proc;
	winc.cbClsExtra		= 0;
	winc.cbWndExtra		= 0;
	winc.hInstance		= ::GetModuleHandle( nullptr );
	winc.hIcon			= nullptr;
	winc.hCursor		= LoadCursor( nullptr, IDC_ARROW );
	winc.hbrBackground	= (HBRUSH)( COLOR_3DFACE + 1 );
	winc.lpszMenuName	= nullptr;
	winc.lpszClassName	= L"main_window_t";

	::RegisterClass( &winc );

	DWORD style = WS_OVERLAPPEDWINDOW;
	RECT rect = { 0, 0, 800, 600, };
	::AdjustWindowRect( &rect, style, FALSE );

	g_window = ::CreateWindow(
				L"main_window_t", L"except_and_async",
				style,
				CW_USEDEFAULT, CW_USEDEFAULT,
				rect.right - rect.left, rect.bottom - rect.top,
				nullptr, nullptr,
				::GetModuleHandle( nullptr ), nullptr
			);

	::CreateWindow( L"STATIC", L"group: 1", WS_VISIBLE|WS_CHILD,
		100, 0, 100, 30, g_window, nullptr, ::GetModuleHandle( nullptr ), 0 );
	for( int i = 0; i < 3; ++i )
	{
		wchar_t tmp[ MAX_PATH ] = {};
		::swprintf_s( tmp, MAX_PATH, L"task %c, pri: %d", 'A' + i, i / 2 );
		::CreateWindow( L"BUTTON", tmp, WS_VISIBLE|WS_CHILD,
			100, i * 50 + 50, 150, 30,
			g_window, (HMENU)( 100 + i ), ::GetModuleHandle( nullptr ), 0 );
	}

	::CreateWindow( L"STATIC", L"group: 2", WS_VISIBLE|WS_CHILD,
		300, 0, 100, 30, g_window, nullptr, ::GetModuleHandle( nullptr ), 0 );
	for( int i = 0; i < 3; ++i )
	{
		wchar_t tmp[ MAX_PATH ] = {};
		::swprintf_s( tmp, MAX_PATH, L"task %c, pri: %d", 'D' + i, i / 2 );
		::CreateWindow( L"BUTTON", tmp, WS_VISIBLE|WS_CHILD,
			300, i * 50 + 50, 150, 30,
			g_window, (HMENU)( 200 + i ), ::GetModuleHandle( nullptr ), 0 );
	}

	g_log_window = ::CreateWindow( L"EDIT", L"",
		WS_VISIBLE|WS_CHILD|ES_AUTOHSCROLL|ES_AUTOVSCROLL|ES_MULTILINE|ES_READONLY,
			500, 0, 200, 600, g_window, nullptr, ::GetModuleHandle( nullptr ), 0 );
	::ShowWindow( g_window, SW_SHOW );

	message_loop();
	return 0;
}
~~~