きなこもち.net

.NET Framework × UiPath,Orchestrator × Azure × AWS × Angularなどの忘備録

String × string × 違いについてまとめてみた

この記事の目的

この記事では、
[string]と[String]の違いをまとめること
を目的としています。

本題

★夏休み課題
  • Repositoryパターン関連
    • Repositoryパターンの使い方

--GoF デザインパターンとは
-- 実際の使われ方調査

  • Linqを使ったデータの操作
    • Where

- Lazy クラスの使い方

  • Unity

-- HelloWorld

    • UI作成
    • 画面遷移
    • あといろいろ
  • NEW qiita記事の移行←【ここ】
★きっかけ

Daily Dotnet Tipsの記事で、C#のstring とStringについて書かれたものを見たので、自分なりにまとめてみました。
Back to Basics – string Vs. String in C# - Daily .NET Tips

自分のよりどころ(c#の師匠)としているのは、プログラミング.NET Frameworkという本です。

プログラミング.NET Framework 第4版 (Microsoft Press)

プログラミング.NET Framework 第4版 (Microsoft Press)


★師匠からの教え

師匠は、以下のようにstringとStringの違いについて言及しています。

筆者は 、コ ードで s t r i n gと S t r i n gのどちらを使用すべきなのかがわからずに混乱している開発者を何人も見てきました 。 C #の s t r i n g (キ ーワ ード )はそのまま S y s t e m . S t r i n g ( F C Lの型 )に対応するので 、これらの間に違いはなく 、どちらも使用できます 。

混乱している開発者・・・自分もその開発者の一人ですw
C#の言語仕様としては、FCLの型名を利用するのではなくキーワードを利用する実装が推奨されているそうです。ただ、C#のキーワードで定義されてる型と、ほかのプログラミング言語で定義されている型で違いがある場合があります。例えば、C#のキーワードでlongとなっているものは、FCLではint64ですが、C++/CLIではint32として扱われるなどがあります。これらの理由から、キーワードではなくFCLの型名でかく方が良いという見解もあります。

結局のところ、型名とキーワードで内部的な処理が違っているわけでもないので、実装されたコードを見たとき、違和感なく、そして誤解なく読み取ることができる方を選択すれば良いと思いました。

★型名とキーワードの違い(見た目編)

f:id:kinakomotitti:20180819142252p:plain
水色がFCLの型名、濃い青がキーワードとなります。

★型名とキーワードの違い(現実編)

せっかくなので、実際のソリューションではどのように使われているのか簡単に調べてみました。
以下はlog4netの実装の一部です。
f:id:kinakomotitti:20180819142745p:plain

一部を切り取ったので、すべてが上記のようになっているわけではありません。
ただ、このように混在している実装もあるため、この問題は本当にどっちでも良いということが見て取れます。


まとめ

Stringとstringはほぼ同じ。
その違いは、FCLの型名か、キーワードであること。
公式では、キーワードが推奨されている。
FCLの色はみずいろであるため、若干見にくい。
見にくい人は、キーワードにして色を濃くしたら良いと思います。
※特にXMLドキュメントを書くときは、濃いほうが色がわかりやすい。
という例↓
f:id:kinakomotitti:20180819143412p:plain

C# × Lazy × どうやって使うのか調べてみた

この記事の目的

この記事では、
Lazyを使った実装についてまとめること
を目的としています。

本題

★夏休み課題
  • Repositoryパターン関連
  • Linqを使ったデータの操作
    • Where
  • Lazy クラスの使い方←【ここ】
  • Unity
    • HelloWorld
    • UI作成
    • 画面遷移
    • あといろいろ
★はじめに

とあるTwitterとQiita記事をきっかけに、C#のLazyクラスのことを知りました。
そこで、今回は、C#の師匠として使わせていただいている「プログラミング.NET Framework 第4版」の「Lazyに関する部分」を読んで学んだことをアウトプットしています。

プログラミング.NET Framework 第4版 (Microsoft Press)

プログラミング.NET Framework 第4版 (Microsoft Press)

★Lazyとは

インスタンスの初期化処理を遅延させます。遅延ってイメージしにくいですが、初めて使用されるまでオブジェクトの作成が延期されることを意味します。このクラスを使う目的は主に、パフォーマンスの改善、無駄な計算処理の回避、プログラムのメモリ要件の縮小です。

公式ドキュメントの場所
https://docs.microsoft.com/ja-jp/dotnet/framework/performance/lazy-initialization

★実装例

師匠で紹介されていたコードをベースに使い方を学んでいきます。

public void LazySample()
 {
     //1)public Lazy(Func<T> valueFactory)を使ってインスタンス生成時の処理を定義
     Lazy<String> lazy = new Lazy<string>(()=>
     {
         //インスタンス化された時(必要とされた時)の時間をString型で保持する。
         return DateTime.Now.ToLongDateString();
     });
 
     //2)インスタンスが生成されたかを確認(IsValueCreatedがTrueなら生成済み)
     Console.WriteLine(lazy.IsValueCreated); //ここではFalseとなる
 
     //3)値を問い合わせる
     //  最初のアクセスで、ラップされた型(ここではString)が作成され、返され、今後のアクセスのために保存される
     //  また、Value プロパティは読み取り専用
     Console.WriteLine(lazy.Value);
 
     //4)インスタンスが生成されたかを確認(IsValueCreatedがTrueなら生成済み)
     Console.WriteLine(lazy.IsValueCreated);//ここではtrueとなる
 
     System.Threading.Thread.Sleep(10000);
     //5)既に生成済みのインスタンスに対しての値問い合わせなので、3)と同じ値が返ってくる
     Console.WriteLine(lazy.Value);
 }

Valueプロパティにアクセスするまでは、インスタンス化されないので、メモリを節約することができるということがよくわかります。

まとめ

Lasyクラスは、アプリ起動時に設定ファイルを読み込み、保持するような部品のように、必要とされるまではインスタンス化しない方がメモリを節約できるものに利用することで効果を発揮するもの。アプリの基盤や共通部品を作成する際には意識しておきたいものですね。

Windows ML × tudorial × タクシー料金の予測

この記事の目的

この記事では、
Windows MLのチュートリアルの実施メモを残すこと
を目的としています。
元ネタは、Microsoft Docsに投稿されている「New York taxi fares」です。
docs.microsoft.com

本題

★問題の概要

問題は、「ニューヨークで走っているタクシーの利用料金の予測」です。
ニューヨークでは、基本的に、走行距離に比例して値段が変動します。
しかし、タクシー会社によっては、乗客の数や、クレジットカード払いなど様々な要因によって、請求額が変更されるらしいです。


機械学習タスクの選択

タクシーの利用料金を予測するためにまずやらなければならないことは、適切なmachine learning task(機械学習タスク)を選択することです!
教師データに含まれる様々な要因に基づき、実際の根大を予想しようとしているため、今回は、Regression task(回帰タスク)を選択します。

----
Regression Taskとは・・・
関連する項目のセットから、特定の項目の値を予測するタスク。
例)株価予測
----
詳しくはここ
https://docs.microsoft.com/en-us/dotnet/machine-learning/resources/tasks#regression

