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

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

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

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

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。

Q&A

解決済

3回答

4205閲覧

c#の複数のTaskの実行順について

it9265

総合スコア14

C#

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

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。

0グッド

1クリップ

投稿2022/07/18 15:18

実現したいこと

ボタン0から10までの数字を画面に表示をさせたあと、11から20までの数字を表示させたい。

※0から10の数字を表示させる際、表示される順番は問いません。
※上記同様、11から20までの数字が表示される順番も問いません。

ソースコード

CSharp

1 private async void button1_Click(object sender, EventArgs e) 2 { 3 var task = CreateTask(0); 4 var task2 = CreateTask(11); 5 6 await Task.WhenAll(task); 7 Console.WriteLine($"0から10までを出力しました。"); 8 await Task.WhenAll(task2); 9 Console.WriteLine($"11から20までを出力しました。"); 10 } 11 12 13 private Task CreateTask(int v) 14 { 15 List<Task> tasks = new List<Task>(); 16 for (int i = v; i < v + 10; i++) 17 { 18 var task = PrintNum(i); 19 tasks.Add(task); 20 } 21 22 return Task.WhenAll(tasks); 23 } 24 25 private Task PrintNum(int num) 26 { 27 return Task.Run(() => Console.WriteLine($"{num}")); 28 }

実行結果

0
1
2
4
6
5
7
8
9
11
12
13
14
15
19
20
17
16
18
3
0から10までを出力しました。
11から20までを出力しました。

期待した結果

非同期実行ですから、以下のような出力を期待しておりました。

・0から10までの数字がランダムに出力される。
・0から10までを出力しました。と表示される。
・11から20までの数字がランダムに出力される。
・11から20までを出力しましたと表示される。

気になった点は2点です。
1.全ての非同期タスクが実行された後、末尾2行にメッセージが出力されたこと
2.末尾から3行目に『3』と表示されたこと。
本来であれば0から10までの数字が表示された後に11から20の数字が出力されるものだと考えておりました。
ひとつのタスク内で表示順がバラバラになるのは理解できるのですが、1つ目のawait Task.WhenAll(task)で全て出力された後に次のawait Task.WhenAll(task2)が実行されるものではないのでしょうか?

1つ目のタスクが完了後、次のタスクをまとめて実行するにはどのような非同期タスクの待ち方になるでしょうか??

環境

VisualStudio2017
C#
.NET Framework4.5.2

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

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

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

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

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

退会済みユーザー

退会済みユーザー

2022/07/18 22:17

コンソールアプリなのか GUI アプリなのかどっちでしょう? SynchronizationContext が違うので色々違ってくるかも (質問のように混ぜるとどうなるか不明ですが)。
it9265

2022/07/20 22:52

回答遅くなりすみません。 WinFormの環境で開発し、デバッグ用で出力ウィンドウにConsole出力しておりました。 gui環境としていただければと思います。
退会済みユーザー

退会済みユーザー

2022/07/20 23:43

> デバッグ用で出力ウィンドウにConsole出力しておりました。 結果が期待と違ったのはコンソール出力のタイミングの問題のようですね。 質問者さんが期待されたように「1つ目のawait Task.WhenAll(task)で全て出力された後に次のawait Task.WhenAll(task2)が実行される」のはその通りでしょう。それは私の回答のサンプルが示していると思います。
退会済みユーザー

退会済みユーザー

2022/07/25 00:59

質問者さん、その後無言ですが、回答に対するフィードバックを返してください。役に立った/立たなかったぐらいは返せるのでは? 役に立たなかったならどこがダメかを書くとより期待に近い回答が出てくるかも。解決したなら解決に役立った回答にベストアンサーをつけてクローズしてください。とにかく無言は NG です。
it9265

2022/07/27 15:43

失礼しました。 出張での関係で見れておらずお返事大変遅くなりました。 期待の動作が実現できることは理解できたのですが、記載いただいたプログラムと自作のプログラムのどの部分が期待結果を変えているのか?少し自分で落とし込めるまで時間がかかりそうです。
guest

回答3

0

CreateTask した時点で処理は走り始めてます。

C#

1var task = CreateTask(0); 2await Task.WhenAll(task); 3Console.WriteLine($"0から10までを出力しました。"); 4 5var task2 = CreateTask(11); 6await Task.WhenAll(task2); 7Console.WriteLine($"11から20までを出力しました。");

