none
DoWork内で無限ループはダメなんですか? RRS feed

  • 質問

  • VBで体重表示プログラムを作成しています。

    DoWorkルーチンで0.5秒ごとに体重計と通信して測定データを貰い、

    ProgressChangedルーチンで測定データをフォームのラベルに表示させるプログラムです。

    DoWorkルーチンは終了処理に入るまで無限ループさせるつもりでいたのですが、

    MSDN(.NET Flamework4)の注意書きには、ポーリング ループは、CancellationPending true

    設定し損ねる場合があることに注意してください、と書いてあります。

    これって、無限ループはまずいと解釈して良いのでしょうか?

    プログラムの骨組みは以下のようなイメージです。

    DoWorkルーチン

            Do
                System.Threading.Thread.Sleep(500)
                体重 = getweight() 'getweightは体重計で用意された関数
                BackgroundWorker1.ReportProgress(0)
            Loop

    ProgressChangedルーチン

            Label1.text=Cstr(体重)

    今回初めて投稿します。何か投稿上の問題がありましたらご指摘&ご容赦ください。

    2013年3月27日 13:23

回答

  • Sleep()で待つのではなく、Timerを使って500msごとに呼び出してもらう(callback)すればいいかと。
    2013年3月27日 14:55
  • MSDN(.NET Flamework4)の注意書きには、ポーリング ループは、CancellationPending true

    設定し損ねる場合があることに注意してください、と書いてあります。

    これって、無限ループはまずいと解釈して良いのでしょうか?

    いえ、おそらくそういう意味ではありません。その注意書きの先に次のように書かれています。

    競合状態と呼ばれ、マルチスレッドのプログラミングにおける一般的な懸念事項です。」

    つまり、マルチスレッドの問題です。CancelAsyncメソッドを実行してCancellationPendingをtrueにしても、DoWorkイベントハンドラでそれをすぐに感知して終了するわけではありません。もし、感知する前に他のスレッドによってCancellationPendingがfalseにリセットされてしまえば、終了することはないでしょう。これはポーリングによってCancellationPendingの状態を確認する間隔が長ければ長いほど発生の確率が高まります。

    よって、DoWorkイベントハンドラを複数のスレッドで処理していなければ、おそらく問題ないでしょう。無限ループの処理におけるキャンセルと、終了に1年かかる処理のキャンセルに違いがあるでしょうか? 終了に1年かかる処理は、今日という単位で見れば無限ループと変わりません。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/


    2013年3月28日 2:35
    モデレータ
  • 佐祐理さんのおっしゃるようにTimerを使うのが良いかと思います。

    実装例

    http://www.atmarkit.co.jp/fdotnet/dotnettips/372formstimer/formstimer.html

    BackgroundWorkerを使用するのであれば、

    変数「体重」をフィールド変数とせず、DoWorkルーチンのローカル変数とし、

    ReportProgressメソッドの第2引数に渡し、ProgressChangedイベントの

    ProgressChangedEventArgs.UserStatusプロパティで取得する方が良いと思います。

    (フィールド変数とすると、複数のスレッドから同一の変数を変更できてしまうため、タイミングによって

     予期しない値となる場合があるため)

    ご参考

    http://dobon.net/vb/dotnet/programing/displayprogress.html#backgroundworker

    2013年3月31日 14:23

