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

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

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

Re[13]: Socket.BeginReceiveのコールバック障害


(過去ログ 79 を表示中)

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

■46611 / inTopicNo.1)  Socket.BeginReceiveのコールバック障害
  
□投稿者/ maruma (4回)-(2010/02/06(Sat) 19:28:15)

分類:[.NET 全般] 

OS:Windows Vista Business SP1
言語:VB.NET(2008)

いつもお世話になっております。
今回はスレッドプールについて質問があります。

現在、UNIXサーバとTCPプロトコルを利用したソケット通信を行っているプロセスがあります。
(IPv4です。)
ソケット通信に使用しているクラスは、System.Net.Sockes.Socketクラスです。
非同期受信を実現するため、以下のコールバックを行っています。

Private Sub ReceiveCallBack(ByVal ar As System.IAsyncResult)

'処理省略@

ReDim receiveBuffer(Me._maxReceiveLength)

Me._socket.BeginReceive(receiveBuffer _
, 0 _
, receiveBuffer.Length _
, Sockets.SocketFlags.None _
, New AsyncCallback(AddressOf ReceiveCallBack) _
, receiveBuffer)

'処理省略A

End Sub

問題はこのBeginReceive処理でスレッドが戻ってこないことです。
非同期処理のため、BeginReceiveが呼ばれれば直ぐに次の処理が実行されると思います。
(コールバックが直ぐに発生するという意味でなく、処理省略Aが実行されるということ)

初めはSyncLockステートメントによるデッドロックが発生しているのかと疑いました。
そこで、ReceiveCallBackのソースコードの行間にログを記述し吐かせたところ、
間違いなくBeginReceive行で処理が永久に戻らなくなっていました。

この現象は100%起こる訳ではなく、稀に発生するようです。
(発生契機は未だ不明…)
また、この状況をリソースモニタで確認しようとすると、
CPUはもとより、ディスクもメモリも何もモニタしてくれません。
(Windous Vistaのリソースモニタは適時PC内のプロセス状況を拾って来ると思いますが、20分経過しても何も表示されません)

上記の現象が発生すると、当然SyncLockステートメントのEndを実行しないため、
以降はデッドロックが発生します。
しかし、タスクマネージャでは「応答待ち」状態にはならず、12時間放置しても実行中のままです。

タスクマネージャ確認する限り常にCPUが20〜30%程度示し、何かの処理が実行されているようですが、
プロセスごとのCPU使用率はSystem Idle Processが90%前後以外、0%を示しています。

スレッドプールは1プロセスにつき250×CPU数なので
理論上は250×4=1000が可能であると思っています。
それを超える程激しい受信処理を行ってはいないと思っています。
BeginReceiveが1秒間に呼ばれる回数は3〜5程度です。
(参考までに、1度の受信Byteは多くて5000Byteで、通常100Byte前後です。)

この現象が何を契機に発生するか、
推測でも良いので、意見を聞かせて頂けないでしょうか?

また、PCのスペックは以下の通りです。
CPU:Intel Core2 Quad CPU E9550 2.83GHz
メモリ:2.00GB(RAM)
種類:32ビット

この現象を改善する方法として以下のことは試しました。
PC再起動
アプリケーション再インストール
OSクリーンインストール(開発時期にレジストリを弄っていたため)

また、この実行環境には以下のソフトウェアがインストールされています。
・CrystalReport For .NET2008ランタイム
・Oracle 10g Client
・Oracle 11g Client
・ODP.NET 11.0.7.0

以上、よろしくお願い致します。
引用返信 編集キー/
■46612 / inTopicNo.2)  Re[1]: Socket.BeginReceiveのコールバック障害
□投稿者/ やじゅ (1512回)-(2010/02/06(Sat) 21:51:55)
やじゅ さんの Web サイト
2010/02/07(Sun) 01:40:24 編集(投稿者)
2010/02/06(Sat) 21:52:54 編集(投稿者)

No46611 (maruma さん) に返信
> 問題はこのBeginReceive処理でスレッドが戻ってこないことです。

非同期でもタイムアウトできるようにしてみるとか
http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=29852&forum=7
http://web.archive.org/web/20060510225908/http://www.microsoft.com/japan/msdn/enterprise/pag/diforwc-ch06.asp

もちろん原因が分かるのが一番いいんですけどね。

非同期ソケット通信での不具合
http://social.msdn.microsoft.com/Forums/ja-JP/csharpgeneralja/thread/a079d36d-e37f-4f2b-a2c2-ce222d74c6ca
引用返信 編集キー/
■46618 / inTopicNo.3)  Re[1]: Socket.BeginReceiveのコールバック障害
□投稿者/ れい (865回)-(2010/02/06(Sat) 23:25:23)
No46611 (maruma さん) に返信
> 問題はこのBeginReceive処理でスレッドが戻ってこないことです。
> 非同期処理のため、BeginReceiveが呼ばれれば直ぐに次の処理が実行されると思います。
> (コールバックが直ぐに発生するという意味でなく、処理省略Aが実行されるということ)

処理1、2が省略されているのでよくわかりません。
SyncLockを使っているようなので、せめてその周りくらいは書いたほうがよいと思います。

私の経験では正しく使ったうえで、System.Net.Sockets.Socket.BeginReceiveが停止したことはありません。
ミニマムコードを作るといいと思います。

現状で思いつく事例としては、
SocketのReceive/BeginReceive/EndReceiveを複数のスレッドで呼び出してしまう間違いがあります。

