- PR -

マルチスレッドでのフォームの更新処理

投稿者投稿内容
たけ
常連さん
会議室デビュー日: 2004/02/23
投稿数: 42
お住まい・勤務地: 神奈川県/東京都
投稿日時: 2005-03-11 12:16
VB.NETでPocketPCを対象としたマルチスレッドのプログラムを作成しています。

プログラムの概要は、以下のようになっています。
 1.フォームにテキストボックスとボタンを3個配置
 2.ボタン1を押すとWorkerスレッドが起動する
 3.Workerスレッドでは、ループ処理で定期的にテキストボックスの内容を更新する
 4.ボタン2を押すと、Workerスレッドが一時停止し、
  テキストボックス2に入力した内容がテキストボックス1に表示される
  表示後、Workerスレッド再開する
 5.ボタン3を押すと、Workerスレッド停止する

コントロールのプロパティの変更は、対象のコントロールのスレッド上からしか行えないということなので、
Invokeメソッドを使用してテキストボックスの更新を行うようにしました。
ただ、Compact FrameworkではInvokeメソッドにパラメータを渡せないので、
下記サイトを参考にしてInvokeメソッドにパラメータを渡せるようにしました。
 http://www.gotdotnet.com/japan/quickstart/CompactFramework/doc/controlinvoker.aspx

このようにしてプログラムを作って動かしたところ、3までは問題なく動きました。
しかし、4番を行おうとするとプログラムがフリーズしてしまいます。
ただ、フリーズするときとしないときがあります。

デバッグして調べたところ、
Workerスレッドがテキストボックス1を更新しようとするときにフリーズしていました。
Workerスレッドはループの中で毎回一時停止フラグを確認し、
フラグが立っていれば一時停止するという処理になっています。
そのためボタン2を押してフラグを立てても、すぐには停止しないので、
ボタン2押下後にもWorkerスレッドがテキストボックス1を更新することがあります。

このような問題にぶつかっているのですが、これを解決するにはどうしたらよいのでしょうか。
なにかご存知の方がいらっしゃれば、ご教授よろしくお願いいたします。

以下、Workerスレッドの処理です
===============================================================================
'5秒毎にTextBox1を更新する
Public Sub UpdateText()
  Dim updateText As New MethodCallInvoker(AddressOf mainThread.UpdateTextBox)
  Try
    Do
      '一時停止フラグが立っている間、処理を中断
      Do While threadPause = True
        threadRunning = False
        System.Threading.Thread.Sleep(500)
      Loop

      'スレッド実行フラグをTrueに設定
      threadRunning = True

      '2秒待機(2×1000=5000[ms])
      System.Threading.Thread.Sleep(2 * 1000)

      'TextBox1を更新する
      message = DateTime.Now.ToString & ": まわ〜る"
      conInvoker.Invoke(updateText, mainThread.TextBox1, message)

      '3秒待機(3×1000=5000[ms])
      System.Threading.Thread.Sleep(3 * 1000)
    Loop Until threadStop = True 'スレッド停止フラグが立つまでループ
  Catch ex As Exception
    MessageBox.Show(ex.Message, "警告", MessageBoxButtons.OK, _
             MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button1)
  Finally
    threadRunning = False
    threadStop = False
    threadPause = False
  End Try
End Sub
===============================================================================
らぶま
常連さん
会議室デビュー日: 2004/10/21
投稿数: 32
投稿日時: 2005-03-11 12:48
らぶまと申します。
よろしくお願いします。

VB.NETには詳しくないので、
一般的な話で恐縮です。

プログラム中の変数
threadRunning, threadStop,threadPause
は、たけさんが定義した変数でしょうか?

もし、そうだとしたら、うまく動かすことはできないでしょう。
変数を監視したり、変更するタイミングは予想できませんので(動作環境等で変わります)。

ミューテックスやセマフォなどを用いて、
スレッド間の同期処理をきちんと行わなければなりません。

Windowsや.NETの環境ならそれらが整備されていると思います。
_________________
たけ
常連さん
会議室デビュー日: 2004/02/23
投稿数: 42
お住まい・勤務地: 神奈川県/東京都
投稿日時: 2005-03-11 17:12
らぶまさん、アドバイスありがとうございます。

アドバイスを参考にしてManualResetEventを使って同期を取ってみました。

 1.ボタン2を押すとWaitOneメソッドでフォームスレッドをブロックします
 2.WorkerスレッドがSetメソッド呼び出して同期イベントをシグナル状態にします
 4.WorkerスレッドをWaitOneメソッドでブロックします
 5.フォームスレッドでボタン2をDisableにします
 6.フォームスレッドでTextBoxの表示を更新します
 7.フォームスレッドでSetメソッドを呼び出し、Workerスレッドの処理を再開します

いまのところはフリーズせずに動いていますが、同期処理の方法はこれでいいのでしょうか。
また、この方法だとボタン2を押してからそれがDisableになるまでに最長で5秒かかってしまいます。
ボタン2のプロパティを変更してからWaitOneメソッドスレッドをブロックしてみたのですが、
この方法ではフリーズすることがありました。
なんとかボタン2を押してすぐにDisableに出来る方法はないでしょうか。

================================================================================
'非シグナル状態でManualResetEventオブジェクトを作成
Public manualEvent As ManualResetEvent = New ManualResetEvent(False)