すべての返信

  • Sleep()で待つのではなく、Timerを使って500msごとに呼び出してもらう(callback)すればいいかと。
    2013年3月27日 14:55
  • MSDN(.NET Flamework4)の注意書きには、ポーリング ループは、CancellationPending true

    設定し損ねる場合があることに注意してください、と書いてあります。

    これって、無限ループはまずいと解釈して良いのでしょうか?

    いえ、おそらくそういう意味ではありません。その注意書きの先に次のように書かれています。

    競合状態と呼ばれ、マルチスレッドのプログラミングにおける一般的な懸念事項です。」

    つまり、マルチスレッドの問題です。CancelAsyncメソッドを実行してCancellationPendingをtrueにしても、DoWorkイベントハンドラでそれをすぐに感知して終了するわけではありません。もし、感知する前に他のスレッドによってCancellationPendingがfalseにリセットされてしまえば、終了することはないでしょう。これはポーリングによってCancellationPendingの状態を確認する間隔が長ければ長いほど発生の確率が高まります。

    よって、DoWorkイベントハンドラを複数のスレッドで処理していなければ、おそらく問題ないでしょう。無限ループの処理におけるキャンセルと、終了に1年かかる処理のキャンセルに違いがあるでしょうか? 終了に1年かかる処理は、今日という単位で見れば無限ループと変わりません。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/


    2013年3月28日 2:35
    モデレータ
  • 佐祐理さんのおっしゃるようにTimerを使うのが良いかと思います。

    実装例

    http://www.atmarkit.co.jp/fdotnet/dotnettips/372formstimer/formstimer.html

    BackgroundWorkerを使用するのであれば、

    変数「体重」をフィールド変数とせず、DoWorkルーチンのローカル変数とし、

    ReportProgressメソッドの第2引数に渡し、ProgressChangedイベントの

    ProgressChangedEventArgs.UserStatusプロパティで取得する方が良いと思います。

    (フィールド変数とすると、複数のスレッドから同一の変数を変更できてしまうため、タイミングによって

     予期しない値となる場合があるため)

    ご参考

    http://dobon.net/vb/dotnet/programing/displayprogress.html#backgroundworker

    2013年3月31日 14:23
  • いえ、おそらくそういう意味ではありません。その注意書きの先に次のように書かれています。

    競合状態と呼ばれ、マルチスレッドのプログラミングにおける一般的な懸念事項です。」

    つまり、マルチスレッドの問題です。CancelAsyncメソッドを実行してCancellationPendingをtrueにしても、DoWorkイベントハンドラでそれをすぐに感知して終了するわけではありません。もし、感知する前に他のスレッドによってCancellationPendingがfalseにリセットされてしまえば、終了することはないでしょう。これはポーリングによってCancellationPendingの状態を確認する間隔が長ければ長いほど発生の確率が高まります。

    よって、DoWorkイベントハンドラを複数のスレッドで処理していなければ、おそらく問題ないでしょう。

    いや、ポーリングでチェックしている場合、最後にチェックしてから処理が完了する前にキャンセル要求される可能性があるわけで、この場合はキャンセル要求したにもかかわらずキャンセル要求は見逃され処理は普通に完了します。

    ここでのマルチスレッドにおける競合とはそういう話であって、DoWorkを複数スレッドで実行するというようなことではありません。
    また見てのとおり無限ループでは無い場合の話(普通に処理が完了する場合の、完了とキャンセルのタイミングに関する話)です。

    2013年4月1日 4:31
  • BackgroundWorker.DoWork イベント
    http://msdn.microsoft.com/ja-jp/library/system.componentmodel.backgroundworker.dowork(v=vs.80).aspx

    に書かれていることを引用すると以下のようになります。

    --- 引用開始 --------------------------------------------

    DoWork イベント ハンドラ内のコードは、キャンセル要求が行われているときに処理を終了できること、また、ポーリング ループは、CancellationPending に true を設定し損ねる場合があることに注意してください。この場合、キャンセル要求が行われていても、RunWorkerCompleted イベント ハンドラの System.ComponentModel.RunWorkerCompletedEventArgs の Cancelled フラグは true に設定されません。この状況は競合状態と呼ばれ、マルチスレッドのプログラミングにおける一般的な懸念事項です。マルチスレッド デザインに関する問題の詳細については、「マネージ スレッド処理の実施」を参照してください。

    --- 引用終了 --------------------------------------------

    原文だと以下になります。

    BackgroundWorker.DoWork Event
    http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.dowork(v=vs.80).aspx

    --- 引用開始 --------------------------------------------

    Be aware that your code in the DoWork event handler may finish its work as a cancellation request is being made, and your polling loop may miss CancellationPending being set to true. In this case, the Cancelled flag of System.ComponentModel.RunWorkerCompletedEventArgs in your RunWorkerCompleted event handler will not be set to true, even though a cancellation request was made. This situation is called a race condition and is a common concern in multithreaded programming. For more information about multithreading design issues, see Managed Threading Best Practices.

    --- 引用終了 --------------------------------------------

    私が意訳してみると以下のようになります。

    DoWorkイベントハンドラにおける処理は、キャンセルのリクエストがあった場合でも終了する場合があることに注意して下さい。そして、その時に、DoWorkイベントハンドラにおけるポーリングにおいて、CancellationPending がtrueになっていることを見逃す場合があります。・・・・・・・

    というわけで、missの訳仕方が違っているように思います。なぜならCancellationPending は読み取り専用であり、コードからtrueを設定することはできません。

    と、ここまでは私も理解できたのですが、競合状態と言うのが腑に落ちませんでした。上記の状態はキャンセルが間に合わなかったというだけで、どこにも不整合が発生していません。処理の最後に正常完了とメッセージを表示させれば、ユーザーはキャンセルが間に合わなかったことを知るでしょう。
    確かにキャンセルするタイミングによってキャンセルできたりできなかったりと結果が変わるのですが、これは競合ではなく、単なるタイミングの問題であり、改修すべきバグではないように思えたのです。そこで、マルチスレッド、もしくはそうじゃなくてもBackgroundWorkerのインスタンスが挿げ代わったりした時に、CancellationPending というフラグを取り合うという問題が発生する可能性があることに注意すべきかと思ったのです。要するにキャンセルが間に合わないのはあり得る普通の状態であり、競合状態ではないと考えたのですが、こう考えること自体がおかしいかどうかということが焦点のように思います。

    ただ、処理がスタートして、キャンセルリクエストがあって、処理が完了して、その最後にもう一度CancellationPending をチェックしてe.Cancelをtrueに設定したら整合性が狂いますが、これも競合ではなくて、処理の後に処理を行うかの前提となっているフラグをチェックするというバグなわけです。


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/


    2013年4月1日 6:33
    モデレータ
  • 翻訳ミスはそのとおりだと思います。
    キャンセル要求によりCancellationPendingがtrueになったのを、見落とす場合がある、というのがもともとの意味ですね。

    競合というのは、タイミングにより結果どうなるかが予測できない、タイミングによって変わるという状況を言うものであって、必ずしもそれがバグや不具合、実害があるものとは限りません。
    競合は発生するが、どの結果になっても困らない場合もあり、その場合は問題ありません。

    まだ終了していないタイミングでキャンセル要求を出したにも関わらず、結果はキャンセルではなく完了だったというのは、処理タイミングの競合で、結果が単純に予測される状態ではなくなっているわけです(というか、結果がどちらになるか予測できないわけです)。

    実際にはどちらになっても問題ないとしても、競合自体は発生しているわけです。

    2013年4月1日 8:35
  • おっしゃられていることは理解できますし、今回のケースに競合という言葉を使うことが誤りではないとも思いますが、やはり、私は競合という言葉を使うことは、今回のケースでは避けると思います。race conditionという言葉であれば使うと思います。処理の競合というより、処理が噛み合わないという感覚に近いからです。

    結局、競合という状態をどう定義するかだと思うのですが、なちゃさんが言われているように「競合というのは、タイミングにより結果どうなるかが予測できない、タイミングによって変わるという状況」では、競合を説明する上で不足していると思うのです。例えば、ホテルを予約する際に空室があることを確認した後、ホテルを予約しようとして予約ボタンを押したら満室で予約できないと表示された時、人同士の間では競合が発生していますが、プログラム上では競合が発生していないと思うのです。予約ボタンを押すまで結果が予測できないにもかかわらずです。
    ですから、今回のケースでも、人の操作とDoWorkイベントハンドラの処理とは競合していると思うのですが(人のキャンセル操作が間に合うか間に合わないかの競合)、やはり、プログラム的には競合していないと思うのです(プログラムはキャンセルが間に合えば間に合った処理をし、間に合わなければ間に合わない処理をするだけですから)。
    よって、マルチスレッドにおける競合とは異質のものであり、MSDNがマルチスレッドの例を挙げているのは、ミスリードだと私は思うのです。事実、マルチスレッドの対策が今回のケースの対策に当てはまらないと思います。

    繰り返しになりますが、今回のケースで競合という言葉を使うこと自体は否定しません。私に変な拘りがあることも承知していますし、先に述べた通り、人の操作とDoWorkイベントハンドラの処理は競合していると思うからです。

    #まぁ、本当に私の変な拘りかもしれませんね・・・


    ★良い回答には回答済みマークを付けよう! わんくま同盟 MVP - Visual C# http://d.hatena.ne.jp/trapemiya/

    2013年4月2日 1:50
    モデレータ
  • さてどうしたものかさん、こんにちは。皆様からさまざまな意見を頂いてますが、その後いかがでしょうか?

    返信下さった皆さんの投稿が参考になったと思われますので、勝手ながら私の方で回答マークを付けさせて頂きました。

    参考になった投稿には、質問者が回答マークを付けることができます。この回答マークは、さてどうしたものかさんが後から外すことも可能です。
    他の方が後から閲覧した際参考にして頂くためにも、ぜひ回答をマークして頂くようお勧めいたします。


    ひらぽん http://d.hatena.ne.jp/hilapon/

    2013年4月4日 6:26
    モデレータ