1.概要
C#5.0から追加になった非同期処理【async/await】について
使いにくい【TcpClient】クラスをあえて使って検証してみます。
様々な機能を持ったアプリケーションでは非同期処理はマストかと思います。
応答待ちでデッドロックなんて目も当てられません・・・
2.基本的な使い方
まずは【async/await】の基本的な動作について確認します。
【Form】に【Button】を貼り付けて
【Button】をクリックすると【HeavyProcess】という関数が非同期で実行されます。
bool flg = false; private async void button1_Click(object sender, EventArgs e) { if (flg == true) { Console.WriteLine("Processing"); return; } flg = true; Console.WriteLine("Start"); //Task<string> task = Task.Run<string>(new Func<string>(HeavyProcess)); ←引数なしの場合 Task<string> task = Task.Run(() => HeavyProcess(5000)); string result = await task; // まつけどスレッドは抜ける // HeavyProcessの処理が完了したら以下から処理が再開する if (result == "OK") // 正常 { Console.WriteLine("End"); flg = false; } } private string HeavyProcess(int time) { System.Threading.Thread.Sleep(time); return "OK"; }
動作結果としては以下のログが出力されます。
Start
Processing ← 処理中にボタンをクリックした
Processing
Processing
Processing
Processing
Processing
End
重い処理を行っても、【Form】はデットロックされず
【Button】の操作が可能になっています。
そして処理が完了したという結果もしっかり取得出来ています。
3.TcpClientクラスを非同期にする
単純なTCP通信を使って検証してみます。
【ClientAsync】というクラスを作って、この中でTCP/IP通信を行います。
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; namespace TcpClientTest { public partial class Form1 : Form { ClientAsync clientAsync; public Form1() { InitializeComponent(); } private async void button1_Click(object sender, EventArgs e) { clientAsync = new ClientAsync(); Task<string> task = Task.Run(() => clientAsync.Connect("送信データ", "127.0.0.1", 60000)); string result = await task; // まつけどスレッドは抜ける if (result == "ERR_1") { } else if (result == "ERR_2") { } else if (result == "TIMEOUT") { } else // 正常 { } } } }
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; namespace TcpClientTest { class ClientAsync { private TcpClient client; public ClientAsync() { client = new TcpClient(); client.SendTimeout = 3000; client.ReceiveTimeout = 3000; } public string Connect(string sendMsg, string IPaddress, int port) { string resMsg=""; try { // サーバーへの接続部分 // 接続タイムアウトは下記のように自作する // これを入れないと接続タイムアウトまで約20秒程度かかってしまう int timeout = 3000; Task task = client.ConnectAsync(IPaddress, port); if (!task.Wait(timeout)) { client.Close(); throw new SocketException(10060); } // ソケット上でデータを送受信するためのメソッドを準備 NetworkStream ns = client.GetStream(); ns.ReadTimeout = 3000; ns.WriteTimeout = 3000; // データの送信 var enc = Encoding.UTF8; byte[] sendBytes = enc.GetBytes(sendMsg); ns.Write(sendBytes, 0, sendBytes.Length); // データの受信 System.IO.MemoryStream ms = new System.IO.MemoryStream(); byte[] resBytes = new byte[256]; int resSize = 0; do { resSize = ns.Read(resBytes, 0, resBytes.Length); if (resSize == 0) { Console.WriteLine("切断された"); break; } ms.Write(resBytes, 0, resSize); } while (ns.DataAvailable || resBytes[resSize - 1] != '\n'); resMsg = enc.GetString(ms.GetBuffer(), 0, (int)ms.Length); ms.Close(); resMsg = resMsg.TrimEnd('\n'); // 閉じる ns.Close(); client.Close(); } catch (SocketException ex) { // 接続タイムアウト return "TIMEOUT"; } catch (AggregateException ex) { if (ex.InnerException is SocketException) { // 接続失敗拒否 return "ERR_1"; } } catch (Exception ex) { // その他のエラー return "ERR_2"; } return resMsg; } } }
4.まとめ
【TcpClient】クラスは接続のタイムアウトを設定することが出来ません。
ですので自分である程度カスタムする必要があります。
クライアントからサーバーへ接続できない場合
タイムアウトまで約20秒程待つなんてありえないですね。。。
とりあえず【async/await】は使い方さえわかれば
使う場面が増えるのではないでしょうか?