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

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

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

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

Q&A

解決済

1回答

2170閲覧

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

kazuya_

総合スコア78

C#

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

0グッド

0クリップ

投稿2017/06/24 16:30

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; } } }コード

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

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

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

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

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

guest

回答1

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/24 18:24

編集2017/06/24 18:53
tamoto

総合スコア4103

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

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

kazuya_

2017/06/25 09:05

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問