WebClient を利用し 非同期でhttpコンテンツを取得する - C#

WebClientではhttpでのファイルのダウンロードやwebコンテンツの取得ができます。
(Webコンテンツの取得はこちらの記事を、ファイルのダウンロードはこちらの記事を参照してください。)

上記の記事で紹介した方法はダウンロードなどの通信をメインスレッドで実行するため通信中はUIの操作ができなくなります。こうした問題を回避するため、WebClientでは非同期でのダウンロードやコンテンツ取得ができます。この記事では、非同期でのコンテンツ取得のコードを紹介します。

UI

下図のUIを作成します。

コード

下記のコードを記述します。(button2(Get with Paramsボタン)のclickイベントはここでは実装しません。)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net;

namespace HttpConnectionDemo
{
  public partial class FormHttpWebClientAsync : Form
  {
    public class dlparam
    {
      public string parameter1;
      public string parameter2;
    }

    public FormHttpWebClientAsync()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      WebClient wc = new WebClient();
      wc.Encoding = Encoding.UTF8;
      try { 
        //古い書式
        //wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(CompleteDownloadProc1);
        wc.DownloadStringCompleted += CompleteDownloadProc1;
        wc.DownloadStringAsync(new Uri("http://www.ipentec.com"));
      }
      catch (WebException exc) {
        textBox1.Text += exc.Message;
      }
    }

    public void CompleteDownloadProc1(Object sender, DownloadStringCompletedEventArgs e)
    {
      textBox1.Text = e.Result;
    }
  }
}

解説

WebClient wc = new WebClient();
wc.Encoding = Encoding.UTF8;
上記コードにより、WebClientのインスタンスの作成をします。作成したインスタンスのEncodingパラメーターに取得するコンテンツのエンコードタイプを指定します。(http://www.ipentec.comのコンテンツはUTF-8でエンコーディングされているため、今回は"Encoding.UTF8"を指定しています。)

try { 
  wc.DownloadStringCompleted += CompleteDownloadProc1;
  wc.DownloadStringAsync(new Uri("http://www.ipentec.com"));
}
catch (WebException exc) {
  textBox1.Text += exc.Message;
}
上記コードによりコンテンツの取得を実行します。

  //wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(CompleteDownloadProc1);
  wc.DownloadStringCompleted += CompleteDownloadProc1;
作成したインスタンスのDownloadStringCompletedイベントにイベントハンドラを追加します。イベントハンドラは後述する CompleteDownloadProc1()メソッドです。以前のC#のバージョンではnewによりイベントハンドラのインスタンスを作成する必要がありましたが、現在のバージョンでは代入のみで動作します。イベントハンドラ(デリゲート)の詳細はこちらの記事を参照してください。

  wc.DownloadStringAsync(new Uri("http://www.ipentec.com"));
DownloadStringAsync()メソッドの呼び出しによりコンテンツの取得を開始します。引数には取得するコンテンツのURLを示す、Uriオブジェクトを与えます。

public void CompleteDownloadProc1(Object sender, DownloadStringCompletedEventArgs e)
{
  textBox1.Text = e.Result;
}
上記が府コンテンツ取得の完了関数になります。コンテンツの取得は非同期で実行され、コンテンツ取得が完了するとこのメソッドが呼び出されます。

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。


ウィンドウ左の[Get]ボタンをクリックします。Webのコンテンツを取得し下部のテキストボックスに表示されます。


エンコーディングを指定しているため文字化けもなく表示されます。

補足:パラメーターを完了関数に渡す方法

ダウンロード開始時にプログラム側から完了関数にパラメーターを渡したいことがあります。パラメーターを渡す場合のコードを紹介します。

コード

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

namespace HttpConnectionDemo
{
  public partial class FormHttpWebClientAsync : Form
  {
    public class dlparam
    {
      public string parameter1;
      public string parameter2;
    }

    public FormHttpWebClientAsync()
    {
      InitializeComponent();
    }

    private void button2_Click(object sender, EventArgs e)
    {
      WebClient wc1 = new WebClient();
      WebClient wc2 = new WebClient();
      wc1.Encoding = Encoding.UTF8;
      wc2.Encoding = Encoding.UTF8;
      try {

        //wc.DownloadStringCompleted += new DownloadStringCompletedEventHandler(CompleteDownloadProc2);
        wc1.DownloadStringCompleted +=CompleteDownloadProc2;
        wc2.DownloadStringCompleted += CompleteDownloadProc2;

        dlparam dlp1 = new dlparam();
        dlp1.parameter1 = "ipentec.com";
        dlp1.parameter2 = "てすとだよ。";
        wc1.DownloadStringAsync(new Uri("http://www.ipentec.com"), dlp1);

        dlparam dlp2 = new dlparam();
        dlp2.parameter1 = "ipentec.com - doc";
        dlp2.parameter2 = "てすとかも。";
        wc2.DownloadStringAsync(new Uri("http://www.ipentec.com/document"), dlp2);
      }
      catch (WebException exc) {
        textBox1.Text += exc.Message;
      }
    }

    public void CompleteDownloadProc2(Object sender, DownloadStringCompletedEventArgs e)
    {
      dlparam dlp = (dlparam)e.UserState;
      textBox1.Text += dlp.parameter1 + "\r\n";
      textBox1.Text += dlp.parameter2 + "\r\n";
      textBox1.Text += "-----\r\n"; 
      textBox1.Text += e.Result;
    }
  }
}

解説

コードの大半は先の実装と同じです。

wc1.DownloadStringAsync(new Uri("http://www.ipentec.com"), dlp1);
上記のDownloadStringAsync()メソッドの第二引数にパラメータとなるオブジェクトを渡します。今回の例ではクラス内クラスの"dlparam"クラスのインスタンスを与えます。

public void CompleteDownloadProc2(Object sender, DownloadStringCompletedEventArgs e)
{
  dlparam dlp = (dlparam)e.UserState;
  textBox1.Text += dlp.parameter1 + "\r\n";
  textBox1.Text += dlp.parameter2 + "\r\n";
  textBox1.Text += "-----\r\n"; 
  textBox1.Text += e.Result;
}
ダウンロード完了関数で取得したコンテンツと受け取ったパラメータをテキストボックスに表示します。パラメーターのオブジェクトはメソッドの引数DownloadStringCompletedEventArgs のUserStateプロパティに渡されます。

実行結果

プロジェクトを実行し[Get with Parm]ボタンをクリックします。Webのコンテンツを取得しテキストボックスに表示します。


今回のプログラムでは2つのWebClientオブジェクトを用意し、2つのコンテンツを取得しています。それぞれのコンテンツ取得時に渡したパラメータがテキスト朴素に表示されています。

注意

上記の手法を用いずに、クラスのメンバ変数に値を入れておけばよいと考え実装しても動作すると考えがちです。ダウンロードのスレッドが1つの場合はクラスのメンバ変数でパラメータを共有する実装で問題なく動作しますが、ダウンロードが複数同時に走る場合は、それぞれのダウンロードスレッドに対しパラメータ変数を用意する必要が出てきます。また、完了関数を同じメソッドに設定していた場合、呼び出された完了関数がどのダウンロードメソッドからの呼び出しかを判別できません。これらの問題を回避するため、呼び出し時にパラメーターを与える実装が適切です。
著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
掲載日: 2014-03-12
iPentec all rights reserverd.