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

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

ただいまの
回答率

90.47%

  • C#

    9238questions

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

コンソールアプリでprivate async Taskでリフレクションを使用して帰り値を返せない

解決済

回答 1

投稿

  • 評価
  • クリップ 2
  • VIEW 1,218

kazuya_

score 44

コンソールアプリでFormを継承させた時、何故か返り値が戻りません。
デバッガで見ると、LedSwitchTask内でreturn値の受け渡しが失敗するようです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Windows.Forms;
using System.Reflection;
namespace PrivateAccess {
    class Program {
        static void Main(string[] args) {

            PrivateAccess pr = new PrivateAccess();
            Task<string> res;

            Task.WhenAll(
                res = Task.Run(() =>
                {
                    Task<string> rt = pr.privateasynctest();
                    return rt;  
                })
            ).Wait();
            Console.WriteLine("status:" + res.Result);
        }
    }
    public class PrivateAccess :Form { //Formを継承させると、無限LOOPになる
  //  public class PrivateAccess { //Formを継承させなければ、正常にresが返る

        /// <summary>
        public PrivateAccess() {
        }

        public async Task<string> privateasynctest() {
            try {
                PrivateAccess theObject = new PrivateAccess();
                Type t = theObject.GetType();

                MethodInfo dynMethod = t.GetMethod("LedSwitchTask", BindingFlags.NonPublic | BindingFlags.Instance);

                var task = await (Task<string>)dynMethod.Invoke(theObject, new string[] { "1 0" });
             //   res = await task.ConfigureAwait(false);
                return task;
            }
            catch (Exception ex) {
                string s;
                s = ex.Message;
                return "NG";
            }
        }
        private async Task<string> LedSwitchTask(string allArg) {
            string res;
            res = await Task.Run(new Func<string>(() => {
                string chan;
                chan = allArg.Substring(0, 1);
                Thread.Sleep(800);
                return "OK";
            }));
            return res;
        }
    }
}コード
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

checkベストアンサー

+3

なるほど!!!!!
これは面白いですね。原因が分かりました。

実は、async/awaitがデッドロックを引き起こす条件の一つに、「コンソールアプリケーションではないこと」というのがあります。
厳密に言うと、「SynchronizationContextという、プログラムの実行コンテキストを同期するためのstaticなプロパティに中身が存在する場合にデッドロックが発生する可能性がある」のですが、コンソールアプリケーションだとこれがデフォルトではnullになっています。そうすると、awaitした後に特定のコンテキストに復帰する動作が行われなくなるため、根本的にデッドロックが発生する原因が失われます。つまり、コンソールアプリケーションである限り、たとえWaitメソッドを使ってもスレッド同期によるデッドロックが発生しないということです。

しかし!!System.Windows.Forms.Formのインスタンスを生成するとき、どうやらコンストラクタがSynchronizationContextを生成するようです。つまり、質問のPrivateAccessクラスがFormを継承しているかどうかで、SynchronizationContextの有無が異なっていることになります。
すると、Mainメソッド内で呼び出しているWaitprivateasynctestメソッドのawaitがぶつかってデッドロックを引き起こすようになるわけです。

これは……ちょっと想定外のクソ挙動で戸惑っていますが、、とりあえず、awaitを呼び出している式に対してそれぞれConfigureAwait(false)を付けることでSynchronizationContextの伝播を断ち切ることができるので、async/awaitを使う際はこのコーディングを徹底することでデッドロックを完全に回避することができます。

以下に、最低限の書き換えで動作するようにしたコードを掲載します。こちらをコピペで試してみてください。

public class PrivateAccess :Form { //Formを継承させてもちゃんと動作するはず!

    /// <summary>
    public PrivateAccess() {
    }

    public async Task<string> privateasynctest() {
        try {
            PrivateAccess theObject = new PrivateAccess();
            Type t = theObject.GetType();

            MethodInfo dynMethod = t.GetMethod("LedSwitchTask", BindingFlags.NonPublic | BindingFlags.Instance);

            var task = (Task<string>)dynMethod.Invoke(theObject, new string[] { "1 0" }); // <- awaitを外す
            var res = await task.ConfigureAwait(false); // !
            return res;
        }
        catch (Exception ex) {
            string s;
            s = ex.Message;
            return "NG";
        }
    }
    private async Task<string> LedSwitchTask(string allArg) {
        string res;
        res = await Task.Run(new Func<string>(() => {
            string chan;
            chan = allArg.Substring(0, 1);
            Thread.Sleep(800);
            return "OK";
        })).ConfigureAwait(false); // !
        return res;
    }
}

そして、元々の質問にあった、「IntelliTestから実行したときデッドロックする」については、こちらは推測ですが、おそらく内部でWaitかそれに類する動作を行っていると考えられます。
こちらも同期コンテキストの破棄を徹底することで問題は発生しなくなると予想できますが……(もしかして、UI関係とかのコンテキストが重要な非同期メソッドはIntelliTestでは評価できないってことかしら……?)
ここを掘り下げるにはさらなる調査が必要ですが、なんだか根の深い問題な気がします。


以下はお節介ですが。
自分でコードを書く場面では、決してWaitメソッドやResultプロパティを使わないようにしてくださいね。非同期に値を取得するメソッドをどこかしらで同期するというのは、実行戦略が異なる二つのコンテキストを無理矢理同期することになるため、デッドロックが発生する可能性が非常に高いのです。今回の話については結果的には回避不可能なハマりどころであったと言えますが、同じ種類のハマりどころを自分で作ることになるのだと言い換えると、どれだけヤバイかは理解できると思います。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/06/24 23:56

    ありがとうございました。良い勉強になりました。同期コンテキストとConfigureAwait等、調べていきたいです。

    キャンセル

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

  • ただいまの回答率 90.47%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

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

  • C#

    9238questions

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