C# 非同期処理 コンソールのローディングアニメーションをasync/await仕様で書き換えてみる
概要
前回は職場環境がまだVisualStudio2010のため.NET Framework 4仕様で実装しました。
今年こそはいい加減バージョンアップしてくれるんじゃないかと期待をこめて、async/awaitキーワードを使って書き換えてみます。
環境
Visual Studio 2015
.NET Framework 4.6.1
非同期処理の実装(async/awaitバージョン)
async/awaitは継続タスクを簡単に書くためのシンタックスシュガーのようなものということですが、個人的にはTask.ContinueWithメソッドは嫌いじゃないので、チュートリアルレベルではあまり恩恵を感じられませんでした。
非同期処理の奥深き底が見えません…(|||´д`)
■ Calculation.cs
using System; using System.Threading.Tasks; using static System.Console; using static System.Threading.Thread; namespace ConsoleAsync { public static class Calculation { public static async Task<int> RunAsync() { //0以上10未満のランダム数値を生成 var seed = new Random().Next(10); WriteLine($"[{ DateTime.Now.ToString("HH:mm:ss.fff")}] Random seed {seed}"); Task<int> t = Task.Run<int>(() => TaskA(seed)); if (t.Exception != null) { //AggregateExceptionにラップされているので元の例外を再スロー throw t.Exception.InnerException; } return TaskB(await t); } //引数を2倍し結果を返す public static int TaskA(int value) { WriteLine($"[{DateTime.Now.ToString("HH:mm:ss.fff")}] Task A"); Sleep(2500); //エラー① //throw new InvalidOperationException("タスクAで発生した例外"); var ret = value * 2; WriteLine($"[{ DateTime.Now.ToString("HH:mm:ss.fff")}] Task A result:{ret}"); return ret; } //引数を3倍し結果を返す public static int TaskB(int value) { WriteLine($"[{DateTime.Now.ToString("HH:mm:ss.fff")}] Task B"); Sleep(2500); //エラー② //throw new InvalidOperationException("タスクBで発生した例外"); var ret = value * 3; WriteLine($"[{ DateTime.Now.ToString("HH:mm:ss.fff")}] Task B result:{ret}"); return ret; } } }
■ Program.cs
タスク完了のポーリングは呼び出し側に移動(こうしないと実装できなかった)。おそらくこっちが意味的に正しいのかと。
using System; using static System.Console; using static ConsoleAsync.Calculation; namespace ConsoleAsync { class Program { static void Main(string[] args) { try { WriteLine($"[{DateTime.Now.ToString("HH:mm:ss.fff")}] Start"); var t = RunAsync(); //タスクの完了をポーリング while (!t.IsCompleted) { Spiner.Spin(); } Write(new string(' ', WindowWidth)); SetCursorPosition(0, CursorTop - 1); //Resultプロパティの読み取りでも例外をキャッチ可能 WriteLine($"[{DateTime.Now.ToString("HH:mm:ss.fff")}] Result:{t.Result}"); } catch (AggregateException e) { WriteLine($"[{DateTime.Now.ToString("HH:mm:ss.fff")}] error - {e.InnerException.Message}"); WriteLine("Application exit."); ReadKey(true); Environment.Exit(1); } catch (Exception e) { WriteLine($"[{DateTime.Now.ToString("HH:mm:ss.fff")}] error - {e.Message}"); ReadKey(true); Environment.Exit(1); } Write(new string(' ', WindowWidth)); SetCursorPosition(0, CursorTop - 1); Write($"[{DateTime.Now.ToString("HH:mm:ss.fff")}] Complete!"); ReadKey(true); } } }
C# 6.0新機能
せっかくなので一部C# 6.0の新機能に書き換えてみました。
Using static
確かにコードが短くなるけど、あんまり使いすぎると返ってわかりにくくなるかも。
using static System.Console;
文字列補間
これは便利!引数を間違えて指定することもなくなります。
WriteLine($"[{ DateTime.Now.ToString("HH:mm:ss.fff")}] Task A result:{ret}");
新機能についてはChannel9のde:code2015の動画がさくっとみれてわかりやすいです。
https://channel9.msdn.com/Events/de-code/decode-2015/DEV-022S
Channel9オススメ!他にもいろいろな動画があるのでぜひ。