【C#】【book】「実戦で役立つ C# プログラミングのイディオム/定石&パターン」の学習記録(16)

下記の「実戦で役立つ C# プログラミングのイディオム/定石&パターン」を使った学習記録のページです。
実戦で役立つ C#プログラミングのイディオム/定石&パターン
 
本学習記録のトップページはこちら
 

 

16.2 async/await 以前の非同期プログラミング

 

同期処理

  • シングルタスクのアプリケーション
    • ボタンを押すと 3秒間何もできない

 

プロジェクト作成

f:id:dnkrnka:20181112225430p:plain
 

使用例
起動直後 ボタン押下後 3秒以内 3秒経過後
f:id:dnkrnka:20181112225530p:plain:w300 f:id:dnkrnka:20181112230030p:plain:w300 f:id:dnkrnka:20181112230108p:plain:w300

 

Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MultiThreadSample
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
    }

    private void Form1_Load( object sender, EventArgs e )
    {

    }

    private void button1_Click( object sender, EventArgs e )
    {
      label1.Text = "";
      Cursor = Cursors.WaitCursor; // 待機中にカーソルを砂時計や回転などのアイコンに変える

      DoLongTimeWork(); // 何か時間が掛かる処理をする

      label1.Text = "現在の状態: 終了";
      Cursor = Cursors.Arrow; // カーソルを元の矢印状に戻す
    }

    private void DoLongTimeWork()
    {
      // ここで本来は時間のかかる処理を書く
      // Thread.Sleepメソッドは、指定時間だけ処理を待機するメソッド。
      Thread.Sleep( 3000 ); // 3 sec
    }

    private void label1_Click( object sender, EventArgs e )
    {
    }
  }
}

 

Form1.Designer.cs

本コードは、Form デザインを変更すると Visual Studio により自動的に作成される。
従って手動での作成・変更はしていない。

namespace MultiThreadSample
{
  partial class Form1
  {
    /// <summary>
    /// 必要なデザイナー変数です。
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary>
    /// 使用中のリソースをすべてクリーンアップします。
    /// </summary>
    /// <param name="disposing">マネージド リソースを破棄する場合は true を指定し、その他の場合は false を指定します。</param>
    protected override void Dispose( bool disposing )
    {
      if ( disposing && (components != null) )
      {
        components.Dispose();
      }
      base.Dispose( disposing );
    }

    #region Windows フォーム デザイナーで生成されたコード

    /// <summary>
    /// デザイナー サポートに必要なメソッドです。このメソッドの内容を
    /// コード エディターで変更しないでください。
    /// </summary>
    private void InitializeComponent()
    {
      this.components = new System.ComponentModel.Container();
      this.button1 = new System.Windows.Forms.Button();
      this.label1 = new System.Windows.Forms.Label();
      this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components);
      this.SuspendLayout();
      // 
      // button1
      // 
      this.button1.Location = new System.Drawing.Point(23, 23);
      this.button1.Name = "button1";
      this.button1.Size = new System.Drawing.Size(173, 30);
      this.button1.TabIndex = 0;
      this.button1.Text = "例:非 Thread のパターン";
      this.button1.UseVisualStyleBackColor = true;
      this.button1.Click += new System.EventHandler(this.button1_Click);
      // 
      // label1
      // 
      this.label1.AutoSize = true;
      this.label1.Location = new System.Drawing.Point(21, 77);
      this.label1.Name = "label1";
      this.label1.Size = new System.Drawing.Size(121, 12);
      this.label1.TabIndex = 1;
      this.label1.Text = "現在の状態: 初期状態";
      this.label1.Click += new System.EventHandler(this.label1_Click);
      // 
      // contextMenuStrip1
      // 
      this.contextMenuStrip1.Name = "contextMenuStrip1";
      this.contextMenuStrip1.Size = new System.Drawing.Size(61, 4);
      // 
      // Form1
      // 
      this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
      this.ClientSize = new System.Drawing.Size(468, 109);
      this.Controls.Add(this.label1);
      this.Controls.Add(this.button1);
      this.Name = "Form1";
      this.Text = "Form1";
      this.Load += new System.EventHandler(this.Form1_Load);
      this.ResumeLayout(false);
      this.PerformLayout();

    }

    #endregion

    private System.Windows.Forms.Button button1;
    private System.Windows.Forms.Label label1;
    private System.Windows.Forms.ContextMenuStrip contextMenuStrip1;
  }
}

 

