BackgroundWorkerについて への返答

投稿で使用できる特殊コードの説明。(別タブで開きます。)
本名は入力しないようにしましょう。
投稿した後で削除するときに使うパスワードです。返答があった後は削除できません。
返答する人が目安にします。相手が小学生か社会人かで返答の仕方も変わります。
最初の投稿が質問の場合、質問者が解決時にチェックしてください。(以降も追加書き込み・返信は可能です。)
※「過去ログ」について書くときはその過去ログのURLも書いてください。

以下の返答は逆順(新しい順)に並んでいます。

投稿者 daive  (社会人) 投稿日時 2009/2/24 21:43:08
ドライバが、実績があって、
BackgroundWorker でなく、通常の場合は、正常に動作するのであれば、

例えば、ユーザーコントロールを作って、1接続毎に、使ってみるのはいかがでしょうか?
ユーザーコントロール内で、作り方によっては、タイマーを使って定期的に生存確認もできますし、
他の処理をさせることも可能です。

時計表示や、シーケンサ制御などで、使用していますが、余り工夫しなくても、
マルチプロセス動作ぽいことが可能です。
(時計などは、デザインモードで動作しないような、工夫も必要な場合もありますが)

通常の場合でも、ドライバ部分で、ロックしてしまうのであれば、
メーカーに問い合わせするより、方法がないのではないでしょうか。

もちろん、DBなどの、よけいな処理を除いた、ドライバの動作検証用コードを作って
ドライバの動作を検証することは必要です。
また、ドライバが、VS2005用で、使用しているWindowsや、.NETのバージョンや、
OSのDLLのバージョンに依存しているかどうかの、確認も必要です。
その辺は、ドライバの取説を見れば書いてあるか、何処かに情報があるはずです。

現状の書込みからでは、これ位しか思い浮かびません。
装置、ドライバ、の種類や仕様、メーカーが不明ですし。

※全く関係ないかもですが、開発環境なのですが、VS2005IDEは結構実メモリを要求します。
 DBでも、開発用PCのメインメモリを要求したりしますので、変なスワップが起きて
 止まったように見えるという事は無いですよね?
 (作成するフォーム、プログラムにもよりますが、WindowsXP1GRAMでは、厳しいかもです。
  MS-SQLサーバーが開発マシンに居る場合は、初期設定のままの動的メモリ配置の場合は、
  変なクエリで、空きメモリが減少してしまいますし。CPUは、Pentium-4では厳しいですし。)
投稿者 みっちー  (社会人) 投稿日時 2009/2/24 08:47:37
みっちーです。
返信をいただいたにも関わらず、かなり間が空いてしまって大変申し訳ございません。

ドライバがマルチプロセス対応かどうかはまだ調べきれていないのですが、複数のリクエストを投げないようにしてもフリーズが起こるようなんです。
装置を1つだけにし、接続していないとき(isOpenフラグがFalseのとき)は再接続以外のことを行わない、(再接続用の)BackgroundWorkerがビジーのときは再接続処理も行わない。
このようにしても、やはりフリーズしてしまいます。
(テキストボックスやボタンなどに対する入力が受け付けられず、「(応答なし)」となってしまう状態です。)

この場合、daiveさんが示してくださったプロセスの二番目「ドライバに対する他のリクエストも受付けない」という部分がなく、結果三番目の「メインルーチン停止」も起こらないのではないかと思うのですが・・・。

私の勘違いなのか、それとも別の原因があるのか、ご教授いただければとおもいます。
また、もしほかにこのような仕様を実装できるやり方などありましたら、そちらもお願いいたします。
投稿者 daive  (社会人) 投稿日時 2009/2/7 09:15:23
>BackgroundWorker 
>deviceReConnect(装置番号) '※3 
となっていますが、ドライバの方は、マルチプロセス対応でしょうか?
マルチプロセス対応でない場合は、できませんよね?
呼出側がいくらバックグランド処理していても、受付側が、単一処理では、
応答が無くても当然では?

ドライバが再接続中なりで停止→ドライバに対する他のリクエストも受付けない→メインルーチンも停止

とかになっていませんか?
投稿者 みっちー  (社会人) 投稿日時 2009/2/4 09:19:19
はじめまして。みっちーと申します。
早速ですが、質問させてください。
現在仕事で、複数の装置から値を取得してDBに登録したり画面に表示したりするプログラムを作成しています。
使用しているのはVB2005です。
これについて、「一つ以上の装置との接続が切れた場合、接続できる状態になったら自動的に再接続をする」という機能を盛り込まなくてはならなくなりました。
装置との接続や値取得には装置メーカーが提供しているAPIを使用していますが、残念ながら「接続できる状態かどうか」を返してくれるプロパティやメソッドが存在しません。
ですので接続が切れた場合はTimerを使って、一定時間ごとに(必ず)再接続を試みるという仕様にすることになりました。

