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

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

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

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

Q&A

解決済

2回答

898閲覧

非同期処理の練習、Task.Run後におかしな値を返す。

Sado

総合スコア89

C#

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

0グッド

0クリップ

投稿2022/08/08 10:48

前提

c#のtaskと非同期処理の理解のため、別スレッドに処理を投げるコードを書いてみたのですが意図した結果が得られません。
各ポイントごとにログを出してみたところ、Task.Run()直後からおかしな値を返す挙動をします。

発生している問題

おおまかなログの発生順は意図した通り出力できているのですが、返してくる値がすべて「5」なのが問題です。
私としては「0,1,2,3,4」と出力して欲しいのですが、加算式にもしていないのに何故かループ回数と同じ「5」が5回出力されます。
この「5」に置き換わるタイミングが、Task.Runを使用している箇所HeavyProc()からです。
ここまでの検証で、なぜ「5」が出力されるのか全く見当がつかない状態です。

また、非同期処理なので多少ログの実行順にばらつきが生まれるのは理解できるのですが、色分けの処理がうまく実行されず、緑色から解除されない事が稀にあります。
色を変えてログ処理するメソッドは、すぐに元の色に戻すように書いたつもりですが「非同期処理」においてはこれでは不十分なのでしょうか。
色変えのタイミングに関してはまだ自分の中でも検証が不十分ですので、こういった問題が発生することだけ先に示しておきます。

dotnet run [Main] start... [RunHeavyProcs] i = 0 [RunHeavyProcs] i = 1 [RunHeavyProcs] i = 2 [RunHeavyProcs] i = 3 [RunHeavyProcs] i = 4 [RunHeavyProcs] waiting all-task [HeavyProc] 5 Start... [Main] Finished. [HeavyProc] 5 Start... [HeavyProc] 5 Start... [HeavyProc] 5 Start... [HeavyProc] 5 Start... [RunHeavyProcs] all-task is completed [RunHeavyProcs] r = 5 -- Passed_HeavyProc [RunHeavyProcs] r = 5 -- Passed_HeavyProc [RunHeavyProcs] r = 5 -- Passed_HeavyProc [RunHeavyProcs] r = 5 -- Passed_HeavyProc [RunHeavyProcs] r = 5 -- Passed_HeavyProc

該当のソースコード

cs