Program.cs

下記のコードは、Form アプリを選択すると Visual Studio が自動生成してくれる。
従って手動での作成・変更はしていない。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MultiThreadSample
{
  static class Program
  {
    /// <summary>
    /// アプリケーションのメイン エントリ ポイントです。
    /// </summary>
    [STAThread]
    static void Main()
    {
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault( false );
      Application.Run( new Form1() );
    }
  }
}

 
 

16.2.1 Thread を使った非同期処理

  • Thread クラスを使ったマルチスレッドのアプリケーション
    • ボタンを押してから 3秒間であってもアプリケーションの操作( 「×」を押して閉じる) ができる

 

プロジェクト作成

f:id:dnkrnka:20181112232750p:plain
 

Form1.cs [デザイン]

f:id:dnkrnka:20181112232907p:plain
 

Form1.cs

Thread クラスの使い方はコード中にコメントとして書いている。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MultiTask2
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
    }

    private void button1_Click( object sender, EventArgs e )
    {
      label1.Text = "";
      var th = new Thread(DoSomething);
      th.Start(); // DoSomething が非同期で実行される
    }

    private void DoSomething()
    {
      DoLongTimeWork(); // 時間の掛かる処理

      // フォーム上へのコントロールは UI スレッドでなければアクセスできない。
      // そのために Invoke メソッドを使って UI スレッドに Label の更新をさせている。
      label1.Invoke( (Action)delegate ()
      {
        label1.Text = "現在の状態: 終了";
      } );
    }

    private void DoLongTimeWork()
    {
      // ここで本来は時間のかかる処理を書く
      // Thread.Sleepメソッドは、指定時間だけ処理を待機するメソッド。
      Thread.Sleep( 3000 ); // 3 sec
    }
  }
}

 

Form1.Designer.cs

本コードは、Form デザインを変更すると Visual Studio により自動的に作成される。
従って手動での作成・変更はしていない。

namespace MultiTask2
{
  partial class Form1
  {
    /// <summary>
    /// 必要なデザイナー変数です。
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary>
    /// 使用中のリソースをすべてクリーンアップします。
    /// </summary>
    /// <param name="disposing">マネージド リソースを破棄する場合は true を指定し、その他の場合は false を指定します。</param>
    protected override void Dispose( bool disposing )
    {
      if ( disposing && (components != null) )
      {
        components.Dispose();
      }
      base.Dispose( disposing );
    }

    #region Windows フォーム デザイナーで生成されたコード

    /// <summary>
    /// デザイナー サポートに必要なメソッドです。このメソッドの内容を
    /// コード エディターで変更しないでください。
    /// </summary>
    private void InitializeComponent()
    {
      this.button1 = new System.Windows.Forms.Button();
      this.label1 = new System.Windows.Forms.Label();
      this.SuspendLayout();
      // 
      // button1
      // 
      this.button1.Location = new System.Drawing.Point(12, 21);
      this.button1.Name = "button1";
      this.button1.Size = new System.Drawing.Size(283, 23);
      this.button1.TabIndex = 0;
      this.button1.Text = "Thread クラスを使った非同期通信をする";
      this.button1.UseVisualStyleBackColor = true;
      this.button1.Click += new System.EventHandler(this.button1_Click);
      // 
      // label1
      // 
      this.label1.AutoSize = true;
      this.label1.Location = new System.Drawing.Point(12, 72);
      this.label1.Name = "label1";
      this.label1.Size = new System.Drawing.Size(121, 12);
      this.label1.TabIndex = 1;
      this.label1.Text = "現在の状態: 初期状態";
      // 
      // Form1
      // 
      this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
      this.ClientSize = new System.Drawing.Size(318, 92);
      this.Controls.Add(this.label1);
      this.Controls.Add(this.button1);
      this.Name = "Form1";
      this.Text = "Form1";
      this.ResumeLayout(false);
      this.PerformLayout();

    }

    #endregion

    private System.Windows.Forms.Button button1;
    private System.Windows.Forms.Label label1;
  }
}

 

Program.cs

下記のコードは、Form アプリを選択すると Visual Studio が自動生成してくれる。
従って手動での作成・変更はしていない。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MultiTask2
{
  static class Program
  {
    /// <summary>
    /// アプリケーションのメイン エントリ ポイントです。
    /// </summary>
    [STAThread]
    static void Main()
    {
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault( false );
      Application.Run( new Form1() );
    }
  }
}

 
 

