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

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

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

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

Q&A

解決済

3回答

617閲覧

async voidでデッドロックする理由について

kazuya_

総合スコア78

C#

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

0グッド

1クリップ

投稿2017/06/28 12:25

フォームアプリで、ボタンクリックから呼び出した場合は、デッドロックしません。コンソールアプリでForm継承して
呼び出すと、voidなので戻り値がないのですがデッドロックしてしまいます。

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 { //コンソールアプリの場合 最下段の(1)のブレークポイントが無視され、止まらない class Program { static void Main(string[] args) { PrivateAccess pr = new PrivateAccess(); pr.privateasynctest(); Console.WriteLine("status: Ok"); for (long i = 0; i < 100000; i++) { for (long j = 0; j < 100000; j++) { } } } } //フォームアプリケーションの場合 フォーム内のボタンを押下して 最下段の(1)のブレークポイントで止めることができる //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 { /// <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); var task = await (Task<string>)dynMethod.Invoke(theObject, new string[] { "1 0" }); } 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"; })); return res; // (1) } } }

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

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

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

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

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

guest

回答3

0

ベストアンサー

こんにちは。

回答だけ書きますね。
今回の問題はデッドロックしていません。
単純に、コンソールアプリケーションの場合はreturn res; // (1)の行に到達する前にMainメソッドを抜けているため、ブレークする前にプログラムが終了しています。

投稿2017/06/28 23:31

tamoto

総合スコア4105

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

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

kazuya_

2017/06/29 03:49 編集

ありがとうございます。Main内でWAIT待ちのかわりにFOR文でLOOPさせていますが、ここは関係なく Task.Runを実行している部分がLedSwitchTaskのスレッドに戻れず先に抜けるということでしょうか? あと、PrivateAccessでFormを継承しているので、デッドロックだと思っていました。
ozwk

2017/06/29 04:10 編集

というか待ちたいなら どのぐらい待てるかわからないループ回すんじゃなくてReadKey()とか使えばいいのでは。 (ちなみに質問文のコードを手元の環境で動かしたらちゃんとブレークしました。)
tamoto

2017/06/29 05:02

前も回答した通り、ConfigureAwait(false)をつけていない非同期メソッドは、awaitの続きを前と同じスレッドで実行しようとします。質問のコードの場合では、Mainメソッドのスレッドに乗っています。Mainメソッドは「非同期メソッド開始->長時間のforループ->Mainメソッドの終了」の間スレッドを占有しているため、awaitの続きはその後にスケジュールされていることになります。しかし、アプリケーションには「Mainメソッドの完了で終了する」という絶対のルールがあるため、awaitの続きが実行される前にアプリケーションが終了します。 forループは「awaitが戻りたいスレッドを占有している」という点でWaitとやってることは同じです。Waitの方は「Taskが完了するまで終わらないため、ロックが永続する」程度の違いしかないです。 結局のところ、コンテキストを持つアプリケーションで非同期メソッドを使うには、「全て非同期にする」「コンテキストを保持しない(ConfigureAwaitを使う)」のどちらかを徹底するしか方法はありません。
tamoto

2017/06/29 05:12

> ozwkさん おや……質問のコードでbreakしましたか? このコードだとできないと思っていたんですが……ちょっと後で自分の環境でも検証してみます。 デバッガの環境を教えてもらってもよろしいでしょうか?
kazuya_

2017/06/29 08:33

ありがとうございます。環境によりbreakするかもしれないです。 下記のようにすると、privateasynctestのtask変数には戻り値が返りました。 LOOPに到達するまでに、早く抜けられたら、返るのではないでしょうか? LOOPに到達した時点で、戻れなくなるのでは? それで、LedSwitchTask内ではthread生成とか時間がかかり、戻れない状態になっているのではないかと思いました。 private async Task<string> LedSwitchTask2(string allArg) { string chan = allArg.Substring(0, 1); await Task.Delay(0); // Thread.Sleep(800); return "OK"; }
tamoto

2017/06/29 23:46

> kazuya_ さん loop到達までに早く抜けられたら、というのは、実は3割くらい正解です。 正確に言うと、awaitは非同期実行を開始する準備をしている間にその非同期処理が完了してしまっていた(ものすごく早く処理が完了した)場合、コンテキストスイッチを行わず、同一スレッドで継続的に処理を行います。つまり、完全に同期処理と同じ順序で処理されることになるため、スレッドの衝突が起こらなくなります。 コメントのコードの場合、awaitしている行がTask.Delay(0)のみであるため、同期処理と同等に取り扱われ、returnの行に到達することができます。 Mainメソッドの"status: Ok"が出力される前にbreakで停止しているはずなので、確認してみて下さい。 試しにDelayの値を1000などにしてみると、breakできずにプログラムが終了することを確認できると思います。
ozwk

2017/06/29 23:59

> tamotoさん Formの継承外してました。 継承したらbreakできませんでした。
guest

0

「スレッドプログラミングは人類には早すぎる」と並列処理の本に書いてあったので、
ベストプラクティスに則るべきだと思います。原因究明は大変。

Formから継承するのが悪いのであって、MVVM的に別クラスとして作ればいいだけだと思います。
(FormをNewしたときに、余計なことをやっていると推察。)
(そうはできないからメタプログラミングしているんだよ案件かもしれませんが)

投稿2017/06/28 18:51

kiichi54321

総合スコア1984

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

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

kazuya_

2017/06/29 03:42

ありがとうございます。
guest

0

タイトル「async voidでデッドロック」だけ見てレスしていますのでハズレかもしれませんが・・・

非同期プログラミングのベスト プラクティス
https://msdn.microsoft.com/magazine/jj991977

"async void メソッドは、呼び出し側が非同期であることを想定していない場合、大惨事につながることがあります。戻り値の型が Task のとき、呼び出し側はその後の操作で処理することを認識しています。戻り値の型が void のとき、呼び出し側は処理が戻ったときにメソッドが完了したと想定します"

関係なかったら失礼しました。

投稿2017/06/28 13:11

退会済みユーザー

退会済みユーザー

総合スコア0

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

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

kazuya_

2017/06/29 23:29

ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問