★アプリケーションの作成

利用する機械学習タスクが決まったので、操作対象のアプリケーションを作成します。アプリケーションは、,NET Coreをターゲットとしたコンソールアプリを作成します。
f:id:kinakomotitti:20180701194904p:plain
こんな感じで作りました。
いきなり名前を無視してしまった…まぁいいか。

次に、Dataフォルダをプロジェクトに作成します。教師データを格納する場所ですね。
f:id:kinakomotitti:20180701194912p:plain

最後に、Microsoft.MLをNugetからインストールします。
f:id:kinakomotitti:20180701194917p:plain
これをインストールして、
f:id:kinakomotitti:20180701194923p:plain
こうなりました。確かに、依存関係にMicrosoft MLが追加されました。

★教師データの準備

githubリポジトリから、教師データをダウンロードします。
github.com
f:id:kinakomotitti:20180701194937p:plain
【Download】ボタンで右クリックしないと、対象ファイルを取得できない仕様に若干手こずりました(/・ω・)/

ダウンロードした2つのファイルを前のステップで作成したDataフォルダに格納します。
f:id:kinakomotitti:20180701195010p:plain
f:id:kinakomotitti:20180701195017p:plain

プロジェクトに追加されていることも確認できました!
f:id:kinakomotitti:20180701195028p:plain
出力ディレクトリーにコピーする設定も忘れずに行う!