16.2.2 BackgroundWorker クラスを使った非同期処理

プロジェクト作成

f:id:dnkrnka:20181112235847p:plain
 

Form1.cs [デザイン]

f:id:dnkrnka:20181113000017p:plain

 

Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MultiThreadSample3
{
  public partial class Form1 : Form
  {
    // インスタンスを獲得しておく
    private BackgroundWorker _worker = new BackgroundWorker();

    public Form1()
    {
      InitializeComponent();
      _worker.DoWork += _worker_DoWork; // RunWorkerAsync メソッドで呼び出されるメソッドを定義する
      _worker.RunWorkerCompleted += _worker_RunWorkerCompleted; // 処理が完了したときに呼び出されるメソッドを定義する
    }

    private void _worker_DoWork( object sender, DoWorkEventArgs e )
    {
      DoLongTimeWork();   // 時間が掛かる処理
    }

    private void _worker_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e )
    {
      label1.Text = "現在の状態:終了";
    }

    private void button1_Click( object sender, EventArgs e )
    {
      label1.Text = "";
      _worker.RunWorkerAsync(); // DoWork イベントに登録したメソッドが呼び出される
    }

    private void DoLongTimeWork()
    {
      // ここで本来は時間のかかる処理を書く
      // Thread.Sleepメソッドは、指定時間だけ処理を待機するメソッド。
      Thread.Sleep( 3000 ); // 3 sec
    }
  }
}

 

Form1.Designer.cs

本コードは、Form デザインを変更すると Visual Studio により自動的に作成される。
従って手動での作成・変更はしていない。

namespace MultiThreadSample3
{
  partial class Form1
  {
    /// <summary>
    /// 必要なデザイナー変数です。
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary>
    /// 使用中のリソースをすべてクリーンアップします。
    /// </summary>
    /// <param name="disposing">マネージド リソースを破棄する場合は true を指定し、その他の場合は false を指定します。</param>
    protected override void Dispose( bool disposing )
    {
      if ( disposing && (components != null) )
      {
        components.Dispose();
      }
      base.Dispose( disposing );
    }

    #region Windows フォーム デザイナーで生成されたコード

    /// <summary>
    /// デザイナー サポートに必要なメソッドです。このメソッドの内容を
    /// コード エディターで変更しないでください。
    /// </summary>
    private void InitializeComponent()
    {
      this.button1 = new System.Windows.Forms.Button();
      this.label1 = new System.Windows.Forms.Label();
      this.SuspendLayout();
      // 
      // button1
      // 
      this.button1.Location = new System.Drawing.Point(12, 12);
      this.button1.Name = "button1";
      this.button1.Size = new System.Drawing.Size(326, 23);
      this.button1.TabIndex = 0;
      this.button1.Text = "BackgroundWorker クラスを使った非同期通信をする";
      this.button1.UseVisualStyleBackColor = true;
      this.button1.Click += new System.EventHandler(this.button1_Click);
      // 
      // label1
      // 
      this.label1.AutoSize = true;
      this.label1.Location = new System.Drawing.Point(12, 60);
      this.label1.Name = "label1";
      this.label1.Size = new System.Drawing.Size(121, 12);
      this.label1.TabIndex = 1;
      this.label1.Text = "現在の状態: 初期状態";
      // 
      // Form1
      // 
      this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
      this.ClientSize = new System.Drawing.Size(399, 86);
      this.Controls.Add(this.label1);
      this.Controls.Add(this.button1);
      this.Name = "Form1";
      this.Text = "Form1";
      this.ResumeLayout(false);
      this.PerformLayout();

    }

    #endregion

    private System.Windows.Forms.Button button1;
    private System.Windows.Forms.Label label1;
  }
}

 

Program.cs

下記のコードは、Form アプリを選択すると Visual Studio が自動生成してくれる。
従って手動での作成・変更はしていない。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MultiThreadSample3
{
  static class Program
  {
    /// <summary>
    /// アプリケーションのメイン エントリ ポイントです。
    /// </summary>
    [STAThread]
    static void Main()
    {
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault( false );
      Application.Run( new Form1() );
    }
  }
}

 
 

16.2.2 BackgroundWorker クラスを使った非同期処理(2)