1using System; 2using System.Collections.Generic; 3using System.Threading; 4using System.Threading.Tasks; 5 6/* 7 非同期処理の練習 8 https://qiita.com/inew/items/0126270bca99883605de 9 */ 10namespace SushiTabetai 11{ 12 internal static class Program 13 { 14 // Entry-point 15 static void Main(string[] args) 16 { 17 Console.BackgroundColor = ConsoleColor.Black; 18 Console.ForegroundColor = ConsoleColor.White; 19 WriteLineColor("[Main] start...", ConsoleColor.Yellow); 20 21 /* async voidを使うと警告がでる場合があるので, 値の破棄"_"を使うと 22 回避できるかもしれない. 23 使用する場合は注意. */ 24 _ = RunHeavyProcs(); 25 26 WriteLineColor("[Main] Finished.", ConsoleColor.Yellow); 27 Console.ReadKey(); 28 } 29 30 // 重い処理 31 static string HeavyProc(int x) 32 //static async Task<string> HeavyProc(int x) 33 { 34 WriteLineColor($"[HeavyProc] {x} Start...", ConsoleColor.DarkGreen); 35 Thread.Sleep(2000); 36 //await Task.Delay(2000); 37 return $"{x} -- Passed_HeavyProc"; 38 } 39 40 static async Task RunHeavyProcs() 41 { 42 //Console.WriteLine("[RunHeavyProcs] ..."); 43 44 var taskList = new List<Task<string>>(); 45 for (int i = 0; i < 5; i++) 46 { 47 Console.WriteLine("[RunHeavyProcs] i = {0}", i); 48 var _task = Task.Run(() => HeavyProc(i)); 49 taskList.Add(_task); 50 } 51 52 Console.WriteLine("[RunHeavyProcs] waiting all-task"); 53 await Task.WhenAll(taskList); 54 Console.WriteLine("[RunHeavyProcs] all-task is completed"); 55 for (int i = 0; i < taskList.Count; i++) 56 { 57 Console.WriteLine("[RunHeavyProcs] r = {0}", taskList[i].Result); 58 } 59 } 60 61 static void WriteLineColor(string str, ConsoleColor col) 62 { 63 var tmpCol = Console.ForegroundColor; 64 Console.ForegroundColor = col; 65 Console.WriteLine(str); 66 Console.ForegroundColor = tmpCol; 67 } 68 } 69}

長文失礼しました。上記不明点についてご教示いただけたら幸いです。
よろしくお願いいたします。

環境

Visual Studio Code (最新) + C#プラグイン
.NET SDK 6.0.101
(vscode内ターミナルでdotnetコマンドで実行)

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

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

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

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

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

guest

回答2

0

ベストアンサー

c#

1for (int i = 0; i < 5; i++) 2 { 3 Console.WriteLine("[RunHeavyProcs] i = {0}", i); 4 var x = i; 5 var _task = Task.Run(() => HeavyProc(x)); 6 taskList.Add(_task); 7 }

投稿2022/08/08 10:55

ozwk

総合スコア13521

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

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

Sado

2022/08/08 11:02

回答ありがとうございます。 正しく動作することが確認できましたが、なぜ間にローカル変数を挟まないといけないのかがわかりません。 値渡しされているならなんら問題ないと考えてしまうのですが、間に1つ挟む理由とはなんでしょうか?
ozwk

2022/08/08 11:33

関数へは確かに値渡しなのですが、ラムダ式で外部変数として参照しています
Sado

2022/08/08 11:58

お二方、回答ありがとうございます。 なるほど、今回の不可解さはラムダ式……匿名関数の理解不足が招いたものだったんですね。 ラムダ式中に渡す変数と、その変数をキャプチャする為に匿名関数のインスタンスが生成されるタイミングが制御できていなかったと。 勉強になります。
guest

0

なぜ「5」が出力されるのか全く見当がつかない状態です。

for ループを抜けてから非同期処理が開始されていて、その時点の変数 i の値は質問のコード例では 5 だからでしょう。

コンソール出力を見ると、質問のコードの Console.WriteLine("[RunHeavyProcs] waiting all-task"); は for ループを抜けてから実行されており、その後 HeavyProc が非同期に実行されコンソールに [HeavyProc] 5 Start... と出力されています。

・・・と私が言うだけでは説得力がないかもしれないので、@IT の記事を紹介しておきます。下の方の「参考:Parallelクラスを使わずに並列実行する」のセクションのコードの下の注記を見てください。

ループをParallelクラスで並列処理にするには?[C#/VB]
https://atmarkit.itmedia.co.jp/ait/articles/1706/21/news021.html

投稿2022/08/09 00:37

編集2022/08/09 00:48
退会済みユーザー

退会済みユーザー

総合スコア0

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

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

Sado

2022/08/12 11:57

回答ありがとうございます。 BA選択済みでしたが、補足説明とても助かります。 BAの回答にあった「匿名関数とインスタンスの関係」で粗方納得したつもりでいましたが、「5」について少しもやもやしていたところでした。 (ローカル変数iは破棄されるんなら取得できる値は0~4まででは???と少し混乱していました) 実は、出力結果は「5,5,5,5,5」だけではなく、「3,3,5,5,5」が出たりややランダムな値を返してきます。 このあたりはforから発火された別スレッドの実行タイミングによるものなのでしょうけれど、ローカル変数iが匿名インスタンスにキャプチャされ、破棄されるか否か(4だったり5だったり)というタイミングで別スレで処理されてしまったのですね。 パラレルの例を理解するのは難しいので、ひとまずはループ中の並列処理について警戒していきたいと思います。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問