今追加したデータの中で、今後のステップで利用する項目は以下の通りです。
+ vendor_id:タクシー会社のID
+ rate_code:乗車比率?←ちょっとよくわからない
+ passenger_count:乗客の数
+ trip_time_in_secs:乗車時間
+ trip_distance:移動距離
+ payment_type:支払い方法
+ fare_amount:料金

★dataクラスの作成

予測に使うデータ用のクラスを作成します。
チュートリアルに従い、「TaxiTrip」クラスを作成していきます。
f:id:kinakomotitti:20180701195037p:plain
コードは以下の通りです。(長いので一部だけ抜粋)
>|cs|
[Column("0")]
public string VendorId;

[Column("1")]
public string RateCode;

[Column("2")]
public float PassengerCount;
|


次に、予測結果を出力する先のクラスも作成します。
(TaxiTripクラスファイルの中に定義していますが、別のファイルに定義しても問題ないと思います。)
>|cs|
public class TaxiTripFarePrediction
{
[ColumnName("Score")]
public float FareAmount;
}
|

ColumnNameをScoreとしています。
Score列は特別な列名で、ML.NETでは、予測結果を格納するために利用されます。

★モデルの定義

いよいよ、先ほど作成したモデルクラスに、データを流し込む処理を実装します。
チュートリアルの手順に従い、Program.csに読み込み専用変数を定義します。
これらは、Dataフォルダに格納したCSVファイルのパスを格納するために利用します。

★学習処理の作成

いっぱいUSINGに追加します!
>|cs|
using Microsoft.ML;
using Microsoft.ML.Data;
using Microsoft.ML.Models;
using Microsoft.ML.Trainers;
using Microsoft.ML.Transforms;
|


そして、いよいよモデルの作成処理を実装していきます!
ここからは、あらかじめ用意しておいたコードベースにメモを残しておきます。
※なお、コンソールアプリのエントリポイント(Mainメソッド)を非同期にする都合で、言語バージョンをC#7.1以上に設定する必要があります。
f:id:kinakomotitti:20180701195050p:plain

コードも長くなるので、チュートリアルの以下の項目を実施した後のコードを下に示します

★データのロードと整形
★学習アルゴリズムの選択
★学習実行
★モデルの評価
★学習済みモデルを使った予測の実施


まとめ

機械学習フレームワークチュートリアルをやってみました!
実装自体はサクサクできましたが、RMSやR値など、統計的な知識がなく、正確な予測ができているかすら分かりませんでした( ゚Д゚)

いよいよ、統計学に手を出す時が来た気がしました。。。

基礎学習 × .NET Framework の非同期処理を見直してみた × その4

この記事の目的

この記事では、

Taskクラスを使って画面の情報を更新する方法をまとめること

を目的としています。

ついでに、Windows Forms,WPF,UWPで非同期処理を実装する方法についても調べてみました。

本題

★ことの発端

GUIアプリケーション(特にUWP)にて、以下の処理を実装するために調べ始めました。

キャンセル可能なタスクを実行。
タスクを実行中は途中経過をUIに随時反映。
・タスクの途中でキャンセルされたとき、ContinueWithで指定したタスク(TaskContinuationOptions.OnlyOnCanceled)を実行。
ContinueWithで指定したタスクにより、画面に「キャンセルされました」メッセージを表示。

・タスクがキャンセルされずに完了した場合、ContinueWithで指定したタスク(TaskContinuationOptions.OnlyOnRanToCompletion)を実行。
ContinueWithで指定したタスクにより、画面に処理結果を表示。


★Taskクラスを使った場合

TaskのContinueWithメソッドに、TaskScheduler.FromCurrentSynchronizationContext()を渡すことで、UIスレッドで処理を実行することができます。
このTaskSchedulerは、規定では、スレッドプールタスクスケジューラーを利用します。
スレッドプールのタスクスケジューラーは、スレッドプールに対して、タスクをスケジュールするため、UIコントロールにアクセスしようとすると、例外が発生します。
実装イメージは以下の通りです。

 //TaskScheduler.Defaultを指定すると、ThradPoolのスレッドでタスクが実行されるため、以下の例外がスローされる。
 //System.Exception: 'アプリケーションは、別のスレッドにマーシャリングされたインターフェイスを呼び出しました。
 this.SampleTask.ContinueWith((task, status) =>
 {
     this.TaskJobStatus_TextBlock.Text = "canceled";//コントロールにアクセス
  }, new Object(),
  CancellationToken.None,
  TaskContinuationOptions.OnlyOnRanToCompletion,
  TaskScheduler.Default); //←ここ

