LoginSignup

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

呼び出されたクラスでの処理進捗を、呼び出し側のFormクラスで表示したい(値の渡し方)

Q&AClosed

解決したいこと

Formクラスから、「別.cs」ファイルの画像処理クラスを呼び出して処理(処理時間10秒程)しています。
呼び出された画像処理クラスでの処理進捗を、呼び出し側のFormクラスで表示したいです。
エラーではないですが…書いていて違うんじゃないかと思って質問しました。
こういった場合、スレッドでしょうか?
宜しくお願いします。

 環境など:Win10+VisualStudio2015
 言語など:C#、クラシックと呼ばれて久しいフォームアプリ

発生している問題・エラー

エラーではないですが…ショボいコードは以下の通りです。

該当するソースコード

  1. Formクラスで、
      ・Formのインスタンスを公開(Public)
      ・「処理ピクセル数」をプロパティ(int)として公開(Public)
  2. 画像処理クラスで、随時「処理済画素数」を下書込む。
  3. Formクラスで、タイマーを使って進捗を表示

Formクラス側↓

    public partial class Formメイン : Form
    {

        //親インスタンスの定義とプロパティ定義
        private static Formメイン formMainInstance;
        //値の設定はFormMain_Loadにて ⇒ FormMain.formMainInstance = this;) 
        public static Formメイン _FormMainInstance
        {
            get { return formMainInstance; }
            private set { formMainInstance = value; }
        }

        private int p処理済画素数; 
        public int _StatusDone
        {
            get { return p処理済画素数; }
            set { p処理済画素数 = value; }
        }

  ・・・省略・・・

     //呼出部分の抜粋↓
        Class画像処理 c = new Class画像処理();
        result = c._ImageMatching(画像のパス);

画像処理クラス側(Formクラスのプロパティに書込む部分)↓

        //検索完了した画素数をインクリメント!!!
        Formメイン._FormMainInstance._StatusDone += 1;  

自分で試したこと

 上記のコード

0

6Answer

書いていて違うんじゃないかと思って質問しました。

画像処理クラスのメソッドを同期的に UI スレッドで実行しているように見えますが、そうであれば違います。

画像処理クラスのメソッドで処理が終わるまで UI スレッドをブロックしてアプリがフリーズしたようになってしまいませんか?

こういった場合、スレッドでしょうか?

その意味が「非同期プログラミングで画像処理は別スレッドで行う」ということであればその通りです。

解決策は非同期アプリケーションにすることです。

Visual Studio 2015 でも .NET Framework 4.5 以降のフレームワークが使えるので、async / await を利用して比較的容易に非同期アプリケーションを開発できます。

具体例は以下の記事を見てください。IProgress インターフェースを利用しての進捗状況の表示方法も書いてあります。

WPF/Windowsフォーム:時間のかかる処理をバックグラウンドで実行するには?(async/await編)
https://atmarkit.itmedia.co.jp/ait/articles/1512/02/news019.html

3

画像処理クラスから進捗表示クラス(Form)を直接参照するのは好ましい造りではないです。
IProgressなどのインターフェースを経由した疎結合な造りの方が良いでしょう。

↑を利用した場合、

  • FormでIProgressを実装。Reportの引数を元に表示を更新する。
  • 画像処理クラスにIProgress(Form)を渡して、適度なタイミングでReportを呼び出す。

という感じになります。
画像処理クラスが進捗を報告したタイミングで更新されるためタイマーを使う必要もないです。

2

Comments

  1. わざわざタイマが用いられているのは報告の粒度(頻度)に問題があるからかな? とか勝手に想像(報告の頻度 >>> 表示更新の頻度 にしたい,的な).

    まぁそうだとしても「N回に1回だけ表示更新」とか何とかすればそれで済むだけの話ですが.

タイマーを使って進捗を表示

なる仕組みで実際に表示更新できているのでしょうか?

こういった場合、スレッドでしょうか?

あなただけが個人的に使うプログラムなら「どうでもいい」と思います.
目的が画像処理の部分を走らせることだけであり,GUIの作り込みたいなのは 本質的な/必要な 部分ではない場合,そこになんやかんやこだわっても意味無いので.

そうでない場合:例えば,他者にも使わせるので10秒みたいなレベルで時間がかかる処理を行う間ずっと「固まっている」のはよろしくないとか,システムから「応答なし」認定されるのが嫌だとかいうのであれば,別スレッドでやることを考えればよいでしょう.

1

Comments

  1. コードの「書き方」みたいな観点で言えば,私が最初に「違うんじゃないか」と思うのは

    Formのインスタンスを公開(Public)

    の部分ですかね.
    単に,画像処理者に「進捗はこれに報告しろ」と引数なりで指定すればそれで済むんじゃないでしょうか.

    進捗に関しては「処理ピクセル数」という具体的な単位で知る必要があるのかどうかはわかりませんが,特にそこに必要性がない場合(何かしら進んでいることがわかればそれで良いような場合),もっとざっくりとした値(例えばパーセンテージみたいなのとか,画素じゃなくて行にするとか)で進捗報告させるのでも良いかもしれません.
    (ピクセル毎というのは報告頻度が無駄に高いような気がするので)

やり方はいろいろあるでしょうし、賛否もあるでしょうが、
手っ取り早い方法としては、

別フォームのコンストラクタの引数にForm1型の引数を与えておくと、
別フォームのインスタンスを生成する際に、Form1への参照(this)を渡せます。

Form1.cs(呼び出す側)
     private Form2 f2;
     ...
     ...
     //別フォームクラスのインスタンスを作成する
     f2 = new Form2(this);
Form2.cs(別フォーム側)
    public partial class Form2 : Form
    {
        private Form1 f1;

        public Form2(Form1 f)
        {
            f1 = f;
            ...
            ...
        }

       ...
       private void 別メソッド()
       {
           f1._FormMainInstance._StatusDone += 1;
       }
1

Comments

  1. 質問文中で「画像処理クラス」とされている物をわざわざ「別フォーム」なる別の存在に置き換えて話をする必要はないのでは?

  2. @NyancoRitterさん
    わざわざ私のコメントにレスして、さらに自分のコメントへのレスに補足するほどでもないと思うのですが、
    タイトル、本文、引用部では使ってる単語が異なっていますよね。

今どきだと「Task.Run()などでバックグラウンド実行させてIProgressなどで通知」とする方が望ましいですが、ちょっととっつきにくさはあると思います。
それより古い方法ですが「BackgroundWorkerを利用する」だと参考にできる情報が多いこともあってお手軽かもしれません。
(ネットで検索するとVisualStudio2015だとできないやり方も多そうですしね)

1

Comments

  1. それより古い方法ですが「BackgroundWorkerを利用する」だと参考にできる情報が多いこともあってお手軽かもしれません。(ネットで検索するとVisualStudio2015だとできないやり方も多そうですしね)

    Visual Studio 2015 なら .NET Framework 4.5 をフレームワークにしたアプリを開発でき、async / await が使えます。なので、BackgroundWorker を利用するのは決してお手軽ではないです。

    以下の記事の「Figure 3: .NET Frameworkにおける非同期処理システムの歩み」の図にあるように、非同期プログラミングのやり方は進化しています。

    .NET開発における非同期処理の基礎と歴史
    https://atmarkit.itmedia.co.jp/fdotnet/chushin/masterasync_01/masterasync_01_02.html

    BackgroundWorker は上に紹介した記事の図では Event-based 時代のものです。何か特別な事情・理由があればともかく、少なくとも今回の質問のケースではそこまで戻る必要はないはずです。

皆さま:
お忙しい中、ご指導ありがとうございます。
折角の機会なので、避けてきた「非同期プログラミング」に挑戦してみます。

1

Comments

  1. 頑張ってください。

    以下の記事の「Figure 3: .NET Frameworkにおける非同期処理システムの歩み」の図にあるように、非同期プログラミングのやり方は進化しています。async / await が使える今は敷居が下がっていて、避けなくてもよさそうです。

    .NET開発における非同期処理の基礎と歴史
    https://atmarkit.itmedia.co.jp/fdotnet/chushin/masterasync_01/masterasync_01_02.html

    後は自力で考えてみるということと理解していますが、これ以上質問がなければこのスレッドはクローズ願います。(オープンのまま放置しないようお願いします)

  2. @OhiKazuma

    Questioner

    @SurferOnWwwさん:
    紹介頂いた「async/await編」試して理解できました。
    まさしく「欲しいコード」でした。
    来週、実際のコードを触ります。

    約10年前のVS2015でasync/awaitを使えて良かったです。
    「BackgroundWorker」の噂が、抵抗感を起こしていたのかもしれませんね。

    次は脱DoEventsです(o^^o)

    ありがとうございました。
    これにてクローズします。

Your answer might help someone💌