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

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

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

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

Q&A

解決済

4回答

7081閲覧

Task中のループについて

yaneuranoneko

総合スコア11

C#

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

0グッド

0クリップ

投稿2018/02/05 06:44

前提・実現したいこと

2つのフォルダーがあってその中のcsvファイルを取得し、
そのcsvファイルに対して処理をしたいです。
タスクを利用している理由としてはこれらの処理を
フォルダーごとに並列処理がしたいのです。

発生している問題・エラーメッセージ

9行目のTask.Run()に入ったところでエラーになります。 デバッグで確認するとi=2(folders.Length)になっていて for文ではi<folders.Lengthなので0と1にしかならないと思ってます。 エラーメッセージ IndexOutOfRangeException インデックスが配列の境界外です

該当のソースコード

C# ソースコード string[] folders = new string[2], files = new string[100]; private Task Run_Task() { var tasks = new List<Task>(); for (int i = 0; i < folders.Length; i++) { var task = Task.Run(() => { files = Directory.GetFiles(folders[i], "*.csv"); //取得したcsvファイルに対する処理 }); tasks.Add(task); } return Task.WhenAll(tasks); }

試したこと

補足情報(FW/ツールのバージョンなど)

VisualStudio2017

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

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

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

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

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

guest

回答4

0

ベストアンサー

こんにちは。
既に回答が付いているので、今回問題になったキャプチャの仕組みについて説明してみます。

まず答えを書いてしまうと、以下のやり方で解決します。

csharp

1for (int i = 0; i < folders.Length; i++) 2{ 3 var x = i; // for文中のスコープ内に変数を確保 4 var task = Task.Run(() => 5 { 6 // files変数は内部で宣言すること! 7 var files = Directory.GetFiles(folders[x], "*.csv"); // xを使用 8 //取得したcsvファイルに対する処理 9 }); 10 tasks.Add(task); 11}

ラムダ式による外部変数のキャプチャは、その変数のスコープによって共有範囲が定まるので、for文の1繰り返し内にのみ生存する変数をキャプチャすることで、必ず正しいindexの値がラムダ式内で利用できるようになります。iはfor文によって書き換えられていますが、xはコード内で書き換えられていないからです。


既にYamakawaJunichiさんが回答でサジェストしていますが、このコードではforeachを使ったほうが圧倒的に楽です。

csharp

1foreach (var folder in folders) 2{ 3 var task = Task.Run(() => 4 { 5 var files = Directory.GetFiles(folder, "*.csv"); 6 //取得したcsvファイルに対する処理 7 }); 8 tasks.Add(task); 9}

処理を行うのに不要なiとかi++とかfolders.Lengthとかのノイズをコードから消し去ることができます。

投稿2018/02/07 03:54

tamoto

総合スコア4103

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

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

yaneuranoneko

2018/02/08 01:00

回答ありがとうございます。 foreachだと確かに可読性も上がる気がします。 変数はfor文内やTask.Runの中で定義するというのは気が付かなかったので助かりました! ありがとうございます。
guest

0

Task内の処理とfor文を回す処理が別スレッドなので、i++してi < folders.Lengthを判定することと、files = Directory.GetFiles(folders[i], "*.csv")を実行することの順序は保障されません。

よって、i=1のとき
・[メインスレッド]Task.Runでワーカースレッドを立てる
・[メインスレッド]i++し、iが2になる
・[メインスレッド]i < folders.Lengthを判定しfalseなのでループを抜ける
・[ワーカースレッド]files = Directory.GetFiles(folders[i], "*.csv")を実行する

となるとfolders[2]にアクセスしてしまいます。

for文をforeach文にするのが良いと思います。

追記
files = Directory.GetFiles(folders[i], "*.csv");ですが、同じfilesに複数回結果を代入しているので、複数回Task.Runしても最後に完了したデータしか取れません。
string[][]やList<string[]>に入れるなどすべきだと思います。

投稿2018/02/05 07:39

編集2018/02/05 07:59
YamakawaJunichi

総合スコア630

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

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

yaneuranoneko

2018/02/05 08:09

ありがとうございます。 foreach文は試してみます! filesのところは間違えてました。
guest

0

こんにちは。

その振る舞いからすると、i の記憶領域は1つしかなく、Run_Task()関数、および、起動した全てのタスクで共有されているのでは?
各タスクを起動しても実行が直ぐに始まるわけではありませんから、その間にRun_Task()関数で i の値が2に更新されているのだろうと思います。

私自身はC#のラムダ式でパラメータを使ったことはないですが、可能な筈です。
int型は値型ですからiをパラメータで渡せば、その時点でコピーされますので意図通りに動作するだろうと思います。

投稿2018/02/05 07:27

編集2018/02/05 07:28
Chironian

総合スコア23272

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

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

yaneuranoneko

2018/02/05 07:56

var task = Task.Run(() => { int n = i; files = Directory.GetFiles(folders[n], "*.csv"); //取得したcsvファイルに対する処理 }); ここの3行目のように追加するという意味でよろしいですか?
masa_n

2018/02/05 08:19

変数のキャプチャといわれる仕組みですね。Task.Run は実行する処理をデリゲートとして生成しますが、その外側で宣言した変数をデリゲート内で参照することができます。その変数が int のように値型である場合、デリゲートを生成した時点の値がキャプチャされます。
yaneuranoneko

2018/02/05 08:20

なんとなくわかったような気がします。 もうちょっと色々試してみます!
yaneuranoneko

2018/02/05 08:22

>masa_nさん ありがとうございます。 変数のキャプチャは聞いたことがなかったです。 そこらへんも含めてもう一度勉強しなおします。
Chironian

2018/02/05 09:29

現在のコードがキャプチャです。パラメータは普通に関数を呼び出す時に渡すものです。 ラムダ式は、このあたりを理解していないと使うのはかなり苦労します。 リンク先のように別途関数を定義して、それをタスクで実行した方が判りやすいだろうと思いますよ。
guest

0

添字は0~1ですが、配列の数(folders.Length)は2だからです。

追記

初歩的な勘違いです。お恥ずかしい。

投稿2018/02/05 07:02

編集2018/02/05 07:49
sazi

総合スコア25173

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

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

yaneuranoneko

2018/02/05 07:05

Task.Run()に行くときに配列の数だけ渡される仕組みなんかも教えていただければ幸いです。 また、同様の処理をしたい場合はどこを変更すれば期待する処理を行えるようになりますか。
sazi

2018/02/05 07:13

添字に+1したものとの比較にすれば良いだけだと思いますが。 for (int i = 0; i+1 < folders.Length; i++)
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問