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

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

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

Re[6]: Parallel.For と Invoke メソッドの使い方


(過去ログ 92 を表示中)

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

■55213 / inTopicNo.1)  Parallel.For と Invoke メソッドの使い方
  
□投稿者/ kait (1回)-(2010/11/22(Mon) 06:20:38)

分類:[.NET 全般] 

お世話になります。

マルチスレッドを使用する際の Invoke メソッドの使い方についてお尋ねいたします。

Windows フォームアプリケーションに、ボタンを 1 つ貼り、ラベルを 1 つ貼りました。
そして下のコードを記述しました。

private void button1_Click(object sender, EventArgs e)
{
    this.label1.Text = "0";

    for (int i = 0; i < 10; i++)
    {
        System.Threading.Tasks.Parallel.For(0, 100, n =>
        {
            Invoke(new MethodInvoker(A));
        });
    }

    MessageBox.Show("owata");
}

private void A()
{
    int value = int.Parse(this.label1.Text);
    value++;
    this.label1.Text = value.ToString();
    Refresh();
}

この処理をマルチスレッドでおこなうことに意味はありませんが、
ラベルの数字を 1 増やすということを Parallel.For で 100 回繰り返します。

また、全体を 10 回、普通の for で繰り返します。

このとき、99 や 198 といった、Parallel.For の終了地点だと思われる場所で
固まってしまいます。

固まった時の CPU 使用率は 0% です。また、固まる場所は不定です。

Parallel.For と Invoke メソッドの使い方に問題があるのでしょうか?
お気づきの点がございましたら、お教えくださると助かります。

- Visual C# 2010 Express
- Windows 7 x64 と Windows XP の両方で現象が出ます。
- Core i7 (論理 8 CPU) と Athlon 64 (シングルコア) の両方で現象が出ます。
- プロジェクトのビルドモードは x86 で、.NET Framework 4 を使用しています。

よろしくお願いいたします。

引用返信 編集キー/
■55214 / inTopicNo.2)  Re[1]: Parallel.For と Invoke メソッドの使い方
□投稿者/ shu (190回)-(2010/11/22(Mon) 09:15:11)
No55213 (kait さん) に返信

デッドロックが発生しているのではないでしょうか?
Aの中をlock文を使用して同期を取る必要があるかと思います。
引用返信 編集キー/
■55216 / inTopicNo.3)  Re[2]: Parallel.For と Invoke メソッドの使い方
□投稿者/ kait (2回)-(2010/11/22(Mon) 10:13:43)
No55214 (shu さん) に返信
> ■No55213 (kait さん) に返信
> 
> デッドロックが発生しているのではないでしょうか?
> Aの中をlock文を使用して同期を取る必要があるかと思います。

shu さん、ありがとうございます。
Invoke というメソッドはユーザーインターフェーススレッドで実行されると
記述されていたので、その中での同期は必要ないと認識していました。

試しに lock の処理を入れてみました。
このような形でよろしいでしょうか…。


private object lockObject = new object(); // ←ロック用オブジェクト

private void button1_Click(object sender, EventArgs e)
{
    this.label1.Text = "0";

    for (int i = 0; i < 10; i++)
    {
        System.Threading.Tasks.Parallel.For(0, 100, n =>
        {
            Invoke(new MethodInvoker(A));
        });
    }

    MessageBox.Show("owata");
}

private void A()
{
    lock (this.lockObject) // ← ロック
    {
        int value = int.Parse(this.label1.Text);
        value++;
        this.label1.Text = value.ToString();
        Refresh();
    }
}

結果、変わりありませんでした。
停止する場所は不定で、最後まで完走することもあります。

さらにお気づきの点がございましたら、ご指摘いただけますと幸いです。
よろしくお願いいたします。

引用返信 編集キー/
■55217 / inTopicNo.4)  Re[3]: Parallel.For と Invoke メソッドの使い方
□投稿者/ shu (191回)-(2010/11/22(Mon) 10:53:22)
No55216 (kait さん) に返信

label1が何かとぶつかっているんですかね。
private変数で試してみてはどうでしょう?
引用返信 編集キー/
■55218 / inTopicNo.5)  Re[3]: Parallel.For と Invoke メソッドの使い方
□投稿者/ なちゃ (478回)-(2010/11/22(Mon) 10:57:52)
とりあえず、固まった時点でデバッガで停止して、
各スレッドの状態やどこで止まっているかなどを
確認してみてはどうでしょう。