WinSockは受信、送信を別のスレッドでコールすることは可能ですが、
受信を複数スレッドで、もしくは送信を複数スレッドでコールすると壊れる場合があります。
System.Net.Sockets.Socketも同様です。

ご確認を。
引用返信 編集キー/
■46625 / inTopicNo.4)  Re[2]: Socket.BeginReceiveのコールバック障害
□投稿者/ maruma (5回)-(2010/02/07(Sun) 14:25:49)

No46612 (やじゅ さん) に返信

やじゅさん、返信ありがとうございます。
(前回もお世話になりました!
前回の件は無事に解決方法が見つかりましたので、後で報告します。)

こちらの返信がおくれてしまい申し訳ございません。
ソースコードの解析を進めていたところでした。

> 非同期ソケット通信での不具合
> http://social.msdn.microsoft.com/Forums/ja-JP/csharpgeneralja/thread/a079d36d-e37f-4f2b-a2c2-ce222d74c6ca

この内容はかなり近いです。
結局解決していないようですね…。むむ…
れいさんの指摘にもありますが、非同期実行の際

No46618 (れい さん) に返信

れいさん、返信ありがとうございます。

> 処理1、2が省略されているのでよくわかりません。
> SyncLockを使っているようなので、せめてその周りくらいは書いたほうがよいと思います。

配慮が足りず申し訳ございません。
ソースコードは現在解析中です。
また、全ソースコードを載せるのは若干問題がありますので
以下の指摘通り、ミニマムコードを作成して検証し、
改めてソースを開示したいと思います。

> 私の経験では正しく使ったうえで、System.Net.Sockets.Socket.BeginReceiveが停止したことはありません。
> ミニマムコードを作るといいと思います。
>
> 現状で思いつく事例としては、
> SocketのReceive/BeginReceive/EndReceiveを複数のスレッドで呼び出してしまう間違いがあります。
>
> WinSockは受信、送信を別のスレッドでコールすることは可能ですが、
> 受信を複数スレッドで、もしくは送信を複数スレッドでコールすると壊れる場合があります。
> System.Net.Sockets.Socketも同様です。
>
> ご確認を。


れいさんの指摘にあります通り、
該当プロセスはスレッドプールを利用したマルチスレッド形式です。
ソースコードを解析した結果、以下の点が判りました。

1、該当プロセスは常時3台のUNIXサーバから受信している。
2、3台とも、同じソケットクラスを使用し、別々に生成している。
3、生成時とコネクト時はデリゲートを使用した非同期スレッドで行っている。
4、非同期受信スレッドで別のソケットオブジェクトを使用し、送信している。

A、B、Cのソケットオブジェクトに対して
A非同期受信(Aスレッド)→B同期送信(Aスレッド)
の動作を行っています。

この状態は、デバッグ実行では何もエラーも発生せず、
(初回エラー等出力もされません)
比較的長期間稼動させると、壊れる?のでしょうか?

> 受信を複数スレッドで、もしくは送信を複数スレッドでコールすると壊れる場合があります。

つまり、
A非同期受信(Aスレッド)→B同期送信(Aスレッド)
C非同期受信(Cスレッド)→B同期送信(Cスレッド)
を交互の行うと壊れる可能性がある、ということでしょうか?
(肝心のSend部分はSyncLockされているので同時に実行はされません。)

とりあえず、早急にミニマムコードを作成し再現させ、報告したいと思います。

以上、よろしくお願い致します。

引用返信 編集キー/
■46626 / inTopicNo.5)  Re[3]: Socket.BeginReceiveのコールバック障害
□投稿者/ maruma (6回)-(2010/02/07(Sun) 14:27:48)
やじゅさん、すみません。
訂正します。

> れいさんの指摘にもありますが、非同期実行の際

れいさんの指摘にもありますが、マルチスレッド関係が怪しいと思います。

引用返信 編集キー/
■46629 / inTopicNo.6)  Re[3]: Socket.BeginReceiveのコールバック障害
□投稿者/ れい (866回)-(2010/02/07(Sun) 15:17:17)
No46625 (maruma さん) に返信
>>受信を複数スレッドで、もしくは送信を複数スレッドでコールすると壊れる場合があります。
>
> つまり、
> A非同期受信(Aスレッド)→B同期送信(Aスレッド)
> C非同期受信(Cスレッド)→B同期送信(Cスレッド)
> を交互の行うと壊れる可能性がある、ということでしょうか?
> (肝心のSend部分はSyncLockされているので同時に実行はされません。)

B送信部がクリティカルセクション内にあるならば問題ありません。

再現に12時間かかると最少コードを作るだけで大変ですね。
threadpoolがあやしいと思うなら
ThreadPool.GetAvailableThreadsを見るとか。

あとはNICのドライバをアップデートしてみるとか。

そのくらいしか思いつきません。

BeginSend/BeginReceiveを使ったアプリケーションを、
再起動もせず、パッチも充てずに連続1年くらい稼働させてますが、止まっていません。
BeginSendに問題があるとすると、特殊な使い方をしないと再現しないような問題であろうと思います。

引用返信 編集キー/
■46632 / inTopicNo.7)  Re[4]: Socket.BeginReceiveのコールバック障害
□投稿者/ maruma (7回)-(2010/02/07(Sun) 21:05:51)
No46629 (れい さん) に返信

返信ありがとうございます。
現在、連続運転試験を行っているところなのですが、
試験環境端末PC9台のうち、3台がエラーでプロセス停止となりました。
その原因解析でちょっと手が空きません。
おそらくは、同じような問題で止まっているのだと思いますが…。
(今までは開発機で発生させていました。)

