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

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

ただいまの
回答率

90.51%

  • C#

    9048questions

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

  • 非同期処理

    134questions

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

Task.Runで落ちてる?

解決済

回答 2

投稿

  • 評価
  • クリップ 1
  • VIEW 3,593

indist19

score 6

hello1からhello9が出力されることを期待していたのですが、実際にはhello1までしか出力されませんでした。
初回の処理で落ちているようなのですが、挙動の原因が分かりません。
期待したとおりに動作させるためにどのように修正したらよいでしょうか?
出来ればなぜ落ちたのかまで解説していただければ幸いです。
初歩的な質問で煩わしいと思いますが、ご教授宜しくお願い致します。

using System;
using System.Threading.Tasks;
using System.Threading;
using System.Runtime.InteropServices.WindowsRuntime;

namespace project_test {
    class MainClass {
        public static void Main(string[] args) {
            Run ();
        }

        public static async void Run() {
            for (int i = 1; i < 10; i++) {
                await Task.Run (() => {
                    Thread.Sleep (1);
                    System.Console.WriteLine ("hello{0}", i);
                });
            }
        }
    }
}


動作環境

  • OS X EI Captian
  • Xamarin 6.0.1
  • 気になる質問をクリップする

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • amay077

    2016/07/18 22:23

    Task や async/await を組み合わせていますが、普通に for するだけではダメなのでしょうか?ダメな場合、その背景、作りたいプログラムについて教えてください。

    キャンセル

  • indist19

    2016/07/18 22:37

    非同期で書く程ものではないことをは理解しているのですが、非同期処理の書き方が分からなかったので、実際にコードを書いてみようと思い質問のようなコードを書いてみました。
    作りたかったのはhello1からhello9を出力するプログラムです。

    キャンセル

回答 2

checkベストアンサー

+4

既に解決済みになってますが、少し気になったので回答をします。

質問者様の最初のコードは「非同期処理を順次繰り返す」という意図であるなら、正しいです。
ですが、出力は安定しないです。
理由は、Main関数のほうです。
Main関数を抜けると、プログラムが終了したという扱いになるので、非同期関数であるRunを呼び出した直後に、Runの動作を待たずにプログラムが先に終了しているのです。
なので、Runの呼び出しの後に長いSleepを配置することで↓

public static void Main(string[] args) {
    Run ();
    Thread.Sleep (10000); // Runの実行が完了するまでプログラムを終了させない
}


意図している出力を確認することができるはずです。
もっと適切に待機するなら、
Run関数の戻り値をTask化して、Waitで待機するのが良いでしょう。

public static void Main(string[] args) {
    Run () .Wait ();
}