BackgroundWorkerで進行状況を表示させる例

プロジェクト作成

f:id:dnkrnka:20181113223404p:plain
 

Form1.cs [デザイン]
初期状態 実行中 完了
f:id:dnkrnka:20181113223151p:plain:w300 f:id:dnkrnka:20181113223226p:plain:w300 f:id:dnkrnka:20181113223248p:plain:w300

 

Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MultiThreadSample3
{
  public partial class Form1 : Form
  {
    private BackgroundWorker _worker = new BackgroundWorker();

    public Form1()
    {
      InitializeComponent();
      _worker.DoWork += _worker_DoWork; // RunWorkerAsync メソッドで呼び出されるメソッドを定義する
      _worker.RunWorkerCompleted += _worker_RunWorkerCompleted; // 処理が完了したときに呼び出されるメソッドを定義する
      _worker.ProgressChanged += _worker_ProgressChanged; // 進行状況を表示する
      _worker.WorkerReportsProgress = true;
    }

    // 何か時間が掛かる処理。前述の DoLongTimeWork に相当する
    private void _worker_DoWork( object sender, DoWorkEventArgs e )
    {
      var collection = Enumerable.Range( 1, 200 ).ToArray();
      int count = 0;
      foreach ( var n in collection )
      {
        // nに対する処理をする
        // DoWork(n);
        Thread.Sleep( 10 );  // DoWorkの代わりのコード
                             // 何パーセントまで処理をしたか求める
        var per = count * 100 / collection.Length;
        // プログレスバーを更新するために、処理状況を通知する
        _worker.ReportProgress( Math.Min( per, toolStripProgressBar1.Maximum ), null );
        count++;
      }
    }

    // Progress バーの更新をする
    private void _worker_ProgressChanged( object sender, ProgressChangedEventArgs e )
    {
      toolStripProgressBar1.Value = e.ProgressPercentage;
    }

    private void _worker_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e )
    {
      toolStripProgressBar1.Value = toolStripProgressBar1.Maximum; // Progressバーを最大まで進める
      label1.Text = "現在の状態:終了";
    }

    private void button1_Click( object sender, EventArgs e )
    {
      label1.Text = "";
      _worker.RunWorkerAsync(); // DoWork イベントに登録したメソッドが呼び出される
    }

    private void toolStripProgressBar1_Click( object sender, EventArgs e )
    {

    }
  }
}

 

Form1.Designer.cs

本コードは、Form デザインを変更すると Visual Studio により自動的に作成される。
従って手動での作成・変更はしていない。

namespace MultiThreadSample3
{
  partial class Form1
  {
    /// <summary>
    /// 必要なデザイナー変数です。
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary>
    /// 使用中のリソースをすべてクリーンアップします。
    /// </summary>
    /// <param name="disposing">マネージド リソースを破棄する場合は true を指定し、その他の場合は false を指定します。</param>
    protected override void Dispose( bool disposing )
    {
      if ( disposing && (components != null) )
      {
        components.Dispose();
      }
      base.Dispose( disposing );
    }

    #region Windows フォーム デザイナーで生成されたコード

