C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[8]: 非同期TCP接続


(過去ログ 17 を表示中)

[トピック内 9 記事 (1 - 9 表示)]  << 0 >>

■6782 / inTopicNo.1)  非同期TCP接続
  
□投稿者/ JJ (1回)-(2007/08/23(Thu) 19:02:01)

分類:[C#] 

C# 2005 Express(Framework2.0)で開発しています。
宜しくお願いします。

Tcpで通信するアプリを開発中です。
System.Net.TcpClientの同期式の接続だと
Serverの電源OFFの場合に
20秒程度待たされます。

そこで、非同期接続を使いタイムアウトを
加味して接続するClassを作成しました。

WaitOneでタイムアウト時に何もしないと
その後ConnectCallBackが走るため
タイムアウトでCloseをやってみました。

特に問題はないと思ってるのですが、
ご意見ありましたら、お願いします。


    public class TcpConnect {
        /// <summary>
        /// 接続待ちEvent
        /// </summary>
        private ManualResetEvent _evConnect = new ManualResetEvent(false);

        /// <summary>
        /// 非同期接続処理のException
        /// </summary>
        private Exception _connEx;

        /// <summary>
        /// 指定先に接続したTcpClient クラスを返す(タイムアウト機能付)
        /// </summary>
        /// <param name="host">DNSマシン名orIPアドレス</param>
        /// <param name="portNo">ポート番号</param>
        /// <param name="timeoutMSec">タイムアウト[mSec]</param>
        /// <returns></returns>
        public TcpClient Connect(string host, int portNo, int timeoutMSec) {

            lock (this) {
                _evConnect.Reset();
                _connEx = null;

                TcpClient tc = new TcpClient();
                tc.BeginConnect(host, portNo, new AsyncCallback(ConnectCallBack), tc);

                if (_evConnect.WaitOne(timeoutMSec, false)) {
                    if (_connEx == null) {
                        return tc;
                    } else {
                        throw _connEx;
                    }
                } else {
                    // ConnectCallBackを発生させる
                    // (TcpClient.Clientがnullで発生する)
                    tc.Close();

                    // タイムアウト通知
                    throw new TimeoutException("Tcp Connect Timeout");
                }
            }
        }

        /// <summary>
        /// TcpClient.BeginConnect の CallBack
        /// </summary>
        /// <param name="ar"></param>
        private void ConnectCallBack(IAsyncResult ar) {

            try {
                TcpClient client = ar.AsyncState as TcpClient;
                if (client.Client != null) {
                    client.EndConnect(ar);
                } else {
                    // BeginConnect中にTcpClient.Close
                    // を呼んだことにより発生
                    // 無視する
                }
            } catch (Exception ex) {
                _connEx = ex;
            } finally {
                _evConnect.Set();
            }
        }
    }

引用返信 編集キー/
■6783 / inTopicNo.2)  Re[1]: 非同期TCP接続
□投稿者/ れい (50回)-(2007/08/23(Thu) 19:28:18)
2007/08/23(Thu) 19:32:05 編集(投稿者)

No6782 (JJ さん) に返信
> 特に問題はないと思ってるのですが、
> ご意見ありましたら、お願いします。

うーん。
問題も無駄もありますね。

AsyncCallbackはAsyncなので他スレッドの場合もあるというのと、
戻り値のIAsyncResultをもうちょっとうまく活用できる点、
その2点を考慮してみたらどうですか?

もうちょっと楽にできますよ。
引用返信 編集キー/
■6784 / inTopicNo.3)  Re[2]: 非同期TCP接続
□投稿者/ JJ (2回)-(2007/08/23(Thu) 19:43:08)
れい様

返信ありがとうございます。

今回のアプリではマルチスレッドで
同時にということはないので
問題ないかと考えております。

調査不足でBeginConnectの戻り値に
IAsyncResultがあるということを今しりました。

ConnectCallBack内で設定してやろうとしたのですが
全てReadOnlyプロパティではないでしょうか?

具体例をあげていただけると大変助かります。