順次実行するならこうですね。
もしくは ManualResetEvent を使って待機するとか。

C#

1using System; 2using System.Collections.Generic; 3using System.Threading; 4using System.Threading.Tasks; 5 6class Program 7{ 8 static void Main(string[] args) { 9 DoAsync(); 10 Console.ReadLine(); 11 } 12 13 static async void DoAsync() { 14 ManualResetEvent waitEvent = new ManualResetEvent(false); 15 ManualResetEvent waitEvent2 = new ManualResetEvent(false); 16 17 var task = CreateTask(0, waitEvent); 18 var task2 = CreateTask(11, waitEvent2); 19 20 waitEvent.Set(); 21 await Task.WhenAll(task); 22 Console.WriteLine($"0から10までを出力しました。"); 23 24 waitEvent2.Set(); 25 await Task.WhenAll(task2); 26 Console.WriteLine($"11から20までを出力しました。"); 27 } 28 29 private static Task CreateTask(int v, ManualResetEvent waitEvent) { 30 List<Task> tasks = new List<Task>(); 31 for (int i = v; i < v + 10; i++) { 32 var task = PrintNum(i, waitEvent); 33 tasks.Add(task); 34 } 35 36 return Task.WhenAll(tasks); 37 } 38 39 private static Task PrintNum(int num, ManualResetEvent waitEvent) { 40 return Task.Run(() => { 41 waitEvent.WaitOne(); 42 Console.WriteLine($"{num}"); 43 }); 44 } 45}

投稿2022/07/18 15:42

編集2022/07/18 15:55
KOZ6.0

総合スコア2639

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

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

0

ベストアンサー

質問者さんのコードで順番が期待通りならないのは、Zuishin さんが y_waiwai さんの回答のコメントで述べられているようなタイミングの問題ではないかと思いますが、そのあたりは自分はよくわかってないし深く追求できないので、質問の本筋からは外れるかもしれませんが、順番が気になるならこうしてはいかがという案を書いておきます。

コンソールアプリか GUI アプリかで SynchronizationContext が違うし、質問のように混ぜるとそのあたりがどうなるかの話がややこしくなりそうなので GUI アプリの WinForms で考えます。

C#