何もしないままでは、Task実行中にUIコントロールを更新することはできませんが、Taskクラスには、UIコントロールにアクセスできるタスクをスケジュールするためのスケジューラーが用意されています。
それがTaskScheduler.FromCurrentSynchronizationContext()です。
このスケジューラーを利用することで、GUIスレッドにてタスクを実行することが可能となります。
先ほどのコードを書き換えると、以下のようになります。
この実装だと、UIコントロールにアクセスしても例外を発生させることなく実行することができます。

this.SampleTask.ContinueWith((task, status) =>
{
    this.TaskJobStatus_TextBlock.Text = MessageManager.WriteJobStatus("OnlyOnRanToCompletion");
    this.TaskJobStatus_TextBlock.Text += $"\r\nResult : {task.Result.ToString()}";
}, new Object(),
   CancellationToken.None,
   TaskContinuationOptions.OnlyOnRanToCompletion,
   TaskScheduler.FromCurrentSynchronizationContext()); //ここ

ただ、GUIスレッドを使うことになるため、重たい処理に対してこのオプションを指定すると、
画面が固まってしまいます。
画面を固めないようにするための非同期処理でもあるので、それでは本末転倒です(-_-メ)
あくまで、【処理結果】を画面に表示するときだけ利用するべきだと思います。

また、Taskのインスタンスを生成する時にはタスクスケジューラを指定できるオーバーロードがないため、TaskFactoryのStartNewを使う必要があります。
上記のコードでは、Taskのインスタンス生成時ではなくContinueWithしている時にタスクスケジューラのオプションを指定しています。
そのため、TaskFactoryは利用していません。



では、画面を固めないように、重たい処理中の途中経過を随時画面に反映するためには、どうしたらよいのか・・・と、いうことを次から見ていきます。
アプリケーションごとに違うので、Windows Forms、WPF、UWPと順番に見ていきます。

Windows Formsの場合

全てのコントロールに含まれるBeginInvokeメソッドを使うことで、
UIスレッドに対しコントロールの更新処理を実行することができます。
以下のブログを参考にしました。
Part 1. Windows フォームのマルチスレッド処理の基礎 – とあるコンサルタントのつぶやき

実装すると、以下のようになります。

private void LongTask()
 {
     for (int i = 0; i <= 100; i++)
     {
         // 何らかのタスクを実施...
         System.Threading.Thread.Sleep(100);
  
         // 進捗状態として画面を更新
         this.BeginInvoke(
             new UpdateProgressBarDelegate(UpdateProgressBar),
             new object[] { i });
     }
}
WPFの場合

System.Windows.Threading.Dispatcherを利用することで、
UIスレッドに対しコントロールの更新処理を実行することができます。
以下のブログを参考にしました。
非同期処理とディスパッチャーufcpp.wordpress.com


実装イメージは以下の通りです。

this.Dispatcher.BeginInvoke(new
      Action(() =>
      {
             //UIスレッドで実行すべき処理
       }));
});
★UWPの場合

最後に、UWPについてみていきます。
UWPも、WPFと同じようにDispatcherを使うことで、
UIスレッドに対しコントロールの更新処理を実行することができます。
UWPのDispacherは、Windows.UI.Core名前空間にあり、WPFのDispacherとは別のものです。
(プロパティ名がDispacherなだけで、クラス名はCoreDispatcherです。)

以下のように実装することができます。

 private int SampleMethod()
 {
     int sum = 0;
     int length = 10;
     for (int i = 0; i < length; i++)
      {
          Task.Run(async () =>
          {
              await this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
              {
                  //ユーザーインターフェースを操作する
                  this.TaskStatus_TextBlock.Text = i.ToString();
              });
          });
          sum += i;
      }
      return sum;
}

まとめ

TaskからUIを更新するときは、「タスクスケジューラーのオプションを指定」。
Windows Formsのときは、「System.Windows.Forms.ControlクラスのBeginInvokeメソッド」。
WPFのときは、「System.Windows.Threading.DispatcherクラスのBeginInvokeメソッド」。
UWPのときは、「Windows.UI.Core.CoreDispatcherのRunAsyncメソッド」。



