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

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

ただいまの
回答率

89.69%

重い処理の処理中に、UI操作を行いたいのですが、処理がブロックされ上手く行きません。

解決済

回答 3

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 1,034

firstlast

score 70

前提・実現したいこと

重い処理の処理中に、UI操作を行いたいのですが、処理がブロックされ上手く行きません。

ブロックされないようにするために、ソースコードには
・イベントプロシージャにasync を付ける。
・重い処理(WEBページアクセス)をawait Task.Runの中に記述する。
を反映しています。

どのようにすれば、ブロックされずにUI操作できるようになりますでしょうか?

よろしくお願いします。

下に画面イメージとソースコードを載せておきます。
左のボタンがWEBページにアクセスするボタン
右のボタンがUI操作を確認するためのボタンです。

ソースコードは、Windows Formsアプリ、及びC#です。
イメージ説明

該当のソースコード

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.Net;
using System.IO;
using System.Text.RegularExpressions;

namespace Asynctest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private async void Button1_Click(object sender, EventArgs e)
        {
            const string url = @"http://192.168.0.99/";    //レスポンスを遅くするために、意図的にこのURLにアクセス

            //スレッド起動
            await Task.Run(() =>
            {
                //UIスレッドにアクセスするためにInvokeを使用
                this.Invoke(new Action(() =>
                {
                    //Task.Delay(1000).Wait();
                    byte[] data;
                    WebRequest req = null;
                    WebResponse res = null;
                    string str = null;

                    try
                    {
                        req = WebRequest.Create(url);
                        res = req.GetResponse();
                        Stream st = res.GetResponseStream();
                        data = ReadBinaryData(st);
                        str = System.Text.Encoding.GetEncoding("csISO2022JP").GetString(data);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex.Message);
                        listBox1.Items.Insert(listBox1.Items.Count , ex.Message);
                        return;
                    }
                    finally
                    {
                        if (res != null) res.Close();
                    }
                    listBox1.Items.Insert(listBox1.Items.Count, str);
                }));

            });

        }

        static byte[] ReadBinaryData(Stream st)
        {
            byte[] buf = new byte[32768];
            using (MemoryStream ms = new MemoryStream())
            {
                while (true)
                {
                    int r = st.Read(buf, 0, buf.Length);

                    //一時バッファの内容をメモリ・ストリームに書き込む
                    if (r > 0)
                    {
                        ms.Write(buf, 0, r);
                    }
                    else
                    {
                        break;
                    }
                }
                return ms.ToArray();
            }
        }

        //リストボックスに1行追加するだけ
        private void Button2_Click(object sender, EventArgs e)
        {
            listBox1.Items.Insert(listBox1.Items.Count - 1, "!");

        }
    }
}

問題解決後のソースコード

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.Net;
using System.IO;
using System.Text.RegularExpressions;

namespace Asynctest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private async void Button1_Click(object sender, EventArgs e)
        {
            const string url = @"http://192.168.0.99/";

            // スレッドを起動し、以降のコードを実行する前に呼び出し元へ戻る
            await Task.Run(() =>
            {
                //非同期で実行したい重い処理
                //Task.Delay(1000).Wait();
                byte[] data;
                WebRequest req = null;
                WebResponse res = null;
                string str = null;

                try
                {
                    req = WebRequest.Create(url);
                    res = req.GetResponse();
                    Stream st = res.GetResponseStream();
                    data = ReadBinaryData(st);
                    str = System.Text.Encoding.GetEncoding("csISO2022JP").GetString(data);
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);

                    //UI要素へのアクセス(非同期で実行したい処理と混ぜずに、単独で行う。)
                    this.Invoke(new Action(() => {
                        listBox1.Items.Insert(listBox1.Items.Count, ex.Message);
                    }));
                    return;
                }
                finally
                {
                    if (res != null) res.Close();
                }
                //UI要素へのアクセス(非同期で実行したい処理と混ぜずに、単独で行う。)
                this.Invoke(new Action(() =>   {
                    listBox1.Items.Insert(listBox1.Items.Count, str);
                }));

            });

        }

        static byte[] ReadBinaryData(Stream st)
        {
            byte[] buf = new byte[32768];
            using (MemoryStream ms = new MemoryStream())
            {
                while (true)
                {
                    int r = st.Read(buf, 0, buf.Length);

                    //一時バッファの内容をメモリ・ストリームに書き込む
                    if (r > 0)
                    {
                        ms.Write(buf, 0, r);
                    }
                    else
                    {
                        break;
                    }
                }
                return ms.ToArray();
            }
        }

        private void Button2_Click(object sender, EventArgs e)
        {
            listBox1.Items.Insert(listBox1.Items.Count, "!");

        }
    }
}

環境