今回、2台がエラーダイアログを出しました。
以下にその内容を示します。

タイトル名:○○○○.exe - アプリケーションエラー
内容:アプリケーションでハンドル出来ない例外が発生しました。
   処理ID=0x6f4(1780)、スレッドID=0x8ec(2284)
アプリケーションを終了するには[OK]をクリックしてください。
   アプリケーションをデバッグするには、[キャンセル]をクリックしてください。

※○○○○.exeは問題のプロセスです。また、2台目は 処理ID=0xfdc(4060)、スレッドID=0xf48(3912)

このエラーダイアログは良く見る.NET Frameworkのエラーダイアログと違い、
[詳細]ボタンもありません。
デバッグしようにも、試験機にはVSをインストールしないため出来ず、
処理のアドレス?も簡単には検索に引っかからず…
(処理のアドレスは内部メソッドのアドレスでしょうか?そうならば確認する手段が無い…)

非常に焦ってきました。
連続運転時間は約30時間でした。
現在はWindowsのイベントログ辺りを探っています。

「プロセスが持っている仮想メモリ領域が何かの理由で壊れた」
とすると、Framework側の話になるのでしょうか?
そもそも、全て同じ問題だと推測しているのも間違いかもしれません。

> threadpoolがあやしいと思うなら
> ThreadPool.GetAvailableThreadsを見るとか。
そうですね!
スレッド数をログに吐き続けてみます。

> あとはNICのドライバをアップデートしてみるとか。
こちらも検討させていただきます。
貴重な情報、ありがとうございます。

> BeginSend/BeginReceiveを使ったアプリケーションを、
> 再起動もせず、パッチも充てずに連続1年くらい稼働させてますが、止まっていません。
> BeginSendに問題があるとすると、特殊な使い方をしないと再現しないような問題であろうと思います。
BeginSendは使用していません。送信は同期送信を行っています。
何か変わる可能性もあるので、BeginSendも後日試したいと思います。

取り急ぎ、エラーダイアログの真相究明とスレッド数出力を行います。

以上、よろしくお願い致します。


引用返信 編集キー/
■46648 / inTopicNo.8)  Re[5]: Socket.BeginReceiveのコールバック障害
□投稿者/ やじゅ (1515回)-(2010/02/08(Mon) 12:13:11)
No46632 (maruma さん) に返信
> デバッグしようにも、試験機にはVSをインストールしないため出来ず、

Debug Diagnostic Tool を使ってみるとか
下記サイトはIISの例ですが、通常のプロセスでも使えます。
http://keicode.com/iis/iis503.php

引用返信 編集キー/
■46651 / inTopicNo.9)  Re[6]: Socket.BeginReceiveのコールバック障害
□投稿者/ なちゃ (387回)-(2010/02/08(Mon) 13:00:55)
一応念のためですが、BeginReceiveに対応するEndReceiveは呼んでますよね?

引用返信 編集キー/
■46667 / inTopicNo.10)  Re[7]: Socket.BeginReceiveのコールバック障害
□投稿者/ maruma (8回)-(2010/02/08(Mon) 17:57:42)
2010/02/08(Mon) 18:00:46 編集(投稿者)
No46648 (やじゅ さん) に返信

やじゅさん、返信ありがとうございます。

> Debug Diagnostic Tool を使ってみるとか
> 下記サイトはIISの例ですが、通常のプロセスでも使えます。
> http://keicode.com/iis/iis503.php

コレは凄いですね!(無知ですみません…)
試験環境にはインストールできませんが、開発機で監視させようと思います。
いつもありがとうございます。


■No46651 (なちゃ さん) に返信

なちゃさん、返信ありがとうございます。

> 一応念のためですが、BeginReceiveに対応するEndReceiveは呼んでますよね?

受信部分のもう少し詳細なコードを以下に示します。

  '''UIクラス側で呼ばれるメソッド
  '''ソケットオブジェクト1つにつき、一度しか呼ばれません
  Public Sub ReceiveStart() 
        
        ※処理省略
        ※ソケット接続確認、非同期受信の開始確認をしています

        ' '受信バッファを初期化する
        Dim receiveBuffer(Me._maxReceiveLength) As Byte
        Me._receivedBytes = New System.IO.MemoryStream

        ' '非同期受信を開始
        Try
            Me._socket.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, _
                                    Sockets.SocketFlags.None, _
                                    New AsyncCallback(AddressOf ReceiveCallBack), receiveBuffer)
        Catch
            'ソケットが閉じたとき
            Me.Close()
            Return
        End Try
  End Sub


  '''コールバックメソッド
  '''AsyncCallbackデリゲートによって呼び出されるメソッドです
  Public Overridable Sub ReceiveCallBack(ByVal ar As System.IAsyncResult) 
        ' '受信データ長
        Dim rlen As Integer = -1
        Dim err As System.Net.Sockets.SocketError = Nothing
        Try
            SyncLock Me.syncSocket
                ' '読み込んだ長さを取得
                rlen = Me._socket.EndReceive(ar, err)
            End SyncLock
        Catch
            'Socketを閉じた時
        End Try

 		' '切断されたか調べる
        If rlen <= 0 Then
            ' ''切断している場合、ソケットを閉じる
            Me.Close()
            Return
        End If

        ' '受信したデータを蓄積する
        Me._receivedBytes.Write(CType(ar.AsyncState, Byte()), 0, rlen)

		※処理省略
		※イベントライズさせ、UI画面に通知します。
		
	  	' '受信バッファサイズ再設定
        ReDim receiveBuffer(Me._maxReceiveLength)

        SyncLock Me.syncSocket
            ' 'ソケット切断チェック
            If _socket Is Nothing Then
                ' ''切断している場合は非同期受信終了
                Return
            End If
            Try
                ' '非同期受信再開
                Me._socket.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, _
                    Sockets.SocketFlags.None, _
                    New AsyncCallback(AddressOf ReceiveCallBack), receiveBuffer) ←●稀に無反応に…
            Catch
                'ソケットが閉じたとき
                Me.Close()
                Return
            End Try
        End SyncLock
    End Sub


