概要
コンソールアプリケーションにおいて、重たい処理を実行中であることをユーザーに伝えるためのアクティビティインジケータ(ビジーマーク)を表示するプログラムをC#とF#で作成しました。
一定時間毎に...
を表示するためにRx(Observable.Timer
)を利用しました。
処理中
処理中.
処理中..
処理中...
- 処理が終わるまで上記の表示の繰り返し、処理が完了したら下記の表示で上書き
処理完了
C#版
nugetでSystem.Reactive
をインストールしておきます。
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
をインストールしておきます。
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