投稿者 魔界の仮面弁士  (社会人) 投稿日時 2009/2/22 22:28:44
> 何をもって原則として使用禁止なんでしょうか?
すでにかずきさんが答えておられますが、System.Windows.Forms.Control の派生クラス(いわゆる「コントロール」)のメンバは、UIスレッド以外からの操作に対応していないのです。
BackgroundWorker のように、System.ComponentModel.Component の派生クラス(いわゆる「コンポーネント」)であれば、別スレッドから呼び出しても構いませんが、メッセージベースの処理を行うようなコンポーネント(たとえば、System.Windows.Forms.Timer など)は、UIスレッドからしか使えません。


> また、ご指導でおっしゃっている事を考えるとこの上記の例の場合で、
> _DoWorkでなく且つcombo1-3の値を引き継ぐ方法があるのだと受け取れました。
そうですね。ビットレート等を、変換中に変更させる必要が無いのであれば、
e.Argument で開始パラメータとして受け取っておけば対応できるかと思います。


> 同結果で変換できました。
マルチスレッド処理では、同時実行制御が重要になります。

たとえば、
 Private money As Integer = 1000   '所持金1000円
というフィールド変数があったとします。

そしてそれを、処理AとBが、それぞれ 1回ずつ
 money += 1   '所持金に 1 円加える。
というコードを実行することを想像してみてください。
AとBとで計 2 回加算されますから、処理結果は 1002 円になりますよね。


ところが、これをマルチスレッドで処理する場合は、そうなるとは限りません。
この処理を、(ほぼ)同時に実行すると、微妙なタイミングの差で異なる金額になってしまう事があります。

《パターン1》
(1) Aが、変数money から値を読み取る。(Aのメモリ値=1000、money=1000)
(2) Aが、読みとった値に +1 を加える。(Aのメモリ値=1001、money=1000)
(3) Bが、変数money から値を読み取る。(Bのメモリ値=1000、money=1000)
(4) Bが、読みとった値に +1 を加える。(Bのメモリ値=1001、money=1000)
(5) Aが、加算した値を変数に書き戻す。(Aのメモリ値=1001、money=1001)
(6) Bが、加算した値を変数に書き戻す。(Bのメモリ値=1001、money=1001)
(7) 変数 money の最終値は 1001 円。

《パターン2》
(1) Aが、変数money から値を読み取る。(Aのメモリ値=1000、money=1000)
(2) Aが、読みとった値に +1 を加える。(Aのメモリ値=1001、money=1000)
(3) Aが、加算した値を変数に書き戻す。(Aのメモリ値=1001、money=1001)
(4) Bが、変数money から値を読み取る。(Bのメモリ値=1001、money=1001)
(5) Bが、読みとった値に +1 を加える。(Bのメモリ値=1002、money=1001)
(6) Bが、加算した値を変数に書き戻す。(Bのメモリ値=1002、money=1002)
(7) 変数 money の最終値は 1002 円。


単純な Integer 型の変数でさえこの始末です。
しかもこれは、とてもシビアなタイミングで発生する現象であり、『ほとんどの場合は上手くいくが、ごく稀に失敗する』という事態になりえます。

時には、1000万回実行してようやく再現するかも知れませんし、あるいは最初の数回で再現するかも知れません。
このようなバグは、一度作り込んでしまうと、後から発見・修正する事が非常に難しいのです。そのため、最初から正しい設計をしなくてはなりません。
(.NET 2.0 以降では、異なるスレッドからの操作ができないようチェックする仕組みが登載されていますが、完璧に検出できるわけではありません)

そしてスレッドが安全に実行されるようにするには、たとえば
 (案1)ローカル変数以外は使わない。(変数を異なるスレッドで共有しない)
 (案2)非ローカル変数を利用するが、1つの変数を1つのスレッドだけで利用する。
 (案3)1つの変数を複数の非同期スレッドから利用するが、変数のアクセス部分だけは同期化させ、共有データが同時にアクセスさせれないよう排他制御する。
といった対策が必要とされるというわけです。


そしてもっとも簡単なのは 案1 であり、私が推奨していた方法がこれにあたります。