なお、このソースコードは
http://codezine.jp/article/detail/22?p=1
を参考に組んである事を記しておきます。
(どぼん!さん、いつもお世話になっております。)

そこで、いつも止まるのが●印の場所です。
現象は先にも述べた通り、スレッドプールにスレッドが新しく生成されない状態のまま
永遠に止まります。
また、System.Windows.Forms.Timerもスレッドプールを使用しているためか、
この後、イベントを通知してもらえません。
メインスレッド自体は動作するようです。

そういえばインストールしてある環境を申告し忘れていました。
・.NET Framework 3.5 (SP1ではありません)
・MS Office 2007
がインストールされています。
これは今回の事象に影響するのでしょうか?

蛇足ですが…
リソースモニタとにらめっこしていたら気になる数値がありました。
メモリの「ハードフォールト(ページフォールト)/分」が単体で常時900程度です。
これは問題ないのでしょうか?
(明らかにパフォーマンスに影響しているとは思いますが…)


追伸:れいさんへ
BeginSendに変えてみましたが、変わりなかった事を報告します。
また、ThreadPool.GetAvailableThreadsを出力するためには
実行スレッドが関係ありますでしょうか?
常時、ワーカースレッド数:500、非同期I/Oスレッド数:999
となります。

皆様、いつもありがとうございます。

以上、よろしくお願い致します。


引用返信 編集キー/
■46671 / inTopicNo.11)  Re[8]: Socket.BeginReceiveのコールバック障害
□投稿者/ れい (867回)-(2010/02/08(Mon) 20:21:01)
No46667 (maruma さん) に返信
> 追伸:れいさんへ
> BeginSendに変えてみましたが、変わりなかった事を報告します。
> また、ThreadPool.GetAvailableThreadsを出力するためには
> 実行スレッドが関係ありますでしょうか?

関係ありません。
常時一定値ならばそこに問題はありませんね。

> 受信部分のもう少し詳細なコードを以下に示します。

ちょうどいま同じようなコードでごにょごにょしています。
長期間実行していませんが、今のところBeginReceiveでは止まりません。

何がちがうのでしょう?

気になる点を挙げてみます。


>   '''コールバックメソッド
>   '''AsyncCallbackデリゲートによって呼び出されるメソッドです
>   Public Overridable Sub ReceiveCallBack(ByVal ar As System.IAsyncResult)
> ' '受信データ長
> Dim rlen As Integer = -1
> Dim err As System.Net.Sockets.SocketError = Nothing
> Try
> SyncLock Me.syncSocket
> ' '読み込んだ長さを取得
> rlen = Me._socket.EndReceive(ar, err)
> End SyncLock
> Catch
> 'Socketを閉じた時
> End Try


このCatchでの例外処理はどうなっていますか?
握りつぶしてしまっているなら、次のBeginReceiveがおかしくても不思議ではありません。


> ※処理省略
> ※イベントライズさせ、UI画面に通知します。

UIオブジェクトの操作はきちんとUIスレッドから行っていますか?


> ' '受信バッファサイズ再設定
> ReDim receiveBuffer(Me._maxReceiveLength)

receiveBufferはスコープ外のはずです。
どこでどう定義され、どう操作していますか?
きちんとアクセスしていますか?

もしreceiveBufferが該当クラスのメンバなら、
_socketをSyncLockで囲うのと同様、receiveBufferも囲うべきでしょう。


> SyncLock Me.syncSocket
> ' 'ソケット切断チェック
> If _socket Is Nothing Then
> ' ''切断している場合は非同期受信終了
> Return
> End If
> Try
> ' '非同期受信再開
> Me._socket.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, _
> Sockets.SocketFlags.None, _
> New AsyncCallback(AddressOf ReceiveCallBack), receiveBuffer) ←●稀に無反応に…
> Catch
> 'ソケットが閉じたとき
> Me.Close()
> Return

BeginReceiveが同期的に終了する可能性があることを考慮していますか?
BeginReceiveの実装がどうなっているのかは知りませんが、
一般の非同期呼び出しは同期的な終了もあり得ます。

その場合ReceiveCallBackが再帰的に呼ばれることになりますので、
BeginReceive以降のコードやこのインスタンスのほかのメンバの操作で気を使わなければいけません。


> 蛇足ですが…
> リソースモニタとにらめっこしていたら気になる数値がありました。
> メモリの「ハードフォールト(ページフォールト)/分」が単体で常時900程度です。
> これは問題ないのでしょうか?
> (明らかにパフォーマンスに影響しているとは思いますが…)

これだけではなんとも。
BeginReceiveが止まることと因果関係があるかもしれませんが、
そこからBeginReceiveを正常に動作させるための知見は得られないでしょう。


