質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Q&A

解決済

1回答

1657閲覧

C# Parallel.For、変数のスコープについて教えてください

papi1204

総合スコア8

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

0グッド

1クリップ

投稿2019/08/03 06:11

編集2019/08/03 13:02

前提

プログラミング初心者です。
visual studio2019でParallel.Forを用いた画像の二値化処理プログラムを作成しました。
このプログラムで下記三点の処理時間を計測します。

・Parallel.Forのみを関数にした処理
・同一関数内で全て行う処理
・Parallel.Forを使わない処理

上手くプログラムが動作するのが確認出来ると後述の疑問点が生まれました。

疑問点

・Parallel.Forを関数にすると何故lockをしなくても処理できるのか
・関数void型で戻り値を使用してないのになぜbyte[] baimageの値は
変更されて正常に二値化されてる画像が表示されるのか

該当のソースコード

C#

1//関数を新規に作り、Parallel.Forを実施 2private void Button1_Click(object sender, EventArgs e) 3{ 4  var ofd = new OpenFileDialog(); 5  if (ofd.ShowDialog() != DialogResult.O 6    return; 7 8  var sw = new Stopwatch(); 9  sw.Start(); 10 11  Bitmap bmp = new Bitmap(ofd.FileName); 12  BitmapData bmpDate = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), 13                ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); 14  byte[] baimage = new byte[bmp.Width * bmp.Height * 4]; 15  Marshal.Copy(bmpDate.Scan0, baimage, 0, baimage.Length); 16 17  parallel(bmp.Height, bmp.Width, baimage); 18 19  Marshal.Copy(baimage, 0, bmpDate.Scan0, baimage.Length); 20  bmp.UnlockBits(bmpDate); 21  sw.Stop(); 22  label1.Text = sw.ElapsedMilliseconds.ToString(); 23 24  pictureBox1.SizeMode = PictureBoxSizeMode.Zoom; 25  pictureBox1.Image = bmp; 26} 27 28private void parallel(int bmp_height, int bmp_width, byte[] image) 29{ 30  Parallel.For(0, bmp_height, y => 31  { 32    for (int x = 0; x < bmp_width; x++) 33    { 34      var bytePos = y * bmp_width * 4 + x * 4; 35      int gray = (int)(0.299 * image[bytePos + 2] + 0.587 * image[bytePos + 1] + 0.114 * image[bytePos]); 36 37      if (gray > 127) 38 { 39   // 閾値を超えた場合、白 40   image[bytePos] = 0xFF; 41 image[bytePos + 1] = 0xFF; 42 image[bytePos + 2] = 0xFF; 43      } 44 else 45 { 46   // 閾値以下の場合、黒 47   image[bytePos] = 0x0; 48 image[bytePos + 1] = 0x0; 49 image[bytePos + 2] = 0x0; 50 } 51 } 52  }); 53} 54 55 56//同一関数内でParallel.Forを実施 57private void Button2_Click(object sender, EventArgs e) 58{ 59  var ofd = new OpenFileDialog(); 60  if (ofd.ShowDialog() != DialogResult.OK) 61    return; 62 63  var sw = new Stopwatch(); 64  sw.Start(); 65 66  Bitmap bmp = new Bitmap(ofd.FileName); 67  BitmapData bmpDate = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), 68                 ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); 69  byte[] baimage = new byte[bmp.Width * bmp.Height * 4]; 70  Marshal.Copy(bmpDate.Scan0, baimage, 0, baimage.Length); 71 72  Parallel.For(0, bmp.Height, y => 73  { 74    lock (this) for (int x = 0; x < bmp.Width; x++) 75    { 76      var bytePos = y * bmp.Width * 4 + x * 4; 77      int gray = (int)(0.299 * baimage[bytePos + 2] + 0.587 * baimage[bytePos + 1] + 0.114 * baimage[bytePos]); 78 79      if (gray > 127) 80 { 81   // 閾値を超えた場合、白 82 baimage[bytePos] = 0xFF; 83 baimage[bytePos + 1] = 0xFF; 84 baimage[bytePos + 2] = 0xFF; 85 } 86 else 87 { 88   // 閾値以下の場合、黒 89 baimage[bytePos] = 0x0; 90 baimage[bytePos + 1] = 0x0; 91 baimage[bytePos + 2] = 0x0; 92 } 93 } 94  }); 95 96  Marshal.Copy(baimage, 0, bmpDate.Scan0, baimage.Length); 97  bmp.UnlockBits(bmpDate); 98  sw.Stop(); 99  label2.Text = sw.ElapsedMilliseconds.ToString(); 100 101  pictureBox1.SizeMode = PictureBoxSizeMode.Zoom; 102  pictureBox1.Image = bmp; 103} 104 105 106//すべてFor文 107private void Button3_Click(object sender, EventArgs e) 108{ 109  var ofd = new OpenFileDialog(); 110  if (ofd.ShowDialog() != DialogResult.OK) 111    return; 112 113  var sw = new Stopwatch(); 114  sw.Start(); 115 116  Bitmap bmp = new Bitmap(ofd.FileName); 117  BitmapData bmpDate = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), 118        ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb); 119  byte[] baimage = new byte[bmp.Width * bmp.Height * 4]; 120  Marshal.Copy(bmpDate.Scan0, baimage, 0, baimage.Length); 121 122  for (int y = 0; y < bmp.Height; y++) 123  { 124    for (int x = 0; x < bmp.Width; x++) 125    { 126      var bytePos = y * bmp.Width * 4 + x * 4; 127      int gray = (int)(0.299 * baimage[bytePos + 2] + 0.587 * baimage[bytePos + 1] + 0.114 * baimage[bytePos]); 128 129      if (gray > 127) 130      { 131        // 閾値を超えた場合、白 132        baimage[bytePos] = 0xFF; 133        baimage[bytePos + 1] = 0xFF; 134        baimage[bytePos + 2] = 0xFF; 135      } 136      else 137      { 138        // 閾値以下の場合、黒 139        baimage[bytePos] = 0x0; 140        baimage[bytePos + 1] = 0x0; 141        baimage[bytePos + 2] = 0x0; 142      } 143    } 144  } 145 146  Marshal.Copy(baimage, 0, bmpDate.Scan0, baimage.Length); 147  bmp.UnlockBits(bmpDate); 148  sw.Stop(); 149  label3.Text = sw.ElapsedMilliseconds.ToString(); 150 151  pictureBox1.SizeMode = PictureBoxSizeMode.Zoom; 152  pictureBox1.Image = bmp; 153}

