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

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

ただいまの
回答率

90.48%

  • C#

    9213questions

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

Fromアプリでasync voidとリフレクションを使用した場合の非同期効果について

解決済

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 502

kazuya_

score 44

Fromアプリにボタンを追加しました。ボタンを押すとpublic async void privateasynctest内の変数taskにLedSwitchTaskメソッドの戻り値が返ってきます。LedSwitchTaskのTask.Runは非同期で走らせたいのですが、Task.Runの部分に.ConfigureAwait(false)を追加した場合と、そうでない場合は、非同期の効果に
関係するでしょうか? ちなみにLedSwitchTaskは、ハードウェアに信号を一度送るだけと想定しています。
このConfigureAwaitのないものでも、特に問題はないでしょうか? なぜこのようなことが気になるかと
いいますと、intellitestで非同期の宣言はasync taskを使用しないと、機能できず走らせるには、
ConfigureAwaitを使用しないとデッドロックすることがあるらしいからです。

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;
using System.Drawing;
namespace PrivateAccess
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
        private void button1_Click(object sender, EventArgs e)
        {
            PrivateAccess pr = new PrivateAccess();
            pr.privateasynctest();
        }
    }
    public class PrivateAccess : Form
    { //Formを継承させると、無限LOOPになる
        //  public class PrivateAccess { //Formを継承させなければ、正常にresが返る

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

        public async void privateasynctest()
        {
            try
            {
                PrivateAccess theObject = new PrivateAccess();
                Type t = theObject.GetType();

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

                string task = await (Task<string>)dynMethod.Invoke(theObject, new string[] { "1 0" });
                var s = task;
            }
            catch (Exception ex)
            {
                string s;
                s = ex.Message;
            }
        }

        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;
        }
    }
}コード
  • 気になる質問をクリップする

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

回答 1

checkベストアンサー

0

こんにちは。

まずは結論から。
ConfigureAwaitを付けることで非同期メソッドの動作そのものは変わりません。そして、デッドロック問題を回避するため、ほぼ全てのawait箇所でConfigureAwaitを書くのが基本であると認識してください。


ConfigureAwaitの効果を説明する前に、awaitとTaskについて説明します。
awaitは「指定したTaskの処理が完了したときにメソッドの続きを実行する」キーワードになります。
実は非同期メソッドは、見た目は普通のメソッドのようですが、中でawaitを書く度にその場所で処理の前後が分割され、awaitしたTaskが完了した時に続きをスケジュールして実行する、いわゆる「処理の連結リスト」のようなものになっています。
そして、数珠つなぎで構成された非同期処理は、それぞれの分割処理の続きを実行する度に、任意の空いているコンテキスト(スレッド)に処理を放り込みます。「処理が書いた順番通りに実行されるなら、どのスレッドがどの処理を実行しても問題はない」という思想に基づいて設計されたものがTaskクラスなのです。

しかし、非同期メソッド中でawaitする前と後でコンテキストが異なると都合が悪い存在があります。それは、「UI処理等、特定のスレッドに依存する処理」です。
UI処理は、元々単一のスレッドでしか実行できないというフレームワークの設計による制約があります。
よって、最も非同期処理を必要とするシーンでTaskクラスとasync/awaitが使えないという残念な状況になってしまうことになります。
これを解決するために、awaitキーワードは、

  • Taskを呼び出す前に、そのコンテキストをいったん保存する
  • Taskが完了した後の続きを実行するとき、そのコンテキストを復元して、そこで続きを実行する

という設計を採用しました。
つまり、非同期メソッド上に実際に記述した処理は「全て単一のスレッド上で実行される」設計になったということです。
この設計によって、UI処理を非同期メソッドで記述出来るようになった代わりに、コンテキストの衝突によるデッドロックの問題を生み出してしまいました。
デッドロックは致命的な存在なので、さらにこれを回避するために「コンテキストの保存を無効化する」手段が必要になりました。それが、ConfigureAwaitメソッドなのです。


つまり、本来awaitはConfigureAwait(false)が書かれた状態がデフォルトであると考えることができます。
そして、UI処理等の、非同期メソッドの処理を一つのコンテキスト上で走らせたい時だけ、ConfigureAwaitを外せばいいのです。
質問のコードを例にすると、

  • LedSwitchTaskメソッド中に、UIを操作するメソッドを書いていない
  • LedSwitchTaskメソッド中でawaitしている全ての非同期メソッドやTaskがUIにアクセスしていない

という2点を満たす場合、LedSwitchTaskメソッド中の全てのawaitにConfigureAwaitを付けることができます。そして、付けなければなりません。

実際のところ、WaitメソッドやResultプロパティを一切使用しなければ、デッドロックは発生することはなく、ConfigureAwaitを書かなくても障害につながることはないのですが、ライブラリや汎用的な非同期APIを作成する際にはコンテキストの管理は絶対に必要になる知識とテクニックなので、ここできっちり覚えてしまった方がいいです。


Taskクラスを説明するのは難しく、理解できない文章になっていたらすみません。
また、以前の回答でも参考に記載しましたが、async/awaitについての解説記事の内容も参考にしてみてください。

質問の内容が過去の質問からの地続きになっていて、文脈が読み取りにくい質問になってきています。
質問単体からその背景が読み取れない質問はあまり良くないので、もしここでわからないところや疑問点があったら、コメント欄に質問してください。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2017/06/25 18:05

    踏み込んだご解説ありがとうございます。なんとなくイメージできたと思います。戻るためにコンテキストをいったん保存しているという部分で、理解が進展しました。コンテキストをいったん保存していおいてConfiureAwaitのあとにUI操作のために戻して利用するというネット説明がありつながりました。

    キャンセル

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

  • C#

    9213questions

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