    /// <summary>
    /// デザイナー サポートに必要なメソッドです。このメソッドの内容を
    /// コード エディターで変更しないでください。
    /// </summary>
    private void InitializeComponent()
    {
      this.button1 = new System.Windows.Forms.Button();
      this.label1 = new System.Windows.Forms.Label();
      this.toolStripProgressBar1 = new System.Windows.Forms.ToolStripProgressBar();
      this.toolStrip1 = new System.Windows.Forms.ToolStrip();
      this.toolStrip1.SuspendLayout();
      this.SuspendLayout();
      // 
      // button1
      // 
      this.button1.Location = new System.Drawing.Point(12, 12);
      this.button1.Name = "button1";
      this.button1.Size = new System.Drawing.Size(326, 23);
      this.button1.TabIndex = 0;
      this.button1.Text = "BackgroundWorker クラスを使った非同期通信をする";
      this.button1.UseVisualStyleBackColor = true;
      this.button1.Click += new System.EventHandler(this.button1_Click);
      // 
      // label1
      // 
      this.label1.AutoSize = true;
      this.label1.Location = new System.Drawing.Point(12, 60);
      this.label1.Name = "label1";
      this.label1.Size = new System.Drawing.Size(121, 12);
      this.label1.TabIndex = 1;
      this.label1.Text = "現在の状態: 初期状態";
      // 
      // toolStripProgressBar1
      // 
      this.toolStripProgressBar1.Name = "toolStripProgressBar1";
      this.toolStripProgressBar1.Size = new System.Drawing.Size(100, 22);
      this.toolStripProgressBar1.Click += new System.EventHandler(this.toolStripProgressBar1_Click);
      // 
      // toolStrip1
      // 
      this.toolStrip1.Dock = System.Windows.Forms.DockStyle.Bottom;
      this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
            this.toolStripProgressBar1});
      this.toolStrip1.Location = new System.Drawing.Point(0, 74);
      this.toolStrip1.Name = "toolStrip1";
      this.toolStrip1.Size = new System.Drawing.Size(346, 25);
      this.toolStrip1.Stretch = true;
      this.toolStrip1.TabIndex = 2;
      this.toolStrip1.Text = "toolStrip1";
      // 
      // Form1
      // 
      this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
      this.ClientSize = new System.Drawing.Size(346, 99);
      this.Controls.Add(this.toolStrip1);
      this.Controls.Add(this.label1);
      this.Controls.Add(this.button1);
      this.Name = "Form1";
      this.Text = "Form1";
      this.toolStrip1.ResumeLayout(false);
      this.toolStrip1.PerformLayout();
      this.ResumeLayout(false);
      this.PerformLayout();

    }

    #endregion

    private System.Windows.Forms.Button button1;
    private System.Windows.Forms.Label label1;
    private System.Windows.Forms.ToolStripProgressBar toolStripProgressBar1;
    private System.Windows.Forms.ToolStrip toolStrip1;
  }
}

 

Program.cs

下記のコードは、Form アプリを選択すると Visual Studio が自動生成してくれる。
従って手動での作成・変更はしていない。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MultiThreadSample3
{
  static class Program
  {
    /// <summary>
    /// アプリケーションのメイン エントリ ポイントです。
    /// </summary>
    [STAThread]
    static void Main()
    {
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault( false );
      Application.Run( new Form1() );
    }
  }
}

 
 

16.2.3 Task クラスを使った非同期処理

前述の Thread クラスを使った処理を Task クラスに置き換えると次のようになる。
変更点のみ記す。(「NET40」というプリプロセスで囲った範囲が変更点である)

Form1.cs
#define NET40

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MultiTask2
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
#if NET40
      button1.Text = "Task クラスを使った非同期通信をする";
#endif
    }

    private void button1_Click( object sender, EventArgs e )
    {
      label1.Text = "";
#if NET40
      Task.Run( () => DoSomething() );
#else
      var th = new Thread(DoSomething);
      th.Start(); // DoSomething が非同期で実行される
#endif
    }

    private void DoSomething()
    {
      DoLongTimeWork(); // 時間の掛かる処理

      // フォーム上へのコントロールは UI スレッドでなければアクセスできない。
      // そのために Invoke メソッドを使って UI スレッドに Label の更新をさせている。
      label1.Invoke( (Action)delegate ()
      {
        label1.Text = "現在の状態: 終了";
      } );
    }

    private void DoLongTimeWork()
    {
      // ここで本来は時間のかかる処理を書く
      // Thread.Sleepメソッドは、指定時間だけ処理を待機するメソッド。
      Thread.Sleep( 3000 ); // 3 sec
    }
  }
}

 

16.2.3 Task クラスを使った非同期処理(2)

前述の 16.2.3 Task クラスを使った非同期処理 は単に Thread クラスを置換しただけであり、優位性が無かった。
しかし, ContinueWith メソッドを使うと、子スレッドから UI スレッドにアクセスするための Invoke メソッドを使う必要が無くなる。
16.2.3 Task クラスを使った非同期処理 からの変更点は「button1_Click」「DoSomething」の2つのメソッドのみである。
変更点についてはコード中にコメントを書いた。

Form1.cs
#define NET40

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MultiTask2
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
#if NET40
      button1.Text = "Task クラスを使った非同期通信をする";
