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

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

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

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

Visual Studio

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

Windows Forms

Windows Forms(WinForms)はMicrosoft .NET フレームワークに含まれる視覚的なアプリケーションのプログラミングインターフェイス(API)です。WinFormsは管理されているコードの既存のWindowsのAPIをラップすることで元のMicrosoft Windowsのインターフェイスのエレメントにアクセスすることができます。

Q&A

解決済

3回答

14994閲覧

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

firstlast

総合スコア138

C#

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

Visual Studio

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

Windows Forms

Windows Forms(WinForms)はMicrosoft .NET フレームワークに含まれる視覚的なアプリケーションのプログラミングインターフェイス(API)です。WinFormsは管理されているコードの既存のWindowsのAPIをラップすることで元のMicrosoft Windowsのインターフェイスのエレメントにアクセスすることができます。

0グッド

0クリップ

投稿2019/06/07 06:05

編集2019/06/08 02:27

前提・実現したいこと

重い処理の処理中に、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)

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

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

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

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

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

退会済みユーザー

退会済みユーザー

2019/06/07 06:21

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

2019/06/07 09:23

返信ありがとうございます。 HttpClient は使えると思います。
退会済みユーザー

退会済みユーザー

2019/06/08 04:02

問題解決後のソースコードですが、await Task.Run(() => { ... }); で全部囲わないで、時間のかかる処理即ち、 res = req.GetResponse(); st = res.GetResponseStream(); だけ囲って、Invoke は使わなくても済むようにできると思います。例外が発生した際のエラーメッセージが期待通り取れるかは要検討ですが。 お試しください。
guest

回答3

0

ベストアンサー

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

投稿2019/06/07 12:45

Q71

総合スコア995

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

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

firstlast

2019/06/07 15:23

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

2019/06/08 01:58 編集

アドバイスいただいた内容で、無事、重い処理を非同期で実行することができました。 重い処理をUIスレッドで実行していたのが原因だったようです。(わざわざTask.Runで別スレッド起動しているのに...) 修正後のソースは、質問内容の本文の方に付け加えておきます。
firstlast

2019/06/08 11:01

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

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/07 08:37

退会済みユーザー

退会済みユーザー

総合スコア0

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

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

firstlast

2019/06/07 09:28

質問したかったことは、重い処理を別スレッドでどうすれば非同期で実行できるかなので、HttpClientを使うことで重くない処理になってしまって、期待してたことでなくなります。^^;
firstlast

2019/06/07 09:30

サンプルコードが適切でなかったかもしれません。
退会済みユーザー

退会済みユーザー

2019/06/07 09:45

> HttpClientを使うことで重くない処理になってしまって、期待してたことでなくなります。 意味が分かりません、何か思い違いをしているような気がします。 非同期にするのは UI 応答性の向上(フリーズしてしまうようになるのを避ける)です。質問に「WEBページの読み込み中に、UI操作を行いたい」と書いてあったので、それが目的と思っていましたが、違うのですか? 元々のコードも非同期にすることにより UI 応答性の向上を図りたいということのように思えましたが、違うのですか? 一体何をしたいのですか?
退会済みユーザー

退会済みユーザー

2019/06/07 09:59 編集

ちなみに「HttpClientを使うことで重くない処理に」はならないはず。重いのは Web サーバーに要求を出して、Web サーバーが要求を処理して、応答が返ってくるまでで、そこは HttpClient とは関係ないく WebRequest / WebResponse でも同じはずです。
firstlast

2019/06/07 09:52

質問の説明が適切でなくすみませんでした。 【誤】WEBページの読み込み中に、UI操作を行いたいのですが、処理がブロックされ上手く行きません。 【正】重い処理の処理中に、UI操作を行いたいのですが、処理がブロックされ上手く行きません。 です。
退会済みユーザー

退会済みユーザー

2019/06/07 09:54

ますます意味不明です。あなたの言う「重い処理」というのは具体的に何のですか?
firstlast

2019/06/07 10:00

「重い処理の処理中に、UI操作を行いたいのですが、処理がブロックされ上手く行きません。」 非同期処理をするにはどうしたらいいか と言い換えることもできるかと思います。 理解できました?
退会済みユーザー

退会済みユーザー

2019/06/07 10:06 編集

だから HttpClient を使ってはと言ってるのですが。あなたが理解できてないと思いますよ。 今のあなたのコードでは非同期になってないから UI 操作ができないのですよ。分かります?
firstlast

2019/06/07 10:07

WEBページにアクセスするのが目的ではないのですが、私の質問に対する本質的な回答に成り得ますか?
退会済みユーザー