これを意識して、冒頭に書いた「やりたいこと」ができるアプリを作ってみようと思います( ゚Д゚)

基礎学習 × .NET Framework の非同期処理を見直してみた × その3

この記事の目的

この記事では、
Taskクラスの基本的?な使い方をまとめること
を目的としています。

本題

★Taskクラスでよく使われるメソッドとは?

Intericodeでは以下のメソッドがよく使われる項目として表示されました。
f:id:kinakomotitti:20180608211952p:plain

InteriCodeについては、以下のページに詳細が記載されています。
www.visualstudio.com


★Task.Run

Taskのインスタンスを作成し、すぐにタスクを実行します。
Taskに後続のタスクを設定しない場合、このメソッドを利用することになります。

★Task.Start

StartメソッドはRunメソッドとは違い、Taskのインスタンスを作成した後、任意のタイミングでタスクを実行します。
こちらは、後続のタスクを設定した後にタスクを実行させたい場合に利用することになります。
また、以下のように定義することで、Runメソッドのように実行することも可能です。
ということで、今のところRunとStartの明確な使い分けができません(=_=)
人気があるのは、Runメソッドということですが(´▽`)

new Task(() => { /*何か処理*/}).Start();

★親Task、子Task

親タスク、子タスクを設定するには、タスクを定義するとき、TaskCreationOptionsのAttachedToParentオプションを指定します。

親に紐づいているタスク(子タスク)は、親タスクの完了時には完了していることが保証されますが、
紐づいていないタスクは、それが保証されません。
TaskCreationOptionsを注意深く見ないと、思わぬバグを仕込みそうです。。。
今のところ、実際の開発で使ったことも使う予定もないのでピンときません( ゚Д゚)

★条件付き実行処理

Task.ContinueWithメソッドを利用することで、元のタスクの完了時に指定したタスクを実行させることができます。
複数回ContinueWithを呼び出し、複数のタスクを元のタスクに紐づけることもできます。
この複数回ContinueWthメソッドを呼び出す方法は大きく2つあると思っています。
それらの2つの場合を意識して実装したコードと、それらの実行結果は以下の通りです。
1つ目:常に元タスクのインスタンスに対してContinueWithする場合
元タスクが完了したとき、元タスクにContinueWithが一斉に開始されます。
以下の例では、3つContinueWithしていたので、元タスク完了時にスレッドが3つ使われて処理が開始されました。

2つ目:ContinueWithの戻り値のタスクにContinueWithしていく場合

また、ContinueWithメソッドには、親Task、子タスクと同じようにTaskCreationOptionsを指定することができます。
TaskCreationOptionsには15個のオプションがあります。
Intelicodeでは確認したところ、以下の5つがよく使われているオプションでした。
f:id:kinakomotitti:20180608211937p:plain


特に人気の以下のオプションについて、実行時の違いを比較していきます。
なお、NotOnCanceledと比較できるようにするため、NotOnFaultedはOnlyOnCanceledにしました。
※Noneは省略しましたm(_ _)m
ExecuteSynchronously
NotOnRanToCompletion
NotOnCanceled
OnlyOnCanceled

ExecuteSynchronouslyの実装と実行結果↓

NotOnRanToCompletionの実装と実行結果↓

NotOnCanceled/OnlyOnCanceledの実装と実行結果↓

★Taskのキャンセル

前回のブログでまとめました(/・ω・)/
kinakomotitti.hatenablog.com


まとめ

・処理が終わったタスクの実行結果(成功、失敗、キャンセル)を確認して、
 それに合わせて次の処理を呼び出すという処理が必要な場合、Taskを利用する。
・ContinueWithを利用したタスク処理を実装・レビューするときは、
T askCreationOptionsをよく見て、適切なオプションであるか確認する。

基礎学習 × .NET Framework の非同期処理を見直してみた × その2

この記事の目的

この記事では、
.【NET Framework】非同期処理のキャンセル処理についてまとめること
を目的としています。

本題

★はじめに

ここでは、「プログラミング.NET Framework 第4版」を読んで学んだことを中心にアウトプットします。

「その2」ということで、今回は、Threadクラス、ThreadPoolクラス、Taskクラスを使った非同期処理のキャンセルの実装方法とそれぞれの注意点をまとめてみます。

.NET Frameworkの非同期処理概要・キャンセル編

教科書にはこうありました。

    • -

.NET Frameworkはキャンセル処理の標準的なパターンを提供しています。
このパターンは協調的(cooperative)です。

キャンセルしたい処理を実行するコードと、
処理をキャンセルしようとするコードは、
両方ともこれから説明する型を使用しなければなりません。

    • -


基本的に、.NET Frameworkで非同期処理をキャンセルするためには、
System.Threading.CancellationTokenSource
を使った実装をする必要があります。

ということで、ここではNET Framework 4 以降で導入された、
「キャンセルのための統一されたモデル」にだけ絞って実装を整理していきます。

★Threadクラス

Threadクラスについても、CancellationTokenを使った協調的なキャンセル処理を実装することができます。

試しに、Static変数を使って、キャンセルされたことを非同期処理を実行しているスレッドに通知したり(以下のコードの「パターン1;その1」)、Abortメソッドを使い、スレッドそのものを停止させる(以下のコードの「パターン1;その2」)という処理の実装もしてみましたが、特に意味はなさそうです(´▽`)