#endif
    }

    private void button1_Click( object sender, EventArgs e )
    {
      label1.Text = "";
#if NET40
      // ContinueWith メソッドを使うことで Invoke メソッドが不要になった
      var currentContext = TaskScheduler.FromCurrentSynchronizationContext();
      Task.Run( () =>
       {
         DoSomething();
       } )
      .ContinueWith( task =>
       {
         label1.Text = "現在の状態: 終了";
       }, currentContext );
#else
      var th = new Thread(DoSomething);
      th.Start(); // DoSomething が非同期で実行される
#endif
    }

    // DoSomething では label1 の情報更新が不要になり、本来の処理のみ記述できるようになった
    private void DoSomething()
    {
      DoLongTimeWork(); // 時間の掛かる処理
    }

    private void DoLongTimeWork()
    {
      // ここで本来は時間のかかる処理を書く
      // Thread.Sleepメソッドは、指定時間だけ処理を待機するメソッド。
      Thread.Sleep( 3000 ); // 3 sec
    }
  }
}

 
 

16.3.1 イベントハンドラを非同期にする

前述の 16.2.3 Task クラスを使った非同期処理(2) を async, await に置き換えたプログラム。
処理については、下記コードにコメントとして書いた。
なお、ルール(文法)としては以下がある。

  • async で修飾されたメソッド内では await を使える
Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MultiTask2
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
      button1.Text = "async, await を使った非同期通信をする";
    }

    // button1_Click メソッドに async キーワードを付与する.
    // これにより非同期メソッドとなる
    private async void button1_Click( object sender, EventArgs e )
    {
      label1.Text = "";

      await Task.Run( () => DoSomething()); // await メソッドを使うことで処理の終了を待つ。

      label1.Text = "現在の状態: 終了"; // 直前の await を付けた処理が終了するまでここには到達しない
    }

    private void DoSomething()
    {
      DoLongTimeWork(); // 時間の掛かる処理
    }

    private void DoLongTimeWork()
    {
      // ここで本来は時間のかかる処理を書く
      // Thread.Sleepメソッドは、指定時間だけ処理を待機するメソッド。
      Thread.Sleep( 3000 ); // 3 sec
    }
  }
}

 
 

16.3.1 イベントハンドラを非同期にする(2)

上記[16.3.1 イベントハンドラを非同期にする」から await で待つ処理から戻り値を受け取るように変更した例である。
StopWatch クラスを使って処理時間を算出し、呼び出し元に返している。
 

実行例

f:id:dnkrnka:20181113232600p:plain
 

form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MultiTask2
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
      button1.Text = "async, await を使った非同期通信をする";
    }

    // button1_Click メソッドに async キーワードを付与することで非同期メソッドとなる
    private async void button1_Click( object sender, EventArgs e )
    {
      label1.Text = "";

      var currentContext = TaskScheduler.FromCurrentSynchronizationContext();
      var elapsed = await Task.Run( () => DoSomething()); // await メソッドを使うことで処理の終了を待つ。
      // 直前の await を付けた処理が終了するまでここには到達しない
      label1.Text = $"現在の状態: 終了  [処理時間 = {elapsed}ミリ秒]";
    }

    private long DoSomething()
    {
      var sw = Stopwatch.StartNew();
      DoLongTimeWork(); // 時間の掛かる処理
      sw.Stop();
      return sw.ElapsedMilliseconds;
    }

    private void DoLongTimeWork()
    {
      // ここで本来は時間のかかる処理を書く
      // Thread.Sleepメソッドは、指定時間だけ処理を待機するメソッド。
      Thread.Sleep( 3000 ); // 3 sec
    }
  }
}

 
 

16.3.2 非同期メソッドを定義する

Form1.cs

非同期メソッド

  • メソッドを非同期にするには以下2つの条件を満たす必要がある
    • async キーワードを付与する
    • 戻り値が無い場合は Task 型にする
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MultiTask2
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
      button1.Text = "async, await を使った非同期通信をする";
    }

    // button1_Click メソッドに async キーワードを付与することで非同期メソッドとなる
    // 本関数内で await を使いたいので, 本関数には async キーワードを付与する
    private async void button1_Click( object sender, EventArgs e )
    {
      label1.Text = "";

      await DoSomething(); // await メソッドを使うことで処理の終了を待つ。

      // 直前の await を付けた処理が終了するまでここには到達しない
      label1.Text = $"現在の状態: 終了]";
    }

    // 非同期メソッド
    // メソッドを非同期にするには以下2つの条件を満たす必要がある
    // - async キーワードを付与する
    // - 戻り値が無い場合は Task 型にする
    private async Task DoSomething()
    {
      // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
      // await を使わずに単に DoLongTimeWork() の呼び出してしまうと
      // 非同期通信にはならない
      await Task.Run( () =>
      {
        DoLongTimeWork(); // 時間の掛かる処理
      }
      );
    }

    private void DoLongTimeWork()
    {
      // ここで本来は時間のかかる処理を書く
      // Thread.Sleepメソッドは、指定時間だけ処理を待機するメソッド。
      Thread.Sleep( 3000 ); // 3 sec
    }
  }
}

 
 