1namespace WinFormsApp2 2{ 3 public partial class Form5 : Form 4 { 5 public Form5() 6 { 7 InitializeComponent(); 8 } 9 10 private async void button1_Click(object sender, EventArgs e) 11 { 12 string str = await TaskRunAsync(); 13 str += "\r\n========== 最初の TaskRunAsync ==========\r\n"; 14 str += await TaskRunAsync(); 15 str += "\r\n========== 次の TaskRunAsync =========="; 16 this.label1.Text = str; 17 } 18 19 private string Work(int number) 20 { 21 int id = Thread.CurrentThread.ManagedThreadId; 22 DateTime start = DateTime.Now; 23 string retunVlaue = $"n = {number}, ThreadID = {id}" + 24 $", start: {start:ss.fff}, "; 25 26 // ここで 3 秒遅延 27 Task.Delay(3000).Wait(); 28 29 DateTime end = DateTime.Now; 30 TimeSpan diff = start - end; 31 retunVlaue += $"end: {end:ss.fff}, timespan: {diff:s\\.fff}"; 32 return retunVlaue; 33 } 34 35 // タスク (この記事の例では同期メソッド Work) を 10 個 Task.Run 36 // メソッドでキューに配置する。OS がスレッドプールから適宜スレッ 37 // ドを取得してキューのタスク実行。await Task.WhenAll ですべての 38 // タスクの完了を待機する 39 private async Task<string> TaskRunAsync() 40 { 41 int id = Thread.CurrentThread.ManagedThreadId; 42 DateTime start = DateTime.Now; 43 string str = $"Main Thread ID = {id}, TaskRunAsync 開始: {start:ss.fff}"; 44 string[] stringResults = new string[10]; 45 var taskList = new List<Task>(); 46 for (int i = 0; i < 10; i++) 47 { 48 int n = i; 49 taskList.Add(Task.Run(() => stringResults[n] = Work(n))); 50 } 51 52 await Task.WhenAll(taskList); 53 54 foreach (string result in stringResults) 55 { 56 str += "\r\n" + result; 57 } 58 59 DateTime end = DateTime.Now; 60 TimeSpan diff = start - end; 61 str += $"\r\nMain Thread ID = {id}, 終了: {end:ss.fff}, 所要時間: {diff:s\\.fff}"; 62 63 return str; 64 } 65 } 66}

結果は以下のようになります。

イメージ説明

投稿2022/07/19 06:07

退会済みユーザー

退会済みユーザー

総合スコア0

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

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

it9265

2022/07/27 15:39 編集

有難うございます。 button1_click関数でそれぞれtaskを待機させて尚且つ1つ目のtaskの完了を待ってから2つ目のtaskが実行できるのですね。非常に参考になります。 ただ、自作した処理と記載いただいた部分でタスクが完了するまで待機できる/できないの違いが現れている箇所が未だ理解できず…。 TaskRunAsync関数の中でTask.Runが随時実行されている処理は自作のコードと変化ない認識なのですが、どの部分が待機において重要なのでしょうか?
退会済みユーザー

退会済みユーザー

2022/07/28 01:54 編集

> TaskRunAsync関数の中でTask.Runが随時実行されている処理は自作のコードと変化ない認識なのですが、どの部分が待機において重要なのでしょうか? 重要な違いは私の回答のサンプルでは Console.WriteLine は使ってないということです。ただし、それは「待機において重要」ということではなく、結果の確認のための方法として重要ということです。 質問者さんのコードでも、質問者さんが期待されたように「1つ目のawait Task.WhenAll(task)で全て出力された後に次のawait Task.WhenAll(task2)が実行される」のは、「出力 (= Console.WriteLine によるコンソールへの表示)」を除いては、await で待機したとおり順番に実行されているはずです。 ただ、Console.WriteLine によるコンソールへの表示については、y-waiwai さんの回答に対する Zuishin さんのコメントで、 > 割り込みのタイミングが合わなければ常にビジーで実行されず、後回しにされることもあります。つまり順番が入れ替わるのは、キューではなく割り込みが使われているためです。 ・・・と書かれたように、タイミングの問題で質問者さんの期待と違った結果になったということでしょう。 ちなみに、コンソールアプリと GUI アプリでは SynchronizationContext が違います。詳しくは以下の記事の 6, 8 あたりを見てください。 SynchronizationContext とは? http://surferonwww.info/BlogEngine/post/2020/09/30/what-is-synchronizationcontext.aspx 質問のように混ぜると非同期操作にどう影響するかわかりませんが、そもそも混ぜることが論外だと自分は思ってます。
it9265

2022/07/29 15:31

ありがとうございます。 下記コメントでZuishinさん含め皆様の意図を理解することができました。 Console.WriteLine によるコンソールへの表示)」を除いては、await で待機したとおり順番に実行されているはずです。
guest

0

スレッドやタスクの実行タイミングは規定されません
どのような結果になろうが文句言えません

投稿2022/07/18 22:05

y_waiwai

総合スコア87784

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

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

Zuishin

2022/07/18 23:57 編集

つまり、この質問を解決する方法はないということですか? > スレッドやタスクの実行タイミングは規定されません いいえ。基本的に先に実行されたものから先に実行されます。(小泉構文) ただし、Console.WriteLine のように排他制御がある場合は待ち時間が発生します。 その場合、割り込みのタイミングが合わなければ常にビジーで実行されず、後回しにされることもあります。 つまり順番が入れ替わるのは、キューではなく割り込みが使われているためです。 であれば、キューを作れば順番も制御できます。 そしてこの場合は、タイミングを制御したいのはわずか一か所だけなので、わざわざキューを作らなくても、前半のタスクがすべて終了してから次のタスクを始めるだけで簡単に解決します。 実行タイミングは基本的に実行順なので、前半のタスクが終了するまで後半のタスクの開始を待つことは、プログラムで指示できます。
it9265

2022/07/27 15:30

私がしたい動作はまさにこの通りで、 タイミングを制御したいのはわずか一ヶ所だけです。 これらをawaitを用いて制御したつもりだったのですが、Task.RunでTaskを返却したタイミングで既に処理が走ってしまっているため、期待の動作と異なっていたようです。 前半のタスクが終了するまで後半のタスクの開始を待つ方法は、await以外でどうプログラムに指示できるのでしょうか?
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.47%

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

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

質問する

関連した質問