Private Sub Button2_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button2.Click
  Try
    manualEvent.WaitOne()  'シグナル状態になるまでスレッドをブロックする
    manualEvent.Reset()   '非シグナル状態にする

    ボタンを無効にして、TextBoxの内容を更新する
  Catch ex As Exception
    例外処理
  Finally
    manualEvent.Set()    'シグナル状態にする
  End Try
End Sub

'5秒毎にTextBox1を更新する
Public Sub UpdateText()
  'スレッド実行フラグをTrueに設定
  threadRunning = True

  Try
    Do
      manualEvent.Set()    'シグナル状態にする
      System.Threading.Thread.Sleep(500)
      manualEvent.WaitOne()  'シグナル状態になるまでスレッドをブロックする
      manualEvent.Reset()   '非シグナル状態にする

      テキストの更新処理をする

      System.Threading.Thread.Sleep(5 * 1000)
    Loop Until threadStop = True
  Catch ex As Exception
   例外処理
  End Try
End Sub
================================================================================
たけ
常連さん
会議室デビュー日: 2004/02/23
投稿数: 42
お住まい・勤務地: 神奈川県/東京都
投稿日時: 2005-03-11 20:40
あれから色々とやってみた結果、またフリーズしてしまいました。

ボタン2を押してからWaitOneメソッドでフォームスレッドをブロックするまでの間に、
Workerスレッドの方がテキスト更新処理を行うとフリーズします。
根本的に何かまちがっているのでしょうか。
らぶま
常連さん
会議室デビュー日: 2004/10/21
投稿数: 32
投稿日時: 2005-03-11 21:28
らぶまです。

ManualEventを使ったことがないので、
はっきりとはいえませんが、

ボタン2をDisableにする件については、
・イベントの初期状態をシグナル(TRUEにする)
・UpdateText()内のmanualEvent.Set()の位置をSystem.Threading.Thread.Sleep(5 * 1000) の上にする
で対応できないでしょうか。
こうしないと、Workerスレッドをボタン1で動かす前に、
いきなりボタン2を押した場合にフリーズすると思いますよ。

後の書き込みの件は・・じっくりみないとわからないなあ。
未記入
大ベテラン
会議室デビュー日: 2005/03/12
投稿数: 148
投稿日時: 2005-03-12 23:34
私はVB.NETが判らないので予想して答えますね
思いっきり勘違いしているかも。

整理してみると
<1>.Workerスレッドが、フォームスレッドで処理が行われるのを待つ。
<2>.(フォームスレッド)ボタン2を押すと、Workerスレッドが一時停止するまで待つ。
ということですか?

これはつまり、お互い相手のスレッドと同期を取ろうとする。
このためデッドロックを起こすのではないでしょうか。

俺なら非同期にする。
納得いかないが、それ以外の方法を知らないので。
未記入
大ベテラン
会議室デビュー日: 2005/03/12
投稿数: 148
投稿日時: 2005-03-13 09:30
私はVB.NETが判らないので予想して答えますね
思いっきり勘違いしているかも。

Sleep(500)なんてやっているとスレッドにしたいみが薄れるとおもいます。

排他制御すると『お互い相手のスレッドと同期を取ろうとする』ことになるのでだめですから、遅延させる仕組みになってしまうなあ。

フォームスレッドでInvokeされたときにButton2_Clickが実行されることはない。
Button2_Clickもフォームスレッドで実行されるので。←たぶん想像です。

[A].Button2_Clickが実行されてから、
WorkerスレッドがこれからInvokeをしようとすると問題になるのですよね。
Button2_Clickで『WorkerスレッドがInvokeをしようとしている』を検出したら、
Button2_ClickはButton2_Clickの処理をやめて(Exit Sub?DoEvent?)、
先にフォームスレッドでInvokeされたときの処理させてから(どうやって?)
再度Button2_Clickの処理をする(PostMessage?)必要がありますね。
一方Workerスレッドでは、
InvokeしようとしていることをButton2_Clickが検出できるようにして、
また、Button2_Clickが実行されていることを検出したらInvokeするのをやめて
一時停止できる状態にしてあげる。
両者の検出および検出できるようにする部分は排他制御してね。
でもInvoke中などはブロックしないように。
これを実現するには、あ〜面倒だ。
出来なくはないと思うがVB.NETを知らないので挙動が判らん。

俺としては『同期するInvokeなんてやめてまえ』と思う。
[B].Workerスレッドから非同期に呼び出しにする。
[C].タイマーでフォームスレッドのほうからupdateTextの値を見て更新する。

いずれにしても排他制御も必要になるな。

[ メッセージ編集済み 編集者: 未記入 編集日時 2005-03-13 09:44 ]
たけ
常連さん
会議室デビュー日: 2004/02/23
投稿数: 42
お住まい・勤務地: 神奈川県/東京都
投稿日時: 2005-03-14 00:09
みなさん、返信が遅れ申し訳ありません。

らぷまさん、レスありがとうございます。

残念ながら、アドバイスをいただいた方法ではうまくいきませんでした。

未記入さん、アドバイスありがとうございます。

確かにおっしゃるとおりの動作をしているように思えます。
非同期でスレッドを実行できればよいのでしょうが、
いかんせん開発対象がCompact Framewrokのため、
非同期処理(BeginInvoke、EndInvaoke)が実装されていないのです。
なんとかWorkerスレッドがInvokeを行うことをフォームスレッドに通知し、
それをフォームスレッドが感知してデッドロックが起こらないようにしてみます。

結果がでたらまた投稿したします。

スキルアップ/キャリアアップ(JOB@IT)