Microsoft Windows 10 Pro (Version 1809)
Microsoft Visual Studio Community 2017(Version 15.9.4)
Microsoft .NET Framework(Version 4.7.03190)

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

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

  • SurferOnWww

    2019/06/07 15:21

    HttpClient は使えないのでしょうか?

    キャンセル

  • firstlast

    2019/06/07 18:23

    返信ありがとうございます。
    HttpClient は使えると思います。

    キャンセル

  • SurferOnWww

    2019/06/08 13:02

    問題解決後のソースコードですが、await Task.Run(() => { ... }); で全部囲わないで、時間のかかる処理即ち、

    res = req.GetResponse();
    st = res.GetResponseStream();

    だけ囲って、Invoke は使わなくても済むようにできると思います。例外が発生した際のエラーメッセージが期待通り取れるかは要検討ですが。

    お試しください。

    キャンセル

回答 3

checkベストアンサー

+1

まずは、要求の分析・設計をしなければなりません。UIが絡む処理と、絡まない処理を、「完全に」分離します。絡まない処理をした後、UIの更新のみ、Control.Invokeから実行します。
Taskで分離したはいいが、すぐにForm.Inovkeでは、またメインスレッドで処理しています。
SurferOnWwwさんの回答にあるコメントでは、質問にあるコードが直接解決したい内容ではないみたいなので、この辺の大雑把な回答までにしておきます。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/06/08 00:23

    返信ありがとうございます。
    アドバイスいただいた内容で、一度実装してみて検証してみます。

    キャンセル

  • 2019/06/08 10:44 編集

    アドバイスいただいた内容で、無事、重い処理を非同期で実行することができました。
    重い処理をUIスレッドで実行していたのが原因だったようです。(わざわざTask.Runで別スレッド起動しているのに...)

    修正後のソースは、質問内容の本文の方に付け加えておきます。

    キャンセル

  • 2019/06/08 10:49 編集

    .

    キャンセル

  • 2019/06/08 20:01

    お礼を忘れていました。
    ありがとうございました。

    キャンセル

0

Invokeって、確かWin32APIでいうSendMessage相当の処理なので、処理が終わるまで応答が返ってこないはずです。代わりにPostMessage相当のBeginInvokeを使えば解決しませんか?

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/06/07 18:32 編集

    BeginInvokeについてネットで実装方法を検索してみたのですが、よくわかりませんでした。
    より具体的なコーディング方法について言及は可能でしょうか?
    お願いします。

    キャンセル

  • 2019/06/07 18:38 編集

    BeginInvokeの実装方法について、ウェブページ
    https://www.atmarkit.co.jp/fdotnet/chushin/masterasync_01/masterasync_01_02.html
    を見つけました。しかし、「.NET Framework 1.1時代からある最も原始的なThreadクラス(System.Threading名前空間)を用いた方法」とのことです。・・・●Thread(スレッド)の節

    因みに今回、私がやろうとしているのは、上記ウェブページの●async/await の節の内容になろうかと思います。

    キャンセル

  • 2019/06/08 11:15

    無事解決致しました。
    KoichiSugiyamaさん、ご協力ありがとうございました。

    キャンセル

  • 2019/06/08 11:19 編集

    要は、重い処理を別スレッドで実行するときに、UI処理を混ぜてしまっていたために、別スレッドになっていなかったということでした。もしかしたら、内部的にSendMessageやPostMessageが絡んでいるのかもしれませんね。

    キャンセル

0

質問に対する私のコメント、

HttpClient は使えないのでしょうか?

に返事がないので、質問者さんの環境で HttpClient が使えるのかどうか分かりませんが、とりあえず参考になりそうな記事を紹介しておきます。

HttpClient Class
https://docs.microsoft.com/ja-jp/dotnet/api/system.net.http.httpclient?view=netframework-4.8

HttpClientクラスでWebページを取得するには?[C#、VB]
https://www.atmarkit.co.jp/ait/articles/1501/06/news086.html

そもそも非同期で使うようにできているので、質問者さんの目的は容易に果たせるのではないかと思います。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

  • 2019/06/08 11:23

    具体的にどのようにした解決したのか書いていただけませんか? 後から検索などでここにたどり着いた人の役に立つ(このスレッドの価値を高める)ために。

    > ただ私の興味は、
    > await Task.Run(() =>
    > {
    > this.Invoke(new Action(() =>
    > {
    > こっちの書き方の方でして...

    にこだわっていては解決できないので、別の手段を取ったのではないかと想像してます。

    キャンセル

  • 2019/06/08 11:25

    修正後のソースを当質問の本体の末尾に付け加えましたので、そちらをご参照ください。

    キャンセル

  • 2019/06/08 11:30

    了解しました。解決したコードの追記ありがとうございました。

    キャンセル

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

  • ただいまの回答率 89.69%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる
  • トップ
  • C#に関する質問
  • 重い処理の処理中に、UI操作を行いたいのですが、処理がブロックされ上手く行きません。