ところが、一つ問題が発生してしまいました。
BackgroundWorkerを使用しているにも関わらず、接続できない状態の場合、再接続を開始してからタイムアウトまでの間プログラムがフリーズしてしまうのです。
画面の操作ができなくなるのも問題ですが、装置とプログラムは常にお互いに通信をしていて、一定時間(約5秒)反応がないと相手方にエラーが発生していると判断する仕様になっているのです。(生存確認)
(この仕様は変更できません。)

再接続処理で止まっている間に、正常に接続できている装置から、プログラム側にエラーがあると判断されてしまうため、困っています。
マルチスレッドを使用していても、こういった場合は止まってしまうのが普通なのでしょうか?
よくBackgroundWorkerの説明で、Sleepを使ってわざと処理を止めて時間を使い、他のスレッドはそれにかかわらず正常に動く、という例をみるのですが、こういった場合はこれとはまた違うのでしょうか。
それとも何か、使い方や考え方が間違っているのでしょうか。

だいたいのプログラムは以下のようになっています。(直接関係ないところは端折ってあります。)

Private Sub frmMain_Load(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles MyBase.Load
   '装置接続処理。 
   '接続が確立されるまで生存確認は行わないので、ここで時間がかかるのは問題ありません。 
   '(ゆえにBackgroundWorkerは使用していません) 
End Sub

Private Sub Timer1_Tick(ByVal sender As System.ObjectByVal e As System.EventArgs) Handles Timer1.Tick
   Try
      '生存確認 
      For i As Integer = 0 To (装置数)
         '装置がオープンされていない場合、確認ができない(する必要がない)ため飛ばす 
         If isOpen(i) Then '※1 
            Continue For
         End If

         If i = 0 Then
            If (Not _bwLive1.IsBusy()) Then
               bwLive1.RunWorkerAsync()
         End If
         ElseIf i = 1 Then
            If (Not _bwLive2.IsBusy()) Then
               _bwLive2.RunWorkerAsync()
            End If
         End If
         (以下予定している装置数分だけ続きます。)
      Next
      

      '再接続(接続されていない場合は再接続を試みる) 
      For i As Integer = 0 To (装置数)
         '装置がオープンされていれば問題ないので飛ばす 
         If isOpen(i) Then '※1 
            Continue For
         End If

         If i = 0 Then
            If (Not _bwRetry1.IsBusy()) Then
               bwRetry1.RunWorkerAsync()
            End If
         ElseIf i = 1 Then
            If (Not _bwRetry2.IsBusy()) Then
               _bwRetry2.RunWorkerAsync()
            End If
         End If
         (以下予定している装置数分だけ続きます。)
      Next

   Catch
      'エラー時処理 
   End Try

End Sub

'※2 
Private Sub _bwLive1_DoWork(ByVal sender As System.ObjectByVal e As System.ComponentModel.DoWorkEventArgs) Handles _bwLive1.DoWork
   '生存確認の処理 
   '(正常であることを示す値を装置へ書き込みにいく。などの処理) 
End Sub

'※2 
Private Sub _bwRetry1_DoWork(ByVal sender As System.ObjectByVal e As System.ComponentModel.DoWorkEventArgs) Handles _bwRetry1.DoWork
   Try
      deviceReConnect(装置番号) '※3 
      isOpen(0) = True
   Catch ex As Exception
      isOpen(0) = False
      Throw
   End Try
End Sub


※1)この変数はこちらで独自に用意したもので、APIとは無関係です。接続時または再接続時にTrue、切断時または通信エラー時にFalseにするようコーディングしています。
※2)_bwLive2以降、_bwRetry2以降は装置番号が違うだけで、他は1と同じです。
※3)再接続処理。ここで、まだ接続できない状態の場合はタイムアウトまで待たされるため、時間がかかります。
   また、タイムアウトは例外エラーとして処理されるため、「Catch ex As Exception」でキャッチされます。


以上です。
※3の部分でどれだけ時間がかかろうと、生存確認は通常と同じタイミングで実行され、フォームの操作も可能であるというつもりでコーディングしたのですが、実際はそうなっていません。
全体的にフリーズしてしまっているような状態です。
(※3が終われば何事もなかったように正常に動作します。)
「タイムアウトを待っている間にも他のスレッドは進行する」というようにしたいのですが、どうしたらいいのでしょうか。

長々と申し訳ありません。
何かアドバイスがあればよろしくお願いいたします。