public static async Task Run() {
    for (int i = 1; i < 10; i++) {
        ...

最後に、もし質問のコードで実際にやりたかったことが「複数のタスクを非同期で並列に実行」だとしたら、このコードでは意味が異なります。awaitは「指定した非同期処理が完了し次第、続きをさらに非同期で実行」するためのキーワードです。


07/20追記
目的が「複数のタスクを非同期で並列に実行」とのことなので、この場合は確かにParallelを使う解決法もあります。が、ちゃんとTaskを使う場合はどうすればよいのかを書いてみます。
並列実行のコードはこうです。

public static void Run() {
    for (int i = 1; i < 10; i++) {
        Task.Run (() => {
            Thread.Sleep (1);
            System.Console.WriteLine ("hello{0}", i);
        });
    }
}


よく見ると元コードからasyncとawaitが外れただけですね。このコードの意味はどうでしょうか?
「WriteLineするだけのTaskをforで繰り返し生成してどんどん実行する」というそのままです。
このコードは書かれた通りに並列に動作します。
Runはあくまで非同期Taskを大量生成するだけなので、async関数ではなくなっているのがポイントです。
もちろん、この場合もWriteLine自体は非同期実行されるので、Main関数でSleepしないと意図した出力は得られないです。
並列実行なのでWriteLineの実際の実行順序が保証されなくなることは注意してください。

もし、この並列Runを適切に待機したいとなると、少しだけ工夫する必要があります。
「大量生成されたTaskを束ねて、その全てが完了したことを表すTaskを生成する」

public static Task Run() {
    var list = new List<Task>(); // 生成するTaskを束ねるためのList
    for (int i = 1; i < 10; i++) {
        var task = Task.Run (() => {
            Thread.Sleep (1);
            System.Console.WriteLine ("hello{0}", i);
        });
        list.Add (task); // Taskを1つずつListへ
    }
    var tasks = list.ToArray (); // 集め終わったListを配列に
    return Task.WhenAll (tasks); // 全てのTaskを一つのTaskに
}


こうすることで、Main関数内でWaitで待機できるようになります。


ところで、この並列実行コードには少し問題があります。
この行です。
System.Console.WriteLine ("hello{0}", i);
実は、各Taskがたった一つの変数iを共有しているため、
並列に実行されたWriteLineは全てhello10を出力してしまいます。
これを回避するには、for文のスコープ内で変数を確保し、それをTaskで使用すれば良いです。

public static void Run() {
    for (int i = 1; i < 10; i++) {
        var x = i; // iをローカルスコープのxにコピー
        Task.Run (() => {
            Thread.Sleep (1);
            System.Console.WriteLine ("hello{0}", x);
        });
    }
}

おまけ。
こういうタイプの並列実行コードを記述するときはLinqを活用すると簡潔でいい感じに仕上がることが多いです。
Linqの扱いに慣れている必要はありますが、わかりやすさは段違いに高くなります。
参考までに、上記コードと同じ挙動のLinq使用版を置いておきます。

public static Task Run()
{
    var tasks = Enumerable
        .Range(1, 9)
        .Select(x => Task.Run(() =>
        {
            Thread.Sleep(1);
            System.Console.WriteLine("hello{0}", x);
        }))
        .ToArray();

    return Task.WhenAll(tasks);
}

// asyncラムダ使用版、こっちのほうが推奨
public static Task Run()
{
    var tasks = Enumerable
        .Range(1, 9)
        .Select(async x =>
        {
            await Task.Delay(1);
            System.Console.WriteLine("hello{0}", x);
        })
        .ToArray();

    return Task.WhenAll(tasks);
}

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/07/19 23:56

    回答有り難うございます。
    最後に書かれているように本来書きたかったコードは「複数のタスクを非同期で並列に実行」でした。
    実際に書いたコードの実行結果がどうしてこうなるのか理解できなかったので、解説してもらえてよく分かりました。
    awaitを使う場合ではあくまで順次実行されるため、並列で何か処理を行うことは出来ないと理解しましたが良いでしょうか?
    並列に実行する場合は、あまり詳しくは調べたわけではありませんがParallelを使うのでしょうか?

    キャンセル

  • 2016/07/20 12:26

    ああ、やはり並列実行の方でしたか。
    非同期並列コードについて、回答の方に追記しました。
    長いですが、どうぞよろしくお願いします。

    キャンセル

  • 2016/07/23 11:21

    一つ一つサンプルコードまでありがとうございます!
    おまけを掘り下げるのもあれなんですが、Linqを使う場合、asyncを使う方が推奨されるのなぜなんでしょうか?
    また、await Task.Delay(1) の挙動は別スレッドで待機した後に本スレッドに戻りWriteLineを実行するイメージで良いでしょうか?
    煩わしいかもしれませんが、解答よろしくお願いします。

    キャンセル

  • 2016/07/25 09:26

    推奨、っていう言い方はちょっと良くなかったですね、「こっちのほうがスマート」みたいな感覚でした、混乱させて申し訳ないです。
    上記のLinq版は二つともコードの意味合いはほとんど変わらないので、実際にはどちらを使っても構わないのです。しかしasyncキーワードというのは「『非同期な処理』を『非同期に実行』する」という当たり前のようなことをするための構文なので、こういう処理にこそasyncが役立つ、ということを言いたかったのでした。

    Task.Delayの実際の挙動について。DelayはThread.Sleepのように「別スレッドで待機」とはなりません。Delayは「指定した時間後に『完了』を通知するように『スケジューリングして』、スレッドを開放」しています。なので、Thread.Sleepのように待機してる間スレッドを1本掴んだまま眠るわけではないので、資源効率が良いです。実行した時の挙動については、DelayでもSleepでも変わらないと思って良いです。

    以下は駄文なので適当に読み飛ばしてください。
    asyncを理解するには、非同期処理に「Task」という名前が付いている意味を考えてみると良いです。Taskはその名の通り、一つの作業単位、仕事、タスクを表すものであると考えることができます。asyncメソッドはそれ自体が一つのタスクであり、その中で複数のタスクをどう処理するかを記述しているだけなのです。いわゆる作業手順書です。手順書であるということは、「書かれた通りの順序で実行していけばいい」ということで、それはつまり「タスクの実行順序さえ間違えなければ、誰が(どのスレッドが)どのタスクを実行したって構わない」と表明しているような事になります。なので、非同期処理そのものを「タスク」と見なすことで、非同期処理の構造を単純化することができるのです。async/awaitは、その非同期Taskを見やすく、書きやすくするだけのただの構文糖衣だと考えてしまえば、難しいことはなにもないはずです。Task.Delayも抽象化した表現で言って「指定したミリ秒数『待機』するという仕事(タスク)を表す」と考えると、それをawaitするということがどういう意味なのか分かってくるのではないでしょうか。

    キャンセル

  • 2016/07/31 23:03 編集

    ansycキーワードは非同期処理ですよ、と言うことを明示するものだったんですね。

    Task.Delayは資源効率が良くThread.Sleepと挙動が変わらないのであれば、非同期処理では基本的にTask.Delayの方が推奨されると言うことですね。

    駄文までありがとうございます。
    この説明のおかげでかなり非同期処理とasyncの挙動がイメージ出来ました。
    構文頭位であるということとTask.Delayの抽象化した表現の説明が特に理解しやすかったです。

    ここまで具体的な説明をしていただけて本当に嬉しく思います。
    大変お手数おかけしましたが解答ありがとうございました。

    キャンセル

+1

非同期処理の中に非同期処理をするとそのようになります。
望む結果を出したい場合は非同期を重複させない方法が安全です。

一度下記URLを一読することをお勧めします。
https://www.infoq.com/jp/news/2013/04/async-csharp-fsharp
http://blog.xin9le.net/entry/2012/07/19/002126

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2016/07/18 22:45

    ありがとうございます。
    読んでみます。

    キャンセル

  • 2016/07/18 23:39

    望んでいた結果になりました。ありがとうございました。
    具体的に次のようなコードになりました。

    using System.Threading.Tasks;

    namespace project_test
    {
    class MainClass
    {
    public static void Main (string [] args)
    {
    for (int i = 1; i < 10; i++) {
    var r = Run (i);
    r.Wait ();
    }
    }

    public static async Task Run (int i)
    {
    await Task.Delay (1000);
    System.Console.WriteLine ("hello{0}", i);
    }
    }
    }

    キャンセル

同じタグがついた質問を見る

  • C#

    9048questions

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

  • 非同期処理

    134questions

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