16.3.2 "戻り値ありの" 非同期メソッドを定義する

Form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MultiTask2
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
      button1.Text = "async, await を使った非同期通信をする";
    }

    // button1_Click メソッドに async キーワードを付与することで非同期メソッドとなる
    // 本関数内で await を使いたいので, 本関数には async キーワードを付与する
    private async void button1_Click( object sender, EventArgs e )
    {
      label1.Text = "";

      // 戻り値を受け取る
      var elapsed = await DoSomething(); // await メソッドを使うことで処理の終了を待つ。

      // 直前の await を付けた処理が終了するまでここには到達しない
      label1.Text = $"現在の状態: 終了 {elapsed}ミリ秒]";
    }

    // 非同期メソッド
    // メソッドを非同期にするには以下2つの条件を満たす必要がある
    // - async キーワードを付与する
    // - 戻り値を受け取るので Task<型> とする
    private async Task<long> DoSomething()  
    {
      var sw = Stopwatch.StartNew();
      // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
      // await を使わずに単に DoLongTimeWork() の呼び出してしまうと
      // 非同期通信にはならない
      await Task.Run( () =>
      {
        DoLongTimeWork(); // 時間の掛かる処理
      }
      );

      sw.Stop();

      return sw.ElapsedMilliseconds;
    }

    private void DoLongTimeWork()
    {
      // ここで本来は時間のかかる処理を書く
      // Thread.Sleepメソッドは、指定時間だけ処理を待機するメソッド。
      Thread.Sleep( 3000 ); // 3 sec
    }
  }
}

 
 

16.4.1 HttpClient の簡単な例

使用例
起動時 ボタン押下後
f:id:dnkrnka:20181116233106p:plain:w400 f:id:dnkrnka:20181116233535p:plain:w400

 

form1.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace AHttpClient
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
    }
    // イベントハンドラを非同期にする (async を付与している)
    private async void button1_Click( object sender, EventArgs e )
    {
      // GetPageAsync を非同期で呼び出す
      var text = await GetPageAsync( @"http://www.bing.com/" );
      textBox1.Text = text;
    }

    private HttpClient _httpClient = new HttpClient();

    // string 型の戻り値を返すので Task<string> としている
    private async Task<string> GetPageAsync( string urlstr )
    {
      // HttpClient クラスの GetStringAsync メソッドを非同期で呼び出す
      var str = await _httpClient.GetStringAsync( urlstr );
      return str;
    }
  }
}

 
 

form1.Designer.cs

本コードは、Form デザインを変更すると Visual Studio により自動的に作成される。
従って手動での作成・変更はしていない。

namespace AHttpClient
{
  partial class Form1
  {
    /// <summary>
    /// 必要なデザイナー変数です。
    /// </summary>
    private System.ComponentModel.IContainer components = null;

    /// <summary>
    /// 使用中のリソースをすべてクリーンアップします。
    /// </summary>
    /// <param name="disposing">マネージド リソースを破棄する場合は true を指定し、その他の場合は false を指定します。</param>
    protected override void Dispose( bool disposing )
    {
      if ( disposing && (components != null) )
      {
        components.Dispose();
      }
      base.Dispose( disposing );
    }

    #region Windows フォーム デザイナーで生成されたコード