引用返信 編集キー/
■55227 / inTopicNo.6)  Re[4]: Parallel.For と Invoke メソッドの使い方
□投稿者/ Azulean (646回)-(2010/11/22(Mon) 19:54:19)
No55218 (なちゃ さん) に返信
> とりあえず、固まった時点でデバッガで停止して、
> 各スレッドの状態やどこで止まっているかなどを
> 確認してみてはどうでしょう。
再現させてみましたが、Invoke メソッドで止まっているようですね。
あまりに呼びすぎてあふれたんだろうか。

個人的にはこれの原因を突き止めるよりは、「そんなに呼ばないこと」で済ませておいた方が良いような気はします。
時間をかける割にはきちんとした見解にまとまらない可能性があるので。

(参考)Invoke メソッドは内部的に PostMessage を利用します。


No55217 (shu さん) に返信
> label1が何かとぶつかっているんですかね。
> private変数で試してみてはどうでしょう?
Invoke メソッドを呼んでいる時点で、A メソッドを実行するのはメインスレッドになります。
今回のメソッドの場合、lock する必要はありませんし、”ぶつかる”ということも考慮しなくて良い内容だと思います。
引用返信 編集キー/
■55229 / inTopicNo.7)  Re[4]: Parallel.For と Invoke メソッドの使い方
□投稿者/ kait (3回)-(2010/11/22(Mon) 22:46:54)
shu さん、なちゃさん、Azulean さん、ありがとうございます。

> 再現させてみましたが、Invoke メソッドで止まっているようですね。
> あまりに呼びすぎてあふれたんだろうか。
>
> 個人的にはこれの原因を突き止めるよりは、「そんなに呼ばないこと」で
> 済ませておいた方が良いような気はします。
> 時間をかける割にはきちんとした見解にまとまらない可能性があるので。

そ…そうですか(´Д`;)

実は元々のプログラムはもっと複雑でして、徐々に問題を単純化していったところ、
このあたりが問題だということがわかったというものです。

元々のプログラムでは、数百個のデータ配列があり、
このそれぞれのデータを Parallel.For のマルチスレッドで処理し、
進捗状況をプログレスバーとラベルで表示しようとしていました。
しかし何度やっても処理が終わらず、固まる場所もいろいろで…。

普通、このような処理は Parallel.For でおこなわない、というより、
マルチスレッドでおこなわないものなのでしょうか…?
よくあるシナリオのような気がしますが…。



…と悩んでいたところ、ヘルプにこのような記述を見つけました。

データとタスクの並列化における注意点
http://msdn.microsoft.com/ja-jp/library/dd997392.aspx

この中の、「UI スレッドでの並列ループの実行は避ける」に以下の記述があります。

> UI スレッドで並列ループを実行する場合は、そのループ内から UI コントロールを
> 更新しないように注意する必要があります。UI スレッドで実行されている
> 並列ループ内から UI コントロールを更新しようとすると、UI 更新の呼び出し方法に
> よっては、状態の破損、例外、更新の遅れ、さらにはデッドロックが発生する場合があります。

(´Д`;)なんてこったい…。まさにこのデッドロックの状態だったわけですね…。

さらに下にコード例が載っていますが、Parallel.For ループを、
ひとつのワーカースレッド内で実行するように記述すればよいようです。

コード例と同様に Task.Factory.StartNew メソッドで Parallel.For ループをくくったところ、
途中で停止するということはなくなりました!!

具体的には、

System.Threading.Tasks.Parallel.For(0, 100, n =>
{
    Invoke(new MethodInvoker(A));
});

を、

System.Threading.Tasks.Task.Factory.StartNew(() =>
{
    System.Threading.Tasks.Parallel.For(0, 100, n =>
    {
        Invoke(new MethodInvoker(A));
    });
});

に変更しました。

しかし Task.Factory.StartNew でくくってしまうと、そのスレッドの待ち合わせを
手動で記述しなければならないのですが、それはまた考えます。

マルチスレッドの難しいところは、コンパイルができたからといっても、
〜してはならない、という作法が多数あり、安心できない点ですよね。