非同期処理を実行するメソッドにCancellationTokenを渡し、
非同期処理を実行するメソッド内の処理で、CancellationTokenのIsCancellationRequestedプロパティにアクセスすることで、処理がキャンセルされたことを知ることができます(以下のコードの「パターン2」)


★ThreadPoolクラス

ThreadPoolクラスでも、Threadクラスと同じように、協調的なキャンセルのパターンが利用することができます。

当然ですが、キャンセル処理を想定していないメソッドはキャンセルすることができません(以下のコードの「キャンセルできない」とコメントがあるところ)


★Taskクラス

Taskクラスでも、Thread/ThreadPoolクラスと同じように、CancellationTokenを利用して協調的なキャンセル処理を実装することができます。

TaskとThread/ThreadPoolの違いは、Taskには以下のステータスの違いがある点です。
・完了:処理をすべて実行し、処理が正常に終わったことを示す
・失敗:処理が何らかの理由で失敗したことを示す
・キャンセル:処理をすべて実行できなった(キャンセルされた)ことを示す

そのため、Taskでは、これらのステータスを知るため、ThrowIfCancellationRequestedメソッドを利用します。
ThrowIfCancellationRequestedを呼び出すと、System.Threading.Tasks.TaskCanceledExceptionがスローされます。
この例外は、TaskのResultプロパティにアクセスしたとき、System.AggregateException のInnerExceptionsリストに格納されてリスローされます。





その2、まとめ

よくよく調べてみると、.NET Framework4以降に追加されたCancellation Tokenによる協調的なキャンセルモデルにより、Taskも、Threadも、ThreadPoolも、同じような実装で処理をキャンセルすることができました。

ということで、今回のまとめは以下の通りです。

  • 非同期処理のキャンセルはCancellationTokenを利用する
  • Thread/ThtreadPoolを利用するときは、IsCancellationRequested。
  • Taskを利用するときは、IsCancellationRequestedで判断したのち、ThrowIfCancellationRequestedで例外をスローする。

基礎学習 × .NET Framework の非同期処理を見直してみた × その1

この記事の目的

この記事では、
.NET Frameworkの非同期処理についての基本的なことをまとめること
を目的としています。

また、基本的なことをまとめる中で、
「非同期処理プログラミングを学ぶ上で、どこから手を出していけばよいか」
を自分なりに考え直してみます。

本題

★はじめに

ここでは、「プログラミング.NET Framework 第4版」を読んで学んだことをアウトプットしています。

「その1」ということで、今回は、多数ある実装方法のうち、
Thread、ThreadPool、Taskクラスの概要と、
それらを使って非同期処理を開始するための実装についてまとめます。

さらに、これまで個人的に後回しにしていたAsync/Awaitについてもまとめてみます。

.NET Frameworkの非同期処理概要

I/O バインドのニーズ (ネットワークからのデータの要求、データベースへのアクセスなど) がある場合、非同期プログラミングを利用することになります。 CPU バインドのコードにも、コストのかかる計算の実行など、非同期コードに適したシナリオがあります。

docs.microsoft.com

Microsoft Docsには上記のように紹介されています。
このように、計算処理の実行時間を短縮したり、
GUIアプリケーションで、処理中でも画面を操作不能にしないようにするときにも
非同期処理が必要となる場面が多くあります。