    /// <summary>
    /// デザイナー サポートに必要なメソッドです。このメソッドの内容を
    /// コード エディターで変更しないでください。
    /// </summary>
    private void InitializeComponent()
    {
      this.components = new System.ComponentModel.Container();
      this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components);
      this.button1 = new System.Windows.Forms.Button();
      this.textBox1 = new System.Windows.Forms.TextBox();
      this.SuspendLayout();
      // 
      // contextMenuStrip1
      // 
      this.contextMenuStrip1.Name = "contextMenuStrip1";
      this.contextMenuStrip1.Size = new System.Drawing.Size(61, 4);
      // 
      // button1
      // 
      this.button1.Location = new System.Drawing.Point(12, 12);
      this.button1.Name = "button1";
      this.button1.Size = new System.Drawing.Size(75, 23);
      this.button1.TabIndex = 0;
      this.button1.Text = "Http並列処理を実行する";
      this.button1.UseVisualStyleBackColor = true;
      this.button1.Click += new System.EventHandler(this.button1_Click);
      // 
      // textBox1
      // 
      this.textBox1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 
            | System.Windows.Forms.AnchorStyles.Left) 
            | System.Windows.Forms.AnchorStyles.Right)));
      this.textBox1.Location = new System.Drawing.Point(12, 41);
      this.textBox1.Multiline = true;
      this.textBox1.Name = "textBox1";
      this.textBox1.Size = new System.Drawing.Size(415, 210);
      this.textBox1.TabIndex = 2;
      // 
      // Form1
      // 
      this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
      this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
      this.ClientSize = new System.Drawing.Size(439, 257);
      this.Controls.Add(this.textBox1);
      this.Controls.Add(this.button1);
      this.Name = "Form1";
      this.Text = "Form1";
      this.ResumeLayout(false);
      this.PerformLayout();

    }

    #endregion
    private System.Windows.Forms.ContextMenuStrip contextMenuStrip1;
    private System.Windows.Forms.Button button1;
    private System.Windows.Forms.TextBox textBox1;
  }
}

 
 

Program.cs

本コードは、Form デザインを変更すると Visual Studio により自動的に作成される。
従って手動での作成・変更はしていない。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace AHttpClient
{
  static class Program
  {
    /// <summary>
    /// アプリケーションのメイン エントリ ポイントです。
    /// </summary>
    [STAThread]
    static void Main()
    {
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault( false );
      Application.Run( new Form1() );
    }
  }
}

 
 

16.4.2 HttpClient の応用

  • HttpClient を使った Wikipedia のデータにアクセスするプログラム
  • WPF を使ったプログラム

 

使用例
起動時 実行後
f:id:dnkrnka:20181117002929p:plain:w500 f:id:dnkrnka:20181117003009p:plain:w500

 

プロジェクトの作成手順

f:id:dnkrnka:20181116235017p:plain:w700
 

プロジェクト

f:id:dnkrnka:20181117002654p:plain
 

MainWindows.xaml
<Window x:Class="AHttpClient2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:AHttpClient2"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
  <Grid>
    <Button x:Name="button" Content="HttpClient 応用例" HorizontalAlignment="Left" Margin="41,48,0,0" VerticalAlignment="Top" Width="146" Click="button_Click"/>
    <TextBox x:Name="textBlock" HorizontalAlignment="Left" Height="294" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="722" Margin="41,93,0,0"/>

  </Grid>
</Window>

 

MainWindows.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xml.Linq;

namespace AHttpClient2
{
  /// <summary>
  /// MainWindow.xaml の相互作用ロジック
  /// </summary>
  public partial class MainWindow : Window
  {
    private HttpClient _httpClient = new HttpClient();

    public MainWindow()
    {
      InitializeComponent();
    }

    // await を使うので, async を付けておくこと
    private async void button_Click( object sender, RoutedEventArgs e )
    {
      textBlock.Text = "";
      var text = await GetFromWikipediaAsync( "クリーンルーム設計" );
      textBlock.Text = text;
    }

    private async Task<string> GetFromWikipediaAsync( string keyword )
    {
      // UriBuilder と FormUrlEncodedContent を使ってパラメータ付き URL を組み立てる
      var builder = new UriBuilder( "https://ja.wikipedia.org/w/api.php" );
      var content = new FormUrlEncodedContent( new Dictionary<string, string>()
      {
        ["action"] = "query",
        ["prop"] = "revisions",
        ["rvprop"] = "content",
        ["format"] = "xml",
        ["titles"] = keyword,
      } );
      builder.Query = await content.ReadAsStringAsync();

      // HttpClientを使い、wikipediaのデータを取得する。
      var str = await _httpClient.GetStringAsync( builder.Uri );

      // 取得したXML文字列から、LINQ to XMLを使い必要な情報を取り出す。
      var xmldoc = XDocument.Parse( str );
      var rev = xmldoc.Root.Descendants( "rev" ).FirstOrDefault();
      return WebUtility.HtmlDecode( rev?.Value );
    }
  }
}