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

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

ただいまの
回答率

87.34%

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

解決済

回答 3

投稿

  • 評価
  • クリップ 1
  • VIEW 1,104

score -2

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

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画面のどこかを右クリックして、[コードの表示]をクリックします。
表示された、

public Form1()
{
    InitializeComponent();
}


の次行に、
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やスレッド関係は勉強中なのですが、ご教示のほどよろしくお願いいたします。

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

質問への追記・修正、ベストアンサー選択の依頼

  • Zuishin

    2021/08/17 14:23 編集

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

    キャンセル

  • KotS

    2021/08/17 14:27

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

    キャンセル

  • SurferOnWww

    2021/08/17 18:17

    表題の「DoEventsの代わりのawait使用について」にはすでに答えが出てますので、このスレッドはクローズしてください。

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

    また、質問者さんが回答欄に書くと訳が分からなくなるので、それも避けていただきたく。

    キャンセル

回答 3

+1

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

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

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

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

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

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

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2021/08/17 15:02

    > 先ほどの「【C#】ASYNC/AWAITで同期メソッドから非同期メソッドを呼ぶ方法」

    その記事は見ないことをお勧めします。GetAwaiter とか使っているのを見ただけでそっと閉じという感じです。

    > としてみましたが、結局5秒間はFormはドラッグできませんでした。

    それは UI スレッドをブロックすることになるので当たり前です。await は待機しますがブロックはしません。UI スレッドのメッセージループが動きますのでドラッグなどの操作は可能になるということです。

    なので、フリーズして困るなら async / await 一択です。

    キャンセル

  • 2021/08/17 15:09 編集

    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

    キャンセル

  • 2021/08/17 17: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でエラーの呼び出し履歴的にログを取ったことがありますので、
    それを実現する上で大変参考になりそうです。
    ありがとうございました。

    キャンセル

0

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

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

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2021/08/17 17:24

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

    キャンセル

check解決した方法

-2

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

今回の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/18 09:52

    どうして質問者さんが自分で書いた上記が「解決した方法」になるのですか? 解決に最も役に立った回答に「ベストアンサー」をつけてクローズすべきです。

    役に立った回答は一つもなくて、自分で解決策を考えてそれで自己解決したということなら話は別ですが、そういうことではないはず。

    キャンセル

  • 2021/08/18 09:56

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

    キャンセル

  • 2021/08/18 10:05

    >「ベストアンサー」は先ほど付けてみました。

    それは + 評価です。ベストアンサーではありません。

    質問者さん、Teratail は初めてで流儀が分からないのかと思ってたら、5 か月前に https://teratail.com/questions/323961 で質問して、その時はちゃんとベストアンサーを付けているじゃないですか? そのようにやってください。

    > 削除した方が良いのであればそのようにいたします。

    削除する必要はないですが、できれば自分の質問欄の下の方に追記する形で「ここでの Q&A での自分の理解のまとめ」という形で書いていただければと思います。

    キャンセル

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

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

関連した質問

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