退会済みユーザー

2019/06/07 10:18 編集

では何が目的なのですか? ますます分かりません。 アクセスすること自体は目的ではなくても、前にも書いたように「Web サーバーに要求を出して、Web サーバーが要求を処理して、応答が返ってくるまで」にフリーズするのは困ると言っているのでは? そうでなかったなら何なのですか?
退会済みユーザー

退会済みユーザー

2019/06/07 10:16

あなたのコードでは非同期にならないので、応答が返ってくるまで UI 操作ができないのですよね。だったら、非同期にするのがより簡単な HttpClient を使ってはどうかと言っているのですが、分かりませんか?
firstlast

2019/06/07 10:37 編集

ありがとうございます。 HttpClientを使うことで、UI操作ができるようになるよっておっしゃっているのは理解しています。 ただ私の興味は、 await Task.Run(() => { this.Invoke(new Action(() => { こっちの書き方の方でして...
退会済みユーザー

退会済みユーザー

2019/06/07 10:41

ならば、 > 返信ありがとうございます。 > HttpClient は使えると思います。 という返事は何だったのですか? なぜ最初の質問で「私の興味」を書かなかったのですか?
Zuishin

2019/06/07 11:58

Invoke を使うことによって中身が UI スレッドで動きます。つまり、中身が重ければ UI スレッドが止まります。重い処理はたいていループなので、その場合はループの中で Application.DoEvents を呼べばうまくいくと思いますが、ウェブコンテンツの取得はそうではありません。なので、重い処理の内容によって方法を変えなければならないことになります。
firstlast

2019/06/07 14:09

返信ありがとうございます。 別スレッドに処理を書きさえすれば、UIスレッドは止まらない、と思い込んでいました。 UI要素にアクセスすると結局止まってしまうんですね... <今後の方針> ワーカースレッドがUI要素にアクセスする代わりに、UIスレッドのメソッドを呼び出してUI要素にアクセスさせれば、やろうとしてたことが実現できるのではと仮説を立て実証しようと思っています。 ※UIスレッドが起動したスレッドはワーカースレッドと言うんですね
Zuishin

2019/06/07 14:15 編集

UI スレッドのメソッドというものは存在しません。メソッドはそれを呼び出したスレッドに所属します。
firstlast

2019/06/07 14:21 編集

ワーカープロセスに仕事をやらせて、その結果を画面に反映するといった処理は、ありがちだと思うのですが、一般的にはどのように実現しているものなのでしょうか... もちろん画面をロックせずにです。
Zuishin

2019/06/07 15:02 編集

この質問で仕事をしているのはワーカースレッドではなく UI スレッドです。なので前提が間違っています。 UI スレッドから仕事を切り離す回答が三つ出ています。私は回答していないのでここまでにさせていただきますが、先に述べたように、処理内容によって方法を変える必要があります。
firstlast

2019/06/07 15:24 編集

>この質問で仕事をしているのはワーカースレッドではなく UI スレッドです。 await Task.Run( の中の処理が、UI要素にアクセスしなければワーカースレッドになるという理解ですが間違っていますか?
YAmaGNZ

2019/06/07 22:45

Task.Runでワーカースレッドで動作しますが、あげられているサンプルではすぐにInvokeしているのでUIスレッドでの動作となっています。 このため皆さんはバックグラウンドで動作させる処理とUIの更新をきっちり分離して設計しましょうと言われています。
firstlast

2019/06/08 01:35

YAmaGNZさん 返信ありがとうございます。 await Task.Run( の中の処理は、ワーカースレッドにもUIスレッドにもなり得るということですね。
firstlast

2019/06/08 02:03

無事解決致しました。 SuferOnWwwさん、Zuishinさん、YAmaGNZさん、ご協力ありがとうございました。m(_ _)m
退会済みユーザー

退会済みユーザー

2019/06/08 02:23

具体的にどのようにした解決したのか書いていただけませんか? 後から検索などでここにたどり着いた人の役に立つ(このスレッドの価値を高める)ために。 > ただ私の興味は、 > await Task.Run(() => > { > this.Invoke(new Action(() => > { > こっちの書き方の方でして... にこだわっていては解決できないので、別の手段を取ったのではないかと想像してます。
firstlast

2019/06/08 02:25

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

退会済みユーザー

2019/06/08 02:30

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

0

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

投稿2019/06/07 06:37

KoichiSugiyama

総合スコア3041

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

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

firstlast

2019/06/07 09:33 編集

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

2019/06/07 09:48 編集

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

2019/06/08 02:15

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

2019/06/08 02:21 編集

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

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

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

質問する

関連した質問