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

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

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

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

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。

Q&A

解決済

3回答

13740閲覧

DoEventsの代わりのawait使用について

KotS

総合スコア7

C#

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

Visual Studio

Microsoft Visual StudioはMicrosoftによる統合開発環境(IDE)です。多種多様なプログラミング言語に対応しています。

.NET Framework

.NET Framework は、Microsoft Windowsのオペレーティングシステムのために開発されたソフトウェア開発環境/実行環境です。多くのプログラミング言語をサポートしています。

0グッド

1クリップ

投稿2021/08/17 02:27

いつもお世話になっております。

Visual C#2017 , .NET Framework4.7で開発しております。

C#で画面が固まらないような待ちでDoEventsを使っていたソースがあるのですが、
.NET系言語ではあまりDoEventsを使うのは良くないと聞きましたので、
それをawaitで実現するようなソースも作ってみました。

Visual Studioで[ファイル]-[新規作成]-[プロジェクト]で
[Visual C#]を選択し展開、
[Windowsデスクトップ]を選択します。
右側のウィンドウで
[Windwosフォームアプリケーション(.NET Framework)]を選択し、
名前を今回は[DoEventsAwaitTest]とし、
フレームワークは[.NET Framework4.7]としました。

表示されたForm1画面に
Buttonコントロールを1つ貼り付けます。
Form画面のどこかを右クリックして、[コードの表示]をクリックします。
表示された、

Visual

1public Form1() 2{ 3 InitializeComponent(); 4}

の次行に、
DoEventsソースの場合は、以下のソースを貼り付けます。

private void button1_Click(object sender, EventArgs e) { Console.WriteLine("button1_Click開始"); int res = funcB(5000); Console.WriteLine("button1_Clickおわり"); } public int funcA(int nMillisecond) { int res = funcB(nMillisecond); return res; } public int funcB(int nMillisecond) { DoEventsDelay(nMillisecond); Console.WriteLine("funcBおわり"); return 1; } public void DoEventsDelay(int nMillisecond) { System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); sw.Start(); do { if (sw.ElapsedMilliseconds > nMillisecond) break;//タイムアウト Application.DoEvents(); } while (true); sw.Stop(); sw = null; }

awaitソースの場合は、以下のソースを貼り付けます。

private async void button1_Click(object sender, EventArgs e) { Console.WriteLine("button1_Click開始"); Task<int> taskA = funcA(5000); await taskA; Console.WriteLine("button1_Clickおわり"); } public async Task<int> funcA(int nMillisecond) { Task<int> taskB = funcB(nMillisecond); await taskB; return taskB.Result; } public async Task<int> funcB(int nMillisecond) { await Task.Delay(nMillisecond); Console.WriteLine("funcBおわり"); return 1; }

いずれもbutton1をクリックしてから5秒間の待ち中もForm画面は固まらず、ドラッグ可能です。

DoEventsソースの出力ウィンドウ結果は

button1_Click開始 funcBおわり button1_Clickおわり

awaitソースの出力ウィンドウ結果は

button1_Click開始 'DoEventsAwaitTest.exe' (CLR v4.0.30319: DoEventsAwaitTest.exe): 'C:\WINDOWS\Microsoft.Net\assembly\GAC_MSIL\mscorlib.resources\v4.0_4.0.0.0_ja_b77a5c561934e089\mscorlib.resources.dll' が読み込まれました。モジュールがシンボルなしでビルドされました。 funcBおわり button1_Clickおわり

とどちらもbutton1_Clickイベントプロシージャ内でfuncBが終わるのを待っていることが分かります。

ここでいくつか質問があるのですが、
①awaitでの待ちを使用する場合は、そのプロシージャをの呼び出し元もさらにその呼び出し元も
開始トリガーのプロシージャ(イベントプロシージャあるいはMain)まで
全てasync Task<>~等の形のプロシージャにする必要があるのでしょうか?
②DoEvents()の代わりとして推奨されるべき方法はawaitが一番良いのでしょうか?

https://docs.microsoft.com/ja-jp/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming
も見てみましたが、何となく確信が持てずにいます。

未だTaskやスレッド関係は勉強中なのですが、ご教示のほどよろしくお願いいたします。

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

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

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

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

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

Zuishin

2021/08/17 02:47

やってみればわかると思います。 でもその前に Console.WriteLine という同期メソッドを呼んでますよね?
KotS

2021/08/17 03:10

ご回答ありがとうございます。 はい。 Console.WriteLineですが、同期メソッドですね。 もともとSystem.Diagnostics.Debug.WriteLineで書いていたのですが、こちらでも出力ウィンドウに書けたのでConsole.WriteLineにしたものです。 仕様上、非同期プロシージャ内では同期メソッドと非同期メソッドが混在する可能性がありそうなのですが、それ自体に問題があるということなのでしょうか? VB6等の同期プログラミングしか経験があまりないもので知識不足ですみません。
Zuishin

2021/08/17 03:15

非同期メソッドから同期メソッドを呼ぶのに何の問題もありません。 同期メソッドが呼べないという制限があると非常に不便です。 自分で同期メソッドを作ってそれを呼ぶのにも問題ないということです。 「すべて非同期にする」をよく読んでもらえれば、「すべてのメソッドを非同期にせよ」ということではなく、「非同期メソッドを呼ぶ同期メソッドを作るべきではない」と読めるはずです。 つまり、 > 「await」を使わずに「.GetAwaiter().GetResult()」を使うことです。 のようなことをするのは極力避けてください。 内部で非同期メソッドを呼ぶなら非同期にしましょう。 そうでなければ同期メソッドで問題ありません。
KotS

2021/08/17 05:13

> 「await」を使わずに「.GetAwaiter().GetResult()」を使うことです。 をしてみましたが、Formが固まってしまい所望の目的は達成できないことがわかりました。 画面が固まらないようにするにはawaitは必須ですね。
Zuishin

2021/08/17 05:21

「非同期メソッドを呼ぶ同期メソッドを作るべきではない」と読めるはずです。 と書きました。そのように固まるのですべきではないということです。
Zuishin

2021/08/17 05:25 編集

「.GetAwaiter().GetResult()」が「非同期メソッドを同期メソッドに変換する行為」です。
KotS

2021/08/17 05:27

はい。 Zuishinさんの仰っている意味を理解するために、念のため試してみました。 ありがとうございました。
退会済みユーザー

退会済みユーザー

2021/08/17 09:17

表題の「DoEventsの代わりのawait使用について」にはすでに答えが出てますので、このスレッドはクローズしてください。 質問者さんが質問欄に書いたことは新たに別のスレッドを立てて質問してください。一つの課題の解決が次の疑問を生んで、同じスレッドの中で次々質問が増えていくというのは避けてください。ここは情報の蓄積も目的としているそうで、後から検索などでここを訪れた人には訳が分からなくなりますので。 また、質問者さんが回答欄に書くと訳が分からなくなるので、それも避けていただきたく。
guest

回答3

0

①awaitでの待ちを使用する場合は、そのプロシージャをの呼び出し元もさらにその呼び出し元も開始トリガーのプロシージャ(イベントプロシージャあるいはMain)まで全てasync Task<>~等の形のプロシージャにする必要があるのでしょうか?

そうですね。質問にある「awaitソースの場合は、以下のソースを貼り付けます」のコードにあるように上から下まで async / await を使うのが良さそうです。

質問さんが参考にされている Microsoft のドキュメントにある「すべて非同期にする」のセクションに書いてありますが、少なくとも「結果をよく考えずに同期コードと非同期コードを混在させるべきではない」と思います。

②DoEvents()の代わりとして推奨されるべき方法はawaitが一番良いのでしょうか?

Windows Forms のような GUI アプリで async / await を使うのは UI スレッドをブロックしないようにして UI の応答性を高める(フリーズしないようにする)ことです。(ASP.NET はちょっと違います)

その目的を果たすことに限って言えば「代わりとして推奨」というレベルではなく、DoEvents を使うなんて 1 ミリも考えるべきではないと思います。

投稿2021/08/17 02:51

編集2021/08/17 02:55
退会済みユーザー

退会済みユーザー

総合スコア0

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

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

KotS

2021/08/17 03:03

ご回答ありがとうございます。 やはりそうなのですね。 実はつい先ほど、以下のリンクを見つけました。 https://nryblog.work/2020/08/13/%E3%80%90c%E3%80%91async-await%E3%81%A7%E5%90%8C%E6%9C%9F%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%81%8B%E3%82%89%E9%9D%9E%E5%90%8C%E6%9C%9F%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%82%92%E5%91%BC/ Task Runを使用すれば、呼び出し元は延々とasyncキーワードにする必要が無いということでしょうか?
退会済みユーザー

退会済みユーザー

2021/08/17 03:17 編集

> Task Runを使用すれば、呼び出し元は延々とasyncキーワードにする必要が無いということでしょうか? いえ、やはり async / await は必要なはずです。 Task.Run メソッドは "スレッドプール上で実行する指定された作業をキューに配置し、その作業を表す Task オブジェクトを戻します" というものです。 例えば、同期メソッド SyncMethod() というものがあるとすると、それを UI スレッドとは別のスレッドで動かしたい場合には、 Task.Run(() => SyncMethod()) というようにします。そうすれば OS がスレッドプールからスレッドを取得して UI スレッドとは別のスレッドで実行してくれます。 しかし、await を付与しないと SyncMethod() の終了を待機しないので、 SyncMethod() は UI スレッドとは別のスレッドで勝手に走って行って終わってしまいます。あと、例外処理にも問題がありそうです。 それでよければ await は不要ですが、そういうケースは少ないのでは?
KotS

2021/08/17 05:07

そうですね。 2年前にVB.NETで自分で作成した稼働済みソースを見つけたのですが、 Dim Task_Proc As Task(Of Boolean) = Task.Run(Function() Return 同期関数 End Function) Dim res As Boolean = Await Task_Proc と結局awaitを使用して待っていました。 もちろん呼び出し元もasyncキーワードを使用していました。 先ほどの「【C#】ASYNC/AWAITで同期メソッドから非同期メソッドを呼ぶ方法」 リンクに倣ってbutton1_Clickイベントプロシージャを同期型の private void button1_Click(object sender, EventArgs e) { Console.WriteLine("button1_Click開始"); Task.Run(() => { return funcA(5000); }) .GetAwaiter() .GetResult(); Console.WriteLine("button1_Clickおわり"); } としてみましたが、結局5秒間はFormはドラッグできませんでした。 >マトリョーシカのごとくasyncメソッドを作り続けることになります。 も画面が固まらないような待ちが必要なときはやはり仕方がないということですよね?
退会済みユーザー

退会済みユーザー

2021/08/17 06:02

> 先ほどの「【C#】ASYNC/AWAITで同期メソッドから非同期メソッドを呼ぶ方法」 その記事は見ないことをお勧めします。GetAwaiter とか使っているのを見ただけでそっと閉じという感じです。 > としてみましたが、結局5秒間はFormはドラッグできませんでした。 それは UI スレッドをブロックすることになるので当たり前です。await は待機しますがブロックはしません。UI スレッドのメッセージループが動きますのでドラッグなどの操作は可能になるということです。 なので、フリーズして困るなら async / await 一択です。
退会済みユーザー

退会済みユーザー

2021/08/17 06:10 編集

Windows Forms アプリで、async/await/Task を使って同期メソッドを非同期で呼び出して、フリーズさせないようにしたサンプルコードを紹介しておきます。 以下の記事の button3_Click メソッドを見てください。例外処理にも注目してください。 デリゲートを利用した非同期メソッドの実装 http://surferonwww.info/BlogEngine/post/2019/06/19/coding-asynchronous-method-by-using-delegate-in-windows-forms-application.aspx
KotS

2021/08/17 08:19

SurferOnWww様 有用なサンプルリンク、ありがとうございます。 textbox1をそのままにした場合と空にした場合で 3つのボタンでテストしてみました。 textbox1をそのままにした場合、 button1は同期待ちなので固まり、button2とbutton3は同様に非同期待ちが実現できているようでした。 textbox1を空にした場合 button1の同期待ちはbutton1_Clickプロシージャ内でのエラーの捕捉とmainルーチンでの集約捕捉ができました。 button2のDelegateはbutton2_Clickプロシージャ内ではなく、MyCallBackでのエラー捕捉ができて、mainルーチンでも集約捕捉はできないようでした。 button3はasync/awaitではbutton3_Clickプロシージャ内でのエラーの捕捉とmainルーチンでの集約捕捉もできました。 button3はasync/awaitはエラー処理も同期処理並みに書きやすくなるのですね。 エラーの集約捕捉処理もかつてVB.NETでエラーの呼び出し履歴的にログを取ったことがありますので、 それを実現する上で大変参考になりそうです。 ありがとうございました。
guest

0

自己解決

皆様、ありがとうございます。

今回のasync/awaitの使用についてまとめますと、
プロシージャにasyncキーワードを使用するとプロシージャ内で
①同期関数を使用して同期処理をしても問題ない。通常の同期処理動作になる。
②非同期関数の場合、非同期待ちが可能になる。
③同期サブルーチン/同期関数を非同期で待ちたいときは、
private async void button1_Click(object sender, EventArgs e)イベントプロシージャ内で

5秒待ちの同期サブルーチンを非同期待ちしたいときは、

private async void button1_Click(object sender, EventArgs e) { await Task.Run(() => { System.Threading.Thread.Sleep(5000); }); }

5秒待ちの同期関数を非同期待ちして戻り値が必要なときは、

private async void button1_Click(object sender, EventArgs e) { Task<int> Task_Proc = Task.Run(() => { return funSyncDelay(5000); }); await Task_Proc; Console.WriteLine("Task_Proc={0}", Task_Proc.Result); } public int funSyncDelay(int nMillisecond) { System.Threading.Thread.Sleep(nMillisecond); return 1; }

ということで、プロシージャ内で非同期待ちをする可能性があればひとまず
asyncキーワードをプロシージャ名に付加しておきましょうということですよね?

投稿2021/08/17 08:44

編集2021/08/17 08:46
KotS

総合スコア7

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

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

退会済みユーザー

退会済みユーザー

2021/08/17 09:18 編集

表題の「DoEventsの代わりのawait使用について」にはすでに答えが出てますので、このスレッドはクローズしてください。 上記のことは別に新しいスレッドを立てて質問してください。一つの課題の解決が次の疑問を生んで、同じスレッドの中で次々質問が増えていくというのは避けてください。ここは情報の蓄積も目的としているそうで、後から検索などでここを訪れた人には訳が分からなくなりますので。 また、質問者さんが回答欄に書くと訳が分からなくなるので、それも避けていただきたく。
KotS

2021/08/18 00:28

承知しました。 今後は気を付けます。
退会済みユーザー

退会済みユーザー

2021/08/18 00:52

どうして質問者さんが自分で書いた上記が「解決した方法」になるのですか? 解決に最も役に立った回答に「ベストアンサー」をつけてクローズすべきです。 役に立った回答は一つもなくて、自分で解決策を考えてそれで自己解決したということなら話は別ですが、そういうことではないはず。
KotS

2021/08/18 00:56

要するに簡単にまとめておきたかったのですが、解決方法欄に書けば良いのでしょうか? 「ベストアンサー」は先ほど付けてみました。 削除した方が良いのであればそのようにいたします。
退会済みユーザー

退会済みユーザー

2021/08/18 01:05

>「ベストアンサー」は先ほど付けてみました。 それは + 評価です。ベストアンサーではありません。 質問者さん、Teratail は初めてで流儀が分からないのかと思ってたら、5 か月前に https://teratail.com/questions/323961 で質問して、その時はちゃんとベストアンサーを付けているじゃないですか? そのようにやってください。 > 削除した方が良いのであればそのようにいたします。 削除する必要はないですが、できれば自分の質問欄の下の方に追記する形で「ここでの Q&A での自分の理解のまとめ」という形で書いていただければと思います。
guest

0

async-awaitにするだけで解決するのであれば良いのですが、同じ処理が複数走ってしまったら問題があるケースもあると思うので、元のプログラムの固まる仕様・原因は確認しておいた方がいいと思います。(マルチスレッド対応していない処理が並列に走ったりして問題を起こすケースも考えられるので)

VB6の頃はマルチスレッド対応していないので、時代背景的に仕方なくループ処理中にDoEventsを挟む事で強制的にウィンドウメッセージのキューを処理して無理矢理UIを操作出来るようにしているプログラムがありましたが、UI操作以外のメッセージも存在しますし、メッセージの処理順も滅茶苦茶になるので、実際は割と危険な行為です。

投稿2021/08/17 05:38

退会済みユーザー

退会済みユーザー

総合スコア0

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

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

KotS

2021/08/17 08:24

radian様 ご回答ありがとうございます。 そうですね。スレッドセーフなものでマルチスレッドを実現する必要がありますね。 確かに今回使用するDLLもマルチスレッド不可な関数がマニュアルに明記してありました。 VB6の頃はとりあえずDoEventsで溜まっている処理を解放、あるいは他のタイマーイベントに処理を移動的なことをよくしていたのですが、順序が保証されないことがあるのですね。 ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問