コンソールアプリケーションにアクティビティインジケータを表示

# 概要

コンソールアプリケーションにおいて、重たい処理を実行中であることをユーザーに伝えるためのアクティビティインジケータ（ビジーマーク）を表示するプログラムをC#とF#で作成しました。

![aaa.gif](https://qiita-image-store.s3.amazonaws.com/0/152805/ac939020-130d-a635-094b-d5cfa0dc4e7b.gif)

一定時間毎に`...`を表示するためにRx（`Observable.Timer`）を利用しました。

- `処理中`
- `処理中.`
- `処理中..`
- `処理中...`
- 処理が終わるまで上記の表示の繰り返し、処理が完了したら下記の表示で上書き
- `処理完了`

# C#版

nugetで`System.Reactive`をインストールしておきます。

```CSharp
using System;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;

class Program {

  // アクティビティインジケータを出力する関数
  static void IndicationPrinter(string msg1, string msg2, int i) {
    Console.SetCursorPosition(0, Console.CursorTop);
    if (i < 0) {
      Console.WriteLine(msg2);
    } else {
      var n = i % 4;
      Console.Write($"{msg1}{new string('.', n)}{new string(' ', 3 - n)}");
    }
  }

  // インジケータを出力しながら関数funcをする非同期で実行する関数
  static Task<T> RunWithActivityIndicator<S, T>(string msg1, string msg2, Func<S, T> func, S arg) {

    var sTimer = Observable.Timer(TimeSpan.FromSeconds(0), TimeSpan.FromSeconds(0.2));
    var timerSubscription = sTimer.Subscribe(i =>
      IndicationPrinter(msg1, msg2, (int)i));

    return Task.Run(() => {
      return func(arg);
    }).ContinueWith((Task<T> t) => {
      timerSubscription.Dispose();
      IndicationPrinter(msg1, msg2, -1);
      return t.Result;
    });
  }

  static void Main(string[] args) {

    Console.WriteLine("C# Ver.");
    //Console.CursorVisible = false; //カーソルを消したい場合

    Func<string, string> heavyWeightFunc1 = (string arg) => {
      Thread.Sleep(2500);
      return $"=={arg}==";
    };

    // 非常に時間がかかる関数1 (Func<int>) 
    Func<int> heavyWeightFunc2 = () => {
      Thread.Sleep(1500);
      return 0;
    };

    var msg1 = "処理中";
    var msg2 = "処理完了";

    var task1 = RunWithActivityIndicator(msg1, msg2, heavyWeightFunc1, "hoge");
    Console.WriteLine($"処理結果：{task1.Result}");

    Console.ReadKey();
  }
}
```

# F#版

nugetで`FSharp.Control.Reactive`をインストールしておきます。

```FSharp
open System
open System.Reactive.Linq
open FSharp.Control.Reactive
open System.Threading
open System.Threading.Tasks

// アクティビティインジケータを出力する関数
let indicationPrinter msg1 msg2 i = 
  Console.SetCursorPosition(0, Console.CursorTop) |> ignore
  if i < 0 then
    printfn "%s" msg2
  else
    match i%4 with
    | 0 -> ignore( printf "%s" (msg1+"   "))
    | 1 -> ignore( printf "%s" (msg1+".  "))
    | 2 -> ignore( printf "%s" (msg1+".. "))
    | _ -> ignore( printf "%s" (msg1+"..."))

// インジケータを出力しながら関数funcをする非同期で実行する関数
let runWithActivityIndicator (msg1,msg2) (func:'S->'T) arg =

  let indicationPrinter = indicationPrinter msg1 msg2

  let sTimer = Observable.Timer(TimeSpan.FromSeconds(0.),TimeSpan.FromSeconds(0.2))
  let timerSubscription = sTimer.Subscribe( fun i -> int(i) |> indicationPrinter  )

  let task = async { return func arg } |> Async.StartAsTask
  task.ContinueWith( fun (t:Task<'T>) -> 
    timerSubscription.Dispose()
    indicationPrinter -1
    t.Result ) 

[<EntryPoint>]
let main argv =

  printfn "F# Ver."
  //Console.CursorVisible <- false; // カーソルを消したい場合

  // 非常に時間がかかる関数1 (string->string) 
  let heavyWeightFunc1 arg =
    Thread.Sleep(1500)
    "==" + arg + "=="
  
  // 非常に時間がかかる関数2 (unit->string) 
  let heavyWeightFunc2 () =
    Thread.Sleep(2500)
    0
  
  // 非常に時間がかかる関数3 (unit->unit) 
  let heavyWeightFunc3 () =
    Thread.Sleep(1000)

  let msg = ("処理中","処理完了")  
  let task1 = runWithActivityIndicator msg heavyWeightFunc1 "hoge"
  printfn "%s" ("処理結果 : " + task1.Result )

  let task2 = runWithActivityIndicator msg heavyWeightFunc2 ()
  printfn "%s" ("処理結果 : " + (task2.Result.ToString()) )

  let task3 = runWithActivityIndicator msg heavyWeightFunc3 ()
  task3.Wait()
  printfn "." 

  Console.ReadKey() |> ignore
  0
```

# 参考資料
- [Reactive Extensions再入門 その４「Timer系のファクトリメソッド ](https://blog.okazuki.jp/entry/20111106/1320584830)
- [F# Async Guide](https://medium.com/jettech/f-async-guide-eb3c8a2d180a)
- [C# 非同期処理 コンソールでくるくるローディングアニメーションを表示する](http://sh-yoshida.hatenablog.com/entry/2016/04/07/191952)
- [処理中にグルグル回るアイコンの名前はどれが正しいの？進捗インジケータ or…?](https://webmaru.blog/1466/)