お手数ですがお願いいたします。
引用返信 編集キー/
■6786 / inTopicNo.4)  Re[3]: 非同期TCP接続
□投稿者/ れい (51回)-(2007/08/23(Thu) 20:26:01)
2007/08/23(Thu) 20:26:20 編集(投稿者)
No6784 (JJ さん) に返信
> 今回のアプリではマルチスレッドで
> 同時にということはないので
> 問題ないかと考えております。

それは問題認識が甘いです。
BeginConnectした時点でマルチスレッドです。
Callbackはどんなスレッドで呼ばれるのか、保証がありません。
呼び出し元の場合もあれば、Pooledなスレッドの場合、そうでない他のスレッドの場合もあります。
ですので、Callbackで変数を使うなら、同期機構をいれないとダメです。

> 具体例をあげていただけると大変助かります。

何をしたいのかも明確にわかるので、
私の持ってるソースを見せれば役に立つだろうと思うのですが。
今日はいろいろめんどくさいので、手書き+検証無し+うろ覚えで。

まず、ウェイトだけならCallbackは要りません。
IAsyncResult.AsyncWaitHandleで待てばいいだけです。
AsyncWaitHandleは作成にコストがかかる場合もあるので、
IsCompleteかCompletedSynchronouslyで確認しておくといいです。
BeginXXXを途中で無理やり閉じると例外を吐くものもあるのでそれも処理します。

まとめると。
BeginXXXの一般的タイムアウト指定方法は下記の用になります。

(手書き+検証無し+うろ覚えにつき注意)
IAsyncResult iar = tc.BeginConnect(host, portNo, null, null);
if ( !iar.CompletedSynchronously ) { か if ( !iar.IsCompleted ) {
  if ( !iar.WaitOne(timeoutMSec, false) ) {
    try {
      //ここにCleanupを。
      tc.close();
    } catch {
    }
    Throw New TimeoutException();
  }
}
tc.EndConnect(iar);

IAsyncResultに関しては「非同期プログラミングのデザインパターン」で検索。
IAsyncResultをきちんと理解しておくと、かなり使えます。


引用返信 編集キー/
■6801 / inTopicNo.5)  Re[4]: 非同期TCP接続
□投稿者/ JJ (3回)-(2007/08/24(Fri) 00:19:34)
No6786 (れい さん) に返信

認識の甘さを痛感しています。
ご指摘ありがとうございます。

ここまでシンプルに出来るとは。。。
本当に問題も無駄もありますね。


ひとつ疑問があります。