★Threadクラス

Threadクラスは、専用スレッドを作成することができるクラスです。
実行をバックグラウンド・フォアグラウンドで実行するように指定することができたり*1
スレッドの優先度を指定して実行することができたりと、
スレッドに対していろいろな設定を行うことができます。

しかし、ThreadPoolのように、”前に使ったスレッドをつかいまわす”ことがで来ません。
それにより、スレッドを作成するためのコストが毎回かかってくるため、パフォーマンスの面で課題があります。
これらの理由から、Threadクラスでの非同期処理の実装は推奨されていないようです。

そんなThreadクラスを使った実装は以下のようになります。

★ThreadPoolクラス

ThreadPoolクラスを利用して非同期処理を実装すると、CLRで用意されたスレッドプールを利用することができます。
スレッドプールは、以下のような特徴があります。

アプリケーション自身が使用できるスレッドのセットと考えることができます。

  • -

スレッドプールのスレッドがタスクを完了したとき、そのスレッドは破棄されずにスレッドプールに戻され、別の要求に応答するために待機し続けます。

スレッドプールにスレッドがない場合は、スレッドの作成が行われますが、
スレッドがある場合は、あるものを使うため、スレッド作成にかかるコストがかかりません。

ちなみに、スレッドプールのスレッドは規定で、バックグラウンドで実行されます。

そんなThreadPoolクラスを使った実装は以下のようになります。

★Taskクラス

ThreadPoolクラスでは、スレッドの再利用、非同期処理のキャンセル、タイムアウトなど、よく使いそうな機能がサポートされています。
しかし、非同期処理が完了したという通知を呼び出し元にできないという問題があります。
また、処理の結果を呼び出し元に返すこともできません。

それらの問題を解決するために登場したのがTaskクラスです。
Taskクラスを利用することで、ThreadPoolクラスのいいところはそのままに、
呼び出し元に処理(タスク)の完了通知や、結果を返すことができます。
タスクの完了通知が受け取れることで、
タスクの完了を待って、後続の処理を実行することができるようになります。
Taskクラスのこれらの機能は、また別枠で整理していこうと思います(´▽`)

そんなTaskクラスを使った実装は以下のようになります。

★Async/Await

Async/Await修飾子を使った非同期処理の実装はとてもシンプルで、
ほぼ、同期処理の実装と同じように実装することができる。

ただ、シンプルなだけ、内部で行われていることを把握しようとするととても大変です。
参考書には以下のように説明があります。

メソッドをasyncで修飾すると、基本的に、コンパイラはメソッドのコードを、非同期ステートマシンを実装する型に変換します。これによって、スレッドがいくつかのコードを非同期ステートマシン内で実行するようにし、完了に必要なコードすべてを実行することなく制御を戻すことができるようになります。

非同期ステートマシン・・・

わかりそうでわからない・・・

ここが理解できるところを一つのゴールにして今後も調べていきます(=_=)
ここでは、公式のDocsにあった実装時の注意点で気になった内容を確認して、お茶を濁そうと思いますw

async メソッドの本体に await キーワードが含まれないと、何も行われません
これは、忘れてはならない重要なことです。 await が async メソッドの本体で使われていない場合、C# コンパイラは警告を生成しますが、コードは通常のメソッドと同様にコンパイルされて実行されます。 また、非同期メソッドに対して C# コンパイラが生成するステート マシンは何も行わないので、非常に非効率的であることにも注意してください。

docs.microsoft.com

awaitのないメソッドはただの同期処理として扱われるようです。
以下、試してみた実装と結果です。

★結局、どんな時にどれを使えばいいのか

ここにあるだけで、3つの実装方法があります。
結局どれを選択していけばよいのか迷いますが、
実装効率、Docsを参考にすると、非同期処理は、Task、Taskを利用するのが一番だと思います。
また、Taskを利用するにあたり、Async/Await修飾子を利用することで、煩雑になりがちな非同期処理をシンプルに実装することができます。
パフォーマンス問題や、特殊なシナリオを実現する必要があるときだけ、Threadクラスなどを視野に入れていけばよいのかなと思います。

その1、まとめ

非同期処理を実装するための技術として、歴史的にはいろいろなものがありますが、
Taskクラスを利用する非同期処理を把握するのが一番!(のはず)

それ以外は、出てきたときにまた考えますw