似たような記述のint型、計算処理プログラムを作成するも
int型だと別途関数を作成した時の値が狂ってしまいlockする必要が出てきてしまいました。

色々と調べたのですが、分からなかったので教えていただけないでしょうか。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zuishin

2019/08/03 11:06

質問の意味がよくわかりません。
guest

回答1

0

ベストアンサー

・Parallel.Forを関数にすると何故上手くいくのか

関数にするから上手くいくわけではありません。
Bitmapクラスはスレッドセーフではないため、同時アクセスを許しません。
ですので、Parallel.For内でbmp.Widthとアクセスしているのが原因となります。

C#

1var bmpHeight = bmp.Height; 2var bmpWidth = bmp.Width; 3 4Parallel.For(0, bmpHeight, y => 5{ 6 for (int x = 0; x < bmpWidth; x++) 7 { 8 var bytePos = y * bmpWidth * 4 + x * 4; 9 int gray = (int)(0.299 * baimage[bytePos + 2] + 0.587 * baimage[bytePos + 1] + 0.114 * baimage[bytePos]); 10 11 if (gray > 127) 12 { 13 // 閾値を超えた場合、白 14 baimage[bytePos] = 0xFF; 15 baimage[bytePos + 1] = 0xFF; 16 baimage[bytePos + 2] = 0xFF; 17 } 18 else 19 { 20 // 閾値以下の場合、黒 21 baimage[bytePos] = 0x0; 22 baimage[bytePos + 1] = 0x0; 23 baimage[bytePos + 2] = 0x0; 24 } 25 } 26}); 27

このように一度変数に受けてやり、その変数を使用するようにすればSystem.InvalidOperationExceptionが発生しません。

・また関数voidなのになぜ値が変更されていのか

C#で配列を引数として渡す場合、配列は参照型ですので、その配列のインスタンスが渡されることになります。
ですので、関数内でその配列の値を変更すると、同じインスタンスですから、呼び出し元の値も変化します。

似たような記述のint型、計算処理プログラムを作成するも

int型だと別途関数を作成した時の値が狂ってしまいlockする必要が出てきてしまいました。

ループ内でその変数を書き換えたりしているのではないですか?
計算に順序性が求められるのであれば、Parallel.Forは順不同ですから期待している計算結果と異なることになるでしょう。

投稿2019/08/03 13:16

編集2019/08/03 13:28
YAmaGNZ

総合スコア10242

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Zuishin

2019/08/03 13:19

参照渡しではないことだけ訂正お願いします。参照渡しは ref で渡すことで、これは単に参照型オブジェクトを普通に渡しているにすぎません。
YAmaGNZ

2019/08/03 13:28

確かにそうですね。ご指摘ありがとうございます。
papi1204

2019/08/04 14:38

返事が遅くなり申し訳ありません。 まず、関数についてですが配列が参照型という事を失念していました。 int型で試したときは配列でもなく、ループ内で値を変更していたのが原因で上手くできなかったんですね… Parallel.Forについてなんですが、型によってスレッドセーフか否かが決まっていて、 スレッドセーフじゃない型は一度変数でクッションを置いたらスレッドセーフになる という認識であってますでしょうか?
papi1204

2019/08/04 14:48

スレッドセーフを調べてきましたが僕の認識が甘かったです。 ファイルやグローバル変数等をローカル変数にしてスレッドセーフにしてたんですね。 分かりやすく説明して頂きありがとうございます。 まだ僕の認識に誤りがあれば指摘して頂けると幸いです。
YAmaGNZ

2019/08/04 22:38

スレッドセーフでないものをマルチスレッドで扱う場合は、同時にアクセスしないように制御することになります。 今回の場合は値の読み込みだけで、その値がループしても変化しない状態ですから単純に変数に受ける形ですが、そうでない場合はlockをする等の排他制御が必要となります。 また、物によってはスレッドセーフなラッパーが用意されていたり、スレッドセーフな代替クラスがあったりしますので、そういった物を使用することになります。
papi1204

2019/08/05 05:34

詳しい説明ありがとうございます。すごく助かりました!! スレッドセーフのラッパーや代替クラスについては自分で調べたいと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問