ここで解決とさせていただきたいと思います。
みなさまのヒントのおかげです。ありがとうございました。

解決済み
引用返信 編集キー/
■55239 / inTopicNo.8)  Re[5]: Parallel.For と Invoke メソッドの使い方
□投稿者/ 渋木宏明 (47回)-(2010/11/23(Tue) 11:16:49)
渋木宏明 さんの Web サイト
>Invoke(new MethodInvoker(A));

なぜに MethodInvokder ?

> 再現させてみましたが、Invoke メソッドで止まっているようですね。
> あまりに呼びすぎてあふれたんだろうか。
>
> 個人的にはこれの原因を突き止めるよりは、「そんなに呼ばないこと」で済ませておいた方が良いような気はします。
> 時間をかける割にはきちんとした見解にまとまらない可能性があるので。

の可能性はあるかなぁ。

> (参考)Invoke メソッドは内部的に PostMessage を利用します。

SendMessage() じゃなくて、PostMessage() でがちゃがちゃやってるんでしたっけ?

引用返信 編集キー/
■55240 / inTopicNo.9)  Re[6]: Parallel.For と Invoke メソッドの使い方
□投稿者/ Azulean (650回)-(2010/11/23(Tue) 11:30:21)
No55239 (渋木宏明 さん) に返信
> SendMessage() じゃなくて、PostMessage() でがちゃがちゃやってるんでしたっけ?
はい。VS2010 で再現させてデバッガで実行位置を確認した際にソースコードで確認しています。
Control.Invoke → Control.MarshaledInvoke と呼ばれていますが、Control.MarshaledInvoke の中で PostMessage を実行しています。
この後、Control.WaitForWaitHandle で待ち合わせをしていますが、ここから脱出しないのが止まる現象です。
引用返信 編集キー/
■55248 / inTopicNo.10)  Re[5]: Parallel.For と Invoke メソッドの使い方
□投稿者/ Hongliang (739回)-(2010/11/23(Tue) 13:56:16)
Parallel.For はすべての非同期操作が完了まで待機するわけですから、そこで Invoke とかやっちゃうとそりゃデッドロックも発生するってもんでは。
メインスレッドが Parallel.For で待機してるからメッセージループが動かない、Invoke で処理完了待ちするけどメッセージループが動かないからいつまでたっても Invoke から返らない、Invoke が完了しないから For も完了しない。

> なぜに MethodInvoker ?
WinForm の Invoke において直接呼んでくれるデリゲートだからでは?
Action とかは DynamicInvoke 経由の呼び出しになってしまいますから。
引用返信 編集キー/
■55252 / inTopicNo.11)  Re[6]: Parallel.For と Invoke メソッドの使い方
□投稿者/ 渋木宏明 (49回)-(2010/11/23(Tue) 18:25:48)
渋木宏明 さんの Web サイト
> Parallel.For はすべての非同期操作が完了まで待機するわけですから、そこで Invoke とかやっちゃうとそりゃデッドロックも発生するってもんでは。
> メインスレッドが Parallel.For で待機してるからメッセージループが動かない、Invoke で処理完了待ちするけどメッセージループが動かないからいつまでたっても Invoke から返らない、Invoke が完了しないから For も完了しない。

それだ (^^;

>>なぜに MethodInvoker ?
> WinForm の Invoke において直接呼んでくれるデリゲートだからでは?
> Action とかは DynamicInvoke 経由の呼び出しになってしまいますから。

意識した記憶ないなぁ。
てか、そもそも Control.Invoke() 自体年単位で呼んだこと無いかも。
.NET 1.x/2.0 時代には結構使ってたんだけどなぁ。

引用返信 編集キー/
■55259 / inTopicNo.12)  Re[7]: Parallel.For と Invoke メソッドの使い方
□投稿者/ shu (195回)-(2010/11/23(Tue) 21:19:12)
No55240 (Azulean さん) に返信
> この後、Control.WaitForWaitHandle で待ち合わせをしていますが、ここから脱出しないのが止まる現象です。
なるほど^^Invoke自体がデッドロックの原因になっているのですね。
引用返信 編集キー/


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

このトピックに書きこむ

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

管理者用

- Child Tree -