> if ( !iar.CompletedSynchronously ) { か if ( !iar.IsCompleted ) {

この部分の必要性がわかりません。
無条件にやってもWaitOneはシグナル状態で
すぐに抜けるのではないでしょうか?

度々申し訳ありませんが、ご教授いただけると幸いです。

引用返信 編集キー/
■6803 / inTopicNo.6)  Re[5]: 非同期TCP接続
□投稿者/ れい (54回)-(2007/08/24(Fri) 00:55:17)
No6801 (JJ さん) に返信
> ■No6786 (れい さん) に返信
>>if ( !iar.CompletedSynchronously ) { か if ( !iar.IsCompleted ) {
> この部分の必要性がわかりません。

よく読んでください。

>AsyncWaitHandleは作成にコストがかかる場合もあるので、
>IsCompleteかCompletedSynchronouslyで確認しておくといいです。

こう書きましたが、これでわからないでしょうか?

IsCompleteとCompleteSynchronouslyのコストは
IAsyncResultに依りますが、
普通に考えればCompleteSynchronouslyのコストが一番低いはずです。
IsCompleteのコストは高い場合が考えられます。
両方呼んでもいいですが、たくさん呼ぶならWiseなANDで。

if ( !CompleteSynchronously && !IsComplete && !iar.WaitOne(timeoutMSec, false) ) {

どちらにしろ、AsyncWaitHandleのコストはかなり高いので、
なるべく呼びたくないです。

どのくらい高いかは、IAsyncResultを自分で実装して見ればわかります。
引用返信 編集キー/
■6804 / inTopicNo.7)  Re[6]: 非同期TCP接続
□投稿者/ なちゃ (55回)-(2007/08/24(Fri) 01:16:25)
>普通に考えればCompleteSynchronouslyのコストが一番低いはずです。
>IsCompleteのコストは高い場合が考えられます。
>両方呼んでもいいですが、たくさん呼ぶならWiseなANDで。

CompleteSynchronouslyはチェックしとくにこしたことはないとは思いますが、
どっちにしてもIsCompleteの方は必須じゃないでしょうか?

※ああ、必須ってのは、CompleteSynchronously は IsComplete の代わりにはならないという意味です。

引用返信 編集キー/
■6806 / inTopicNo.8)  Re[7]: 非同期TCP接続
□投稿者/ れい (56回)-(2007/08/24(Fri) 01:29:06)
2007/08/24(Fri) 05:26:29 編集(投稿者)

No6804 (なちゃ さん) に返信
> CompleteSynchronouslyはチェックしとくにこしたことはないとは思いますが、
> どっちにしてもIsCompleteの方は必須じゃないでしょうか?

パフォーマンスだけの問題ですから、どうでもいいといえばいいですし、
IAsyncResultの実装とかOSとかに依りますので確かなことはなんとも言えないのですが。

ファイルやSocketなど、いろいろやってみましたが、
IsCompleteをチェックしないほうがいい場合もありました。レアですが。
チェック無しでAsyncWaitHandleを呼ぶのはやはりダメでした。
CompleteSynchronouslyは呼んでも呼ばなくてもあまり変わりませんでした。
そもそもtrueを返す実装は殆どないようです。

ここから.Netでの実装とは別の話になります。
処理にかかる時間のばらつきが大きいが、
時間の予想が付く処理というのはよくあります。
たとえばファイルに対するロックがあります。
ロック要求は普通時間がかかりますが、
同じファイルに対する2回目のロックは非常に高速にできます。
データの読み込み操作も、何かキャッシュが介在するなら
2回目は高速に処理できます。

そういった場合、2回目の呼び出しは非同期呼び出しでも
同期的に処理するのが有利です。

こういった処理に対する非同期呼び出しを待つ場合、
もしIsCompleteが重いのであれば
CompleteSynchronouslyだけをチェックするほうが有利になります。
処理が戻ったときにはCompleteSynchronouslyは確定してるので
呼び出しコストはほぼ0ですから。

IAsyncResultのような同期の方法は.Net以前からあって、
応用が広いのでいろいろな処理系によるいろいろな実装があります。
実装に応じてCompleteSynchronouslyとIsCompleteのどちらをチェックをするか、
両方チェックするか考えて使うのがいい方法です。

ですので、

>※ああ、必須ってのは、CompleteSynchronously は IsComplete の代わりにはならないという意味です。

処理系や実装によっては代わりになる場合もよくあります。

CompleteSynchronouslyだけをチェックしたほうがいい場合が
.Netであるかどうかは私は知りません。
IAsyncResultのデザインでは、合ってもおかしくはないと思います。
たぶんほとんどないので、私は普通はIsCompleteだけチェックしてます。
引用返信 編集キー/
■6821 / inTopicNo.9)  Re[8]: 非同期TCP接続
□投稿者/ JJ (4回)-(2007/08/24(Fri) 12:14:29)

No6803 (れい さん) に返信

申し訳ありません。

>AsyncWaitHandleは作成にコストがかかる場合もあるので、
>IsCompleteかCompletedSynchronouslyで確認しておくといいです。

この部分を読み落としていました。

IsCompleteで確認後、必要に応じてWaitOneで正常に動作しました。

タイムアウト判定時に、BegginConnectをCloseで強制終了する流れですが
特にエラーにはなりませんでした。

タイミングに左右される可能性もあるため、ハンドリングは残してあります。

これで無事に解決しました。
本当にありがとうございました。

解決済み
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -