読者です 読者をやめる 読者になる 読者になる

1.21 jigowatts

Great Scott!

C# 非同期処理 コンソールのローディングアニメーションをasync/await仕様で書き換えてみる

C#

概要

前回は職場環境がまだ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オススメ!他にもいろいろな動画があるのでぜひ。