引用返信 編集キー/
■46691 / inTopicNo.12)  Re[9]: Socket.BeginReceiveのコールバック障害
□投稿者/ maruma (9回)-(2010/02/09(Tue) 11:26:38)
No46671 (れい さん) に返信

返信ありがとうございます。
こちらの返信が遅れてしまい、申し訳ございません。
早速、インラインにて返答、質問します。

>>BeginSendに変えてみましたが、変わりなかった事を報告します。
>>また、ThreadPool.GetAvailableThreadsを出力するためには
>>実行スレッドが関係ありますでしょうか?
> 
> 関係ありません。
> 常時一定値ならばそこに問題はありませんね。

了解しました。
今回の問題はスレッドプールの実行スレッド数には関係なさそうですね。


>>受信部分のもう少し詳細なコードを以下に示します。
> 
> ちょうどいま同じようなコードでごにょごにょしています。
> 長期間実行していませんが、今のところBeginReceiveでは止まりません。
> 何がちがうのでしょう?
> 
> 気になる点を挙げてみます。
> 
> 
>>  '''コールバックメソッド
>>  '''AsyncCallbackデリゲートによって呼び出されるメソッドです
>>  Public Overridable Sub ReceiveCallBack(ByVal ar As System.IAsyncResult) 
>>        ' '受信データ長
>>        Dim rlen As Integer = -1
>>        Dim err As System.Net.Sockets.SocketError = Nothing
>>        Try
>>            SyncLock Me.syncSocket
>>                ' '読み込んだ長さを取得
>>                rlen = Me._socket.EndReceive(ar, err)
>>            End SyncLock
>>        Catch
>>            'Socketを閉じた時
>>        End Try
> 
> 
> このCatchでの例外処理はどうなっていますか?
> 握りつぶしてしまっているなら、次のBeginReceiveがおかしくても不思議ではありません。

記載通り、しっかりと握りつぶしてました…。
クローズ処理とリターン処理を追加してみようと思います。
と、ここでのエラーもログに出力するようにしてみます。


>>		※処理省略
>>		※イベントライズさせ、UI画面に通知します。
> 
> UIオブジェクトの操作はきちんとUIスレッドから行っていますか?

こちらはきちんとUIオブジェクトからInvokeさせていました。
一応、InvokeRequiredで判断してます。


>>	  	' '受信バッファサイズ再設定
>>        ReDim receiveBuffer(Me._maxReceiveLength)
> 
> receiveBufferはスコープ外のはずです。
> どこでどう定義され、どう操作していますか?
> きちんとアクセスしていますか?
> 
> もしreceiveBufferが該当クラスのメンバなら、
> _socketをSyncLockで囲うのと同様、receiveBufferも囲うべきでしょう。

receiveBufferはオート(ローカル)変数です。
ReceiveCallBackメソッドの内部で定義されています。

ここで1つ質問なのですが、
Socket.BeginReceive メソッド (Byte[] _
                             , Int32 _
                             , Int32 _
                             , SocketFlags _
                             , SocketError _
                             , AsyncCallback _
                             , Object)
の第一引数である Byte[] はオート変数でも問題ないのでしょうか?
(私は、引数がByval形式である以上、コピーが渡されると考えています。
…が、厳密にはInteger型等限られた型のみしか値渡ししないのでしょうか?)


>>        SyncLock Me.syncSocket
>>            ' 'ソケット切断チェック
>>            If _socket Is Nothing Then
>>                ' ''切断している場合は非同期受信終了
>>                Return
>>            End If
>>            Try
>>                ' '非同期受信再開
>>                Me._socket.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, _
>>                    Sockets.SocketFlags.None, _
>>                    New AsyncCallback(AddressOf ReceiveCallBack), receiveBuffer) ←●稀に無反応に…
>>            Catch
>>                'ソケットが閉じたとき
>>                Me.Close()
>>                Return
> 
> BeginReceiveが同期的に終了する可能性があることを考慮していますか?
> BeginReceiveの実装がどうなっているのかは知りませんが、
> 一般の非同期呼び出しは同期的な終了もあり得ます。

私の不勉強で理解できませんでした…。

まず、この点ですが、この「可能性」が発生する要因が判りません。
スレッドプールの制限や、ソケットオブジェクトの破棄の他に有り得るということですよね?
(OSやサービスの暴走、ハード的な問題はさておき…)
一般的な非同期呼び出しはデリゲートやスレッドプールを利用した実行から、
スレッド生成までということですよね?
何故、「同期的な終了」が発生するのでしょうか?

> その場合ReceiveCallBackが再帰的に呼ばれることになりますので、
> BeginReceive以降のコードやこのインスタンスのほかのメンバの操作で気を使わなければいけません。
同期的終了によって再帰的に呼ばれる…??
その場合の実行スレッドはどうなっているのですか?
ちょっと頭が混乱してきました。

ReceiveCallBackを実行するスレッドはAsyncCallbackによるデリゲートを利用した
非同期スレッド…じゃないんでしょうか?
すみません、非同期やソケットクラスを分かったつもりでいたようです…。

質問ばかりですみません。
ですが、せっかく教えて頂いたので、最後まで理解したい思いがあります。

何卒ご教授願います。

>>蛇足ですが…
>>リソースモニタとにらめっこしていたら気になる数値がありました。
>>メモリの「ハードフォールト(ページフォールト)/分」が単体で常時900程度です。
>>これは問題ないのでしょうか?
>>(明らかにパフォーマンスに影響しているとは思いますが…)
> 
> これだけではなんとも。
> BeginReceiveが止まることと因果関係があるかもしれませんが、
> そこからBeginReceiveを正常に動作させるための知見は得られないでしょう。

蛇足まで返信ありがとうございます。
とりあえず、本件と話が逸れますので、
この蛇足はここで終わりにします。

以上、よろしくお願い致します。

引用返信 編集キー/
■46699 / inTopicNo.13)  Re[10]: Socket.BeginReceiveのコールバック障害
□投稿者/ れい (868回)-(2010/02/09(Tue) 14:43:03)
No46691 (maruma さん) に返信
> >> ' '受信バッファサイズ再設定
> >> ReDim receiveBuffer(Me._maxReceiveLength)
>>
>>receiveBufferはスコープ外のはずです。
>>どこでどう定義され、どう操作していますか?
>>きちんとアクセスしていますか?
>>
>>もしreceiveBufferが該当クラスのメンバなら、
>>_socketをSyncLockで囲うのと同様、receiveBufferも囲うべきでしょう。
>
> receiveBufferはオート(ローカル)変数です。
> ReceiveCallBackメソッドの内部で定義されています。
>
> ここで1つ質問なのですが、
> Socket.BeginReceive メソッド (Byte[] _
> , Int32 _
> , Int32 _
> , SocketFlags _
> , SocketError _
> , AsyncCallback _
> , Object)
> の第一引数である Byte[] はオート変数でも問題ないのでしょうか?
> (私は、引数がByval形式である以上、コピーが渡されると考えています。
> …が、厳密にはInteger型等限られた型のみしか値渡ししないのでしょうか?)

オート変数でも問題ありません。
ですが、そのオート変数が指すバッファをAsyncCallback内からアクセスする必要があります。

後述しますが、
AsyncCallbackやEndReceiveは別スレッドで実行されたり、BeginReceiveのスレッドから実行されたり、再入したりしますので、
Redimで再定義するとややこしい事態になるかと思います。

それを憂えています。

> >> SyncLock Me.syncSocket
> >> ' 'ソケット切断チェック
> >> If _socket Is Nothing Then
> >> ' ''切断している場合は非同期受信終了
> >> Return
> >> End If
> >> Try
> >> ' '非同期受信再開
> >> Me._socket.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, _
> >> Sockets.SocketFlags.None, _
> >> New AsyncCallback(AddressOf ReceiveCallBack), receiveBuffer) ←●稀に無反応に…
> >> Catch
> >> 'ソケットが閉じたとき
> >> Me.Close()
> >> Return
>>
>>BeginReceiveが同期的に終了する可能性があることを考慮していますか?
>>BeginReceiveの実装がどうなっているのかは知りませんが、
>>一般の非同期呼び出しは同期的な終了もあり得ます。
>
> 私の不勉強で理解できませんでした…。
>
> まず、この点ですが、この「可能性」が発生する要因が判りません。
> スレッドプールの制限や、ソケットオブジェクトの破棄の他に有り得るということですよね?
> (OSやサービスの暴走、ハード的な問題はさておき…)
> 一般的な非同期呼び出しはデリゲートやスレッドプールを利用した実行から、
> スレッド生成までということですよね?
> 何故、「同期的な終了」が発生するのでしょうか?
>略
> ReceiveCallBackを実行するスレッドはAsyncCallbackによるデリゲートを利用した
> 非同期スレッド…じゃないんでしょうか?
> すみません、非同期やソケットクラスを分かったつもりでいたようです…。
>

BeginXXXで始めた非同期処理はいつ終わるのかわかりません。
BeginXXXから戻る前に終わっていたとしてもおかしくはありませんので、
そのようにコーディングする必要があります。

また、別スレッドにタスクを投げるのは、たとえそれがスレッドプールであってもコストがかかります。
同じスレッド内で処理を終えられるのであればそのほうが早い。

ならば、
BeginXXXが呼ばれた時点で、スレッドプールにタスクを投げるよりも早く処理を終了できると判断できる場合は
BeginXXXから戻る前にBeginXXXを呼んだスレッドでAsyncCallbackを呼んでしまおう、
というのが同期的終了です。

同期的終了した場合はIAsyncResult.CompletedSynchronouslyがtrueになります。


>>その場合ReceiveCallBackが再帰的に呼ばれることになりますので、
>>BeginReceive以降のコードやこのインスタンスのほかのメンバの操作で気を使わなければいけません。
> 同期的終了によって再帰的に呼ばれる…??
> その場合の実行スレッドはどうなっているのですか?
> ちょっと頭が混乱してきました。

同期的終了の場合、
BeginXXXを呼んだスレッドでAsyncCallbackが呼ばれます。

今回のようにAsyncCallbackでBeginXXXを呼ぶと
AsyncCallbackとBeginXXXは入れ子になって同じスレッドで何回も呼ばれることになります。

#同期的終了が多すぎるとスタックが足りなくなるのも注意ですね。

BeginXXXに変数を渡す必要がある場合、AsyncCallbackを使う場合はいろいろと混乱が生じます。
BeginXXXを呼びだした時点(帰ってきた時点ではない)から、EndXXXから戻るまでの間にAsyncCallbackが呼ばれます。

BeginXXXから戻った時点で操作が完了している場合もあり得る
というのをきちんと考慮していれば大丈夫です。

#先ほど少し見てみましたが、
#Socket.BeginXXXは同期終了する場合もありました。

引用返信 編集キー/
■46705 / inTopicNo.14)  Re[11]: Socket.BeginReceiveのコールバック障害
□投稿者/ maruma (10回)-(2010/02/09(Tue) 15:54:57)
2010/02/09(Tue) 17:43:17 編集(投稿者)
2010/02/09(Tue) 15:59:26 編集(投稿者)

No46699 (れい さん) に返信

れいさん、返信ありがとうございます!!

> オート変数でも問題ありません。
> ですが、そのオート変数が指すバッファをAsyncCallback内からアクセスする必要があります。
>
> 後述しますが、
> AsyncCallbackやEndReceiveは別スレッドで実行されたり、BeginReceiveのスレッドから実行されたり、再入したりしますので、
> Redimで再定義するとややこしい事態になるかと思います。
> それを憂えています。

了解しました。
Redimで再定義せず、Dimで生成することにします。


> BeginXXXで始めた非同期処理はいつ終わるのかわかりません。
> BeginXXXから戻る前に終わっていたとしてもおかしくはありませんので、
> そのようにコーディングする必要があります。
> また、別スレッドにタスクを投げるのは、たとえそれがスレッドプールであってもコストがかかります。
> 同じスレッド内で処理を終えられるのであればそのほうが早い。
> ならば、
> BeginXXXが呼ばれた時点で、スレッドプールにタスクを投げるよりも早く処理を終了できると判断できる場合は
> BeginXXXから戻る前にBeginXXXを呼んだスレッドでAsyncCallbackを呼んでしまおう、
> というのが同期的終了です。
> 同期的終了した場合はIAsyncResult.CompletedSynchronouslyがtrueになります。

…。正直、目からうろこです。
(れいさん、すごい、というか何と言うか…脱帽です。)
そして、この件の障害の原因がはっきりしました。
ありがとうございます。

あつかましいですが、
この情報がMSの公開情報として載っているサイト等あればぜひ教えて下さい。


> 同期的終了の場合、
> BeginXXXを呼んだスレッドでAsyncCallbackが呼ばれます。
> 今回のようにAsyncCallbackでBeginXXXを呼ぶと
> AsyncCallbackとBeginXXXは入れ子になって同じスレッドで何回も呼ばれることになります。

BeginReceive

(同期終了)

CallBack (同期実行) ※本来は非同期実行

BeginReceive ※ここでスレッド停止

上記の現象が確認出来ました。
同期終了後再帰的にBeginReceiveは呼べないようです。
(これはおかしい動作なのでしょうか?)


> #同期的終了が多すぎるとスタックが足りなくなるのも注意ですね。
> BeginXXXに変数を渡す必要がある場合、AsyncCallbackを使う場合はいろいろと混乱が生じます。
> BeginXXXを呼びだした時点(帰ってきた時点ではない)から、EndXXXから戻るまでの間にAsyncCallbackが呼ばれます。
> BeginXXXから戻った時点で操作が完了している場合もあり得る
> というのをきちんと考慮していれば大丈夫です。
> #先ほど少し見てみましたが、
> #Socket.BeginXXXは同期終了する場合もありました。

今回の件で、十分に身にしみました。
非同期実行を行う際に、特にCallBackを使用する場面は気をつけようと思います。

今回の件の回避方法として以下の処理を加えようと思います。
@ IAsyncResult.CompletedSynchronouslyのチェック
A @がTrueの場合はBeginReceiveを呼ぶためのメソッドを非同期実行
B @がTrueの場合は処理を終了(Reurn)
この処理をBeginReceive前後にあるSynclockステートメント前で行おうと考えています。

とりあえず、実装して結果を報告します。
また、実装方針がおかしい場合はご指導よろしくお願い致します。

いつも本当にありがとうございます。

以上、よろしくお願い致します。



追伸:
> #同期的終了が多すぎるとスタックが足りなくなるのも注意ですね。

エラーがキャッチ出来ないと思ったら、
StackOverflowExceptionはTryステートメントもSynclockステートメントも
無視してしまうのですね。
再帰的な構造になっている部分は特に気をつけます。






引用返信 編集キー/
■46730 / inTopicNo.15)  Re[12]: Socket.BeginReceiveのコールバック障害
□投稿者/ れい (869回)-(2010/02/09(Tue) 23:31:15)
No46705 (maruma さん) に返信
> そして、この件の障害の原因がはっきりしました。

お。
では問題は解決したも同然ですね。

> この情報がMSの公開情報として載っているサイト等あればぜひ教えて下さい。

MSDNの「非同期プログラミングのデザインパターン」辺りに書いてあったかもしれませんが。

IAsyncResultのようなやり方(デザインパターンというのでしょうか?)は
Completion I/Oが出てきたあたり(OS/2かな?)、ずいぶん以前から広まっているやり方ですので、
業界の常識というか不文律というか。
「CompletedSynchronouslyというプロパティがある」ということから推測しなさい、
ということなのかと思います。


> BeginReceive
> ↓
> (同期終了)
> ↓
> CallBack (同期実行) ※本来は非同期実行
> ↓
> BeginReceive ※ここでスレッド停止
>
> 上記の現象が確認出来ました。
> 同期終了後再帰的にBeginReceiveは呼べないようです。
> (これはおかしい動作なのでしょうか?)

はい。おかしい動作です。

あまりに深い場合はStackOverflowの問題がありますが、
それを除けばCallback内でBeginXXXを呼んでも構いません。

他のフレームワークやOSのIAsyncResult相当の処理ではCallback内から呼んではいけないものもありますが、
.NetFrameworkはその制限は無い筈です。

もしそこで止まるとするならば、
BeginXXXに渡した変数などに対しておかしな操作をしているのであろうと思います。


> 今回の件の回避方法として以下の処理を加えようと思います。
> @ IAsyncResult.CompletedSynchronouslyのチェック
> A @がTrueの場合はBeginReceiveを呼ぶためのメソッドを非同期実行
> B @がTrueの場合は処理を終了(Reurn)
> この処理をBeginReceive前後にあるSynclockステートメント前で行おうと考えています。
>
> とりあえず、実装して結果を報告します。
> また、実装方針がおかしい場合はご指導よろしくお願い致します。

同期終了するのは「そのほうが処理が軽い」からなので、無理に非同期実行するのは間違いです。

パフォーマンスが要らないので気にしない、というのであれば、
非同期呼び出しを使わない、Callbackを使わないよう設計すべきです。
そのほうがより簡単に、確実に、素早く実装できます。

上記のように、StackOverflowの問題を除けばCallbackからBeginReceiveを呼んでも問題ない、
というか、「問題ないように作れるはず」なので、そう組むべきです。

また、普通に組んでいればStackOverflowが問題になる場合はほとんどありません。
今回の場合、Callback関数を実行する間隔よりも、パケットが届く間隔が短いと無限の再帰・再入が発生します。
Callback内でUIスレッドにInvokeしているという話ですので、かなりのコストがかかっているかと思います。

InvokeをBeginInvokeに変えるだけでだいぶ改善されると思いますが、
本質的にはもう少し変える必要があります。

UIは「人間が見て、触る」ためにあるので、それほどの速度は要りません。
画面を10msecで更新しても見えませんし、そもそもモニタに表示もされません。(垂直同期は60Hz=16msecくらいですので。)

ですので、「CallbackからUIに受信を通知する」のではなく、「UI側から受信があったかどうかを確認する」ほうが合理的です。

・Callbackでは受信回数や受信バイト数をスレッドセーフな変数に書き込む
・UI側はTimerを使って定期的にスレッドセーフな変数を確認する。前回と違っていれば「受信あり」

という処理になります。

一般的に、Callbackはなるべく短く実装します。
ほとんどの場合CompletedSynchronouslyを見る必要はありません。

引用返信 編集キー/
■46877 / inTopicNo.16)  Re[13]: Socket.BeginReceiveのコールバック障害
□投稿者/ maruma (11回)-(2010/02/12(Fri) 21:45:37)
No46730 (れい さん) に返信

れいさん、返信ありがとうございます。
遅くなりましたが、ソケットクラスを修正し、
ある程度の動作を確認したので報告します。


> IAsyncResultのようなやり方(デザインパターンというのでしょうか?)は
> Completion I/Oが出てきたあたり(OS/2かな?)、ずいぶん以前から広まっているやり方ですので、
> 業界の常識というか不文律というか。
> 「CompletedSynchronouslyというプロパティがある」ということから推測しなさい、
> ということなのかと思います。

常識だったのですね…。
安易にMSDNの項目だけを確認していたのが間違いでした。
今後は関連するトピックまで読んでいこうと思います。


> 同期終了するのは「そのほうが処理が軽い」からなので、無理に非同期実行するのは間違いです。
>
> パフォーマンスが要らないので気にしない、というのであれば、
> 非同期呼び出しを使わない、Callbackを使わないよう設計すべきです。
> そのほうがより簡単に、確実に、素早く実装できます。
>
> 上記のように、StackOverflowの問題を除けばCallbackからBeginReceiveを呼んでも問題ない、
> というか、「問題ないように作れるはず」なので、そう組むべきです。
>
> また、普通に組んでいればStackOverflowが問題になる場合はほとんどありません。
> 今回の場合、Callback関数を実行する間隔よりも、パケットが届く間隔が短いと無限の再帰・再入が発生します。
> Callback内でUIスレッドにInvokeしているという話ですので、かなりのコストがかかっているかと思います。
>
> InvokeをBeginInvokeに変えるだけでだいぶ改善されると思いますが、
> 本質的にはもう少し変える必要があります。
>
> UIは「人間が見て、触る」ためにあるので、それほどの速度は要りません。
> 画面を10msecで更新しても見えませんし、そもそもモニタに表示もされません。(垂直同期は60Hz=16msecくらいですので。)
>
> ですので、「CallbackからUIに受信を通知する」のではなく、「UI側から受信があったかどうかを確認する」ほうが合理的です。
>
> ・Callbackでは受信回数や受信バイト数をスレッドセーフな変数に書き込む
> ・UI側はTimerを使って定期的にスレッドセーフな変数を確認する。前回と違っていれば「受信あり」
>
> という処理になります。
>
> 一般的に、Callbackはなるべく短く実装します。
> ほとんどの場合CompletedSynchronouslyを見る必要はありません。

れいさんの教え通り、
CallBack内は短く改良しました。
(バッファに溜め込むだけ)
また、各プロセスがスレッドタイマーでバッファにアクセスし、
電文を取得するように変更しました。

修正に時間がかかりましたが、なんとか思い通りの動きをしています。
また、CPU使用率が常時40%程度であったのが30%程度まで減少しました。

まだ連続運転をきちんと行っていませんが、
性能向上と問題の修正には成功したと思います。

れいさん、皆様、
長らく教授頂きましてありがとうございました。

以上、よろしくお願い致します。

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


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

このトピックに書きこむ

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

管理者用

- Child Tree -