C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[3]: プログレスバーの処理遅延


(過去ログ 137 を表示中)

[トピック内 13 記事 (1 - 13 表示)]  << 0 >>

■80686 / inTopicNo.1)  プログレスバーの処理遅延
  
□投稿者/ 真田昌幸 (15回)-(2016/08/03(Wed) 10:05:43)

分類:[VB.NET/VB2005 以降] 

元号対応をしようとしているシステムで、
プログレスバーでバーのカウントアップの前に処理が終了するが多発しているそうです。

環境としては
OS :Win7
クラス構成:VB.netのラッパークラス→user32.dll参照

なので、根本原因はVista以降の処理遅延問題と想定されます。
「バーの目盛を無理やり一個すすめる」等の力技以外に、
手軽な改善方法があればご教示お願いします。
引用返信 編集キー/
■80687 / inTopicNo.2)  Re[1]: プログレスバーの処理遅延
□投稿者/ 魔界の仮面弁士 (795回)-(2016/08/03(Wed) 10:37:24)
No80686 (真田昌幸 さん) に返信
> プログレスバーでバーのカウントアップの前に処理が終了するが多発しているそうです。
> クラス構成:VB.netのラッパークラス→user32.dll参照

現状の実装が分からないので、具体的な話はできませんが、
UI スレッドで時間のかかる処理を行われているのでは無いでしょうか。
それで、ProgressBar の更新が反映されていないとか。

だとしたら、ラッパークラスを非同期メソッドとして実装するか、
もしくは呼び出し側が BackgroundWorker なり Async/Await なりで
スレッドを分けて実行させるのが現実的かと思います。
引用返信 編集キー/
■80689 / inTopicNo.3)  Re[2]: プログレスバーの処理遅延
□投稿者/ 真田昌幸 (16回)-(2016/08/03(Wed) 11:48:35)
早速のご回答ありがとうございます。

No80687 (魔界の仮面弁士 さん) に返信
> ■No80686 (真田昌幸 さん) に返信
>>プログレスバーでバーのカウントアップの前に処理が終了するが多発しているそうです。
>>クラス構成:VB.netのラッパークラス→user32.dll参照
>
> 現状の実装が分からないので、具体的な話はできませんが、
> UI スレッドで時間のかかる処理を行われているのでは無いでしょうか。
> それで、ProgressBar の更新が反映されていないとか。

System.Windows.Forms.ProgressBarを継承しつつ、
情報を付加して、GetWindowLongとかSetWindowLongを呼んで処理を実装しているようです。
画面作成者はラッパークラスのコントロールを配置して使っているだけの意識になっていると思います。

画面のイベントとしては、コマンドボタンの処理中に、表示させている感じで、
ShownとかActivateとかイベント自体が重い処理と同時にやっているわけではありません。
重い要素があるとすれば、
Loopの中でRefreshしていることと、DBの更新と同時にやっていることくらいです。
現象としては、ProgressBarの目盛が全く進まない状態で本処理が終了し完了メッセージが出てしまうイメージ。

> だとしたら、ラッパークラスを非同期メソッドとして実装するか、
> もしくは呼び出し側が BackgroundWorker なり Async/Await なりで
> スレッドを分けて実行させるのが現実的かと思います。

本来論で言うとラッパークラスの改善でしょうが、
元号対応で予算の大半を使うと思われるため、
おそらくそこに手は出せません。
プロパティ設定の(画面側での)タイミングの変更とか、
あるいは環境面の改善で何とかなるかとかですね。
「手軽な」の意味は。
単に見た目の違和感で、業務ロジック的な不具合ではないので、
抜本的改善のGoサインは出にくいと思います。


引用返信 編集キー/
■80696 / inTopicNo.4)  Re[2]: プログレスバーの処理遅延
□投稿者/ 真田昌幸 (17回)-(2016/08/03(Wed) 17:16:55)
No80687 (魔界の仮面弁士 さん) に返信
> ■No80686 (真田昌幸 さん) に返信
> もしくは呼び出し側が BackgroundWorker なり Async/Await なりで
> スレッドを分けて実行させるのが現実的かと思います。

BackgroundWorkerでのマルチスレッドの方法はDOBON.NETのTipsを見てみました。
うーん。確かにべき論としてはこれがよさそうですが、
イベントハンドラー定義するとか、理論がそこそこややこしいので、
改修の際に外出し(オフショア)したり、新人にコーディングさせるのは難しくなりますね。
そこが難点です。

有力案の一つにはなると思います。

引用返信 編集キー/
■80700 / inTopicNo.5)  Re[3]: プログレスバーの処理遅延
□投稿者/ Azulean (671回)-(2016/08/03(Wed) 22:16:45)
No80689 (真田昌幸 さん) に返信
> 重い要素があるとすれば、
> Loopの中でRefreshしていることと、DBの更新と同時にやっていることくらいです。
> 現象としては、ProgressBarの目盛が全く進まない状態で本処理が終了し完了メッセージが出てしまうイメージ。

ご存知かもしれませんが、Windows プログラムの一般的なお作法として、UI スレッドでループして処理をするのではなく、スレッドを分けること、それによって他のイベント・ウィンドウメッセージを処理させる余裕を UI スレッドに与えることが必要になっています。
書かれている内容を読む限り、それを知らずか、あえて割り切ったかによって UI スレッド(シングルスレッド)で完結されたプログラムとなっているようなので、プログレスバーがうまく動かないのだと思います。
過去の実装指針はどうしようもないとして、ここから打てる手はかなり限られるか、打つ手なしになる可能性が高いです。


Vista 以降のプログレスバーはアニメーションする仕組みなので、Refresh を呼ぶだけではきちんと描画されないのでしょう。
Refresh の代わりに、Application.DoEvents を呼んで改善するかどうか?というところです。
(DoEvents はほかにもボタンが押せるようになる、別の処理が割り込むなど、自由な操作や予期しない動作をする可能性があるので、注意してください)



もっと割り切るなら、プロジェクトのプロパティあたりから「XP Visual スタイルを有効にする」を OFF にして全体を古くさい GUI に戻して、プログレスバーのアニメーションをやめさせるかでしょうか?
割り切りすぎと言われるなら、プログレスバーに頼るのをやめて、自力描画の偽物を作るのも逃げ方の1つかもしれません。
引用返信 編集キー/
■80701 / inTopicNo.6)  Re[3]: プログレスバーの処理遅延
□投稿者/ なちゃ (125回)-(2016/08/03(Wed) 23:32:51)
Vista以降のアニメーションが原因の場合、もしかしたら、プログレスバーの値を少し多く設定して戻すと、強制的に(アニメメーションをスキップして)更新されるかもしれません。
まあこれも強引な方法の範疇のような気もしますが。

しかしVista以降のプログレスバーのアニメーションはかなり遅いので、バックグラウンドで処理してバーを更新する場合でも現実的にこうせざるを得ないことがあったりします。
引用返信 編集キー/
■80702 / inTopicNo.7)  Re[4]: プログレスバーの処理遅延
□投稿者/ 真田昌幸 (18回)-(2016/08/04(Thu) 10:30:59)
No80700 (Azulean さん) に返信
> Vista 以降のプログレスバーはアニメーションする仕組みなので、Refresh を呼ぶだけではきちんと描画されないのでしょう。
> Refresh の代わりに、Application.DoEvents を呼んで改善するかどうか?というところです。
> (DoEvents はほかにもボタンが押せるようになる、別の処理が割り込むなど、自由な操作や予期しない動作をする可能性があるので、注意してください)

この方法はおそらく却下ですね。目先の工数は一番少ないですが、
ご指摘のように想定外挙動誘発が予想されるので。
ネット上でググると意外とこの方法とっている人多いみたいですが。


> もっと割り切るなら、プロジェクトのプロパティあたりから「XP Visual スタイルを有効にする」を OFF にして全体を古くさい GUI に戻して、プログレスバーのアニメーションをやめさせるかでしょうか?

これは、今後のメンテナンス性を考えて却下濃厚。
元号対応でoleaut32.dll(varFormat)を使っているのが、問題になっているくらいなので、
古い技術に先祖返りするのはあり得ません。
また、プロジェクトのプロパティの箇所は、
コントロール自体がクラスライブラリのため(Windows標準のものを継承してますが)、設定できないみたいです。


> 割り切りすぎと言われるなら、プログレスバーに頼るのをやめて、自力描画の偽物を作るのも逃げ方の1つかもしれません。

マルチスレッド案とともに有力案になっているのが、
プログレスバーやめる案です。
理由は、本処理が先にさっさと終わっているような処理なので、プログレスバーを表示するメリットがそもそも少ないため。
砂時計だけではさすがに、ということで、数字のカウンターとか、処理中メッセージで逃げるという原始的なものが有力。

一応、さすがに、シングルコアCPUの端末を使っているユーザーはいないようなので、
マルチスレッド案は有力案です。
ネックは、コードの煩雑性です。



引用返信 編集キー/
■80704 / inTopicNo.8)  Re[5]: プログレスバーの処理遅延
□投稿者/ shu (896回)-(2016/08/04(Thu) 11:21:47)
No80702 (真田昌幸 さん) に返信
>
> マルチスレッド案とともに有力案になっているのが、
> プログレスバーやめる案です。
> 理由は、本処理が先にさっさと終わっているような処理なので、プログレスバーを表示するメリットがそもそも少ないため。
> 砂時計だけではさすがに、ということで、数字のカウンターとか、処理中メッセージで逃げるという原始的なものが有力。
>
> 一応、さすがに、シングルコアCPUの端末を使っているユーザーはいないようなので、
> マルチスレッド案は有力案です。
> ネックは、コードの煩雑性です。
>
マルチスレッドの実装も大分楽になってきていますので、頑張って変更されることを奨めます。

引用返信 編集キー/
■80712 / inTopicNo.9)  Re[5]: プログレスバーの処理遅延
□投稿者/ Azulean (672回)-(2016/08/04(Thu) 21:28:55)
先の投稿は、マルチスレッド案を却下(あるいは、回避)したいという縛りの中での案出しである点はご留意ください。
あくまでも、私はマルチスレッド案推しです。

No80702 (真田昌幸 さん) に返信
>>(DoEvents はほかにもボタンが押せるようになる、別の処理が割り込むなど、自由な操作や予期しない動作をする可能性があるので、注意してください)
>
> この方法はおそらく却下ですね。目先の工数は一番少ないですが、
> ご指摘のように想定外挙動誘発が予想されるので。

却下自体は私もそうするべきだと思っているので異議はありません。
しかし、理由が「想定外挙動誘発が予想される」とされていることが気にかかりました。

マルチスレッドも同じ危険性を持っているためです。
結局、どちらも「メインスレッドのメッセージループを回す」ためのアプローチであるため。


// マルチスレッド案では、そういった危険性を理解し、配慮する実装もセットになっているというのであれば、私のこれは杞憂だったと言うことですが…。
引用返信 編集キー/
■80715 / inTopicNo.10)  Re[6]: プログレスバーの処理遅延
□投稿者/ 真田昌幸 (19回)-(2016/08/05(Fri) 09:55:49)
No80712 (Azulean さん) に返信
> しかし、理由が「想定外挙動誘発が予想される」とされていることが気にかかりました。
>
> マルチスレッドも同じ危険性を持っているためです。
> 結局、どちらも「メインスレッドのメッセージループを回す」ためのアプローチであるため。
>
>
> // マルチスレッド案では、そういった危険性を理解し、配慮する実装もセットになっているというのであれば、私のこれは杞憂だったと言うことですが…。

「想定外挙動誘発が予想される」はどちらかというと、DoEventsの危険性を理解しないプログラマーの
防止策不足を誘発する(要するにバグ誘発)といった方が正確かもしれません。
どちらかというとその意味で書きました。

マルチスレッド案も過信禁物なのは承知してます。
実際、占有率増大でかえって遅くなる事例もあるようですし。
やるなら、ユーザーにシングルコアのPCがゼロであることを確認してからにはなります。
ASP.netとかではなく、WindowsアプリのVBなので、OSもサーバーでなくWin7です。
シングルコアはほぼ無いでしょうが、絶対とは言い切れません。
ヒアリングは必要です。

そもそも、プログレスバーやめる案も有力になっているのは、
夜間バッチでもWebアプリでもなく、
たいして時間がかかりそうにない、本部でのみ使用するWindowsアプリに
そもそもプログレスバー表示による処理過程把握が必要かどうかが微妙だからです。
ユーザーに見た目のこだわりがなければ、
すんなりこの案が通る可能性もあります。





引用返信 編集キー/
■80718 / inTopicNo.11)  Re[7]: プログレスバーの処理遅延
□投稿者/ 魔界の仮面弁士 (799回)-(2016/08/05(Fri) 12:29:04)
No80715 (真田昌幸 さん) に返信
> やるなら、ユーザーにシングルコアのPCがゼロであることを確認してからにはなります。
タスクマネージャーで、CPU が 100% に張り付いていないのであれば、
CPU は 1 コアで十分だと思います。


No80689 (真田昌幸 さん) に返信
> プロパティ設定の(画面側での)タイミングの変更とか、
> あるいは環境面の改善で何とかなるかとかですね。
> 「手軽な」の意味は。

「DB 更新」が時間のかかる処理で、それが UI スレッドから呼ばれているのであれば、
Await を用いる(要 .NET 4.5 以降)を使うのが良い気がしますが、
そこまでするほどの課題では無いという判断なら、
進捗表示を行わないというのも、一つの道だとは思います。


> 現象としては、ProgressBarの目盛が全く進まない状態で本処理が終了し完了メッセージが出てしまうイメージ。
多分 ↓コレと同じですよね。
https://social.msdn.microsoft.com/Forums/ja-JP/0ff2009e-3d83-4099-9839-c043ac2a3d9e/100?forum=csharpgeneralja

であれば、No80701 でなちゃさんが書かれているように、
Value を「少し多く設定して戻す」方法で回避できませんでしたか?


'バーが進まないパターン
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
  ProgressBar1.Style = ProgressBarStyle.Continuous
  ProgressBar1.Minimum = 0
  ProgressBar1.Maximum = 10000
  ProgressBar1.Value = 0

  For i = 1 To 10000
    ProgressBar1.Value = i
    ProgressBar1.Update()
  Next

  '本来は、UI スレッドで Sleep するべきでは無いけれど:
  System.Threading.Thread.Sleep(3000)

  Me.Close()
End Sub


'バーが進むが、80% 近辺で止まってしまい、100%にならない(MSDN Forum の質問例)
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
  ProgressBar1.Style = ProgressBarStyle.Continuous
  ProgressBar1.Minimum = 0
  ProgressBar1.Maximum = 10000
  ProgressBar1.Value = 0

  'Invalidate + Update を Refresh に置き換えても良い
  For i = 1 To 10000
    ProgressBar1.Invalidate()
    ProgressBar1.Value = i
    ProgressBar1.Update()
    'Application.DoEvents()   'これを入れても効果は無い
  Next

  System.Threading.Thread.Sleep(3000)

  Me.Close()
End Sub



'期待通り、100% になってから 3 秒後に閉じるが、要.NET 4.5 以降
'それ以前のバージョンでは、MSDN Forum の回答例のようにイベント待機に置き換える
Private Async Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
  ProgressBar1.Style = ProgressBarStyle.Continuous
  ProgressBar1.Minimum = 0
  ProgressBar1.Maximum = 10000
  ProgressBar1.Value = 0

  For i = 1 To 10000
    ProgressBar1.Value = i
    ProgressBar1.Refresh()
  Next

  Await Task.Delay(3000)

  Me.Close()
End Sub


' No80701 のなちゃさん案
'わざと行き過ぎてから戻す方法であれば、重い処理でもバーが更新される
Private Sub Form1_Shown(sender As Object, e As EventArgs) Handles Me.Shown
  ProgressBar1.Style = ProgressBarStyle.Continuous
  ProgressBar1.Minimum = 0
  ProgressBar1.Maximum = 10000
  ProgressBar1.Value = 0

  ' http://dobon.net/vb/dotnet/control/pbdisableanimation.html
  ProgressBar1.Maximum += 1
  For i = 1 To 10000
    ProgressBar1.Value = i + 1
    ProgressBar1.Value = i
  Next
  ProgressBar1.Maximum -= 1

  System.Threading.Thread.Sleep(3000)

  Me.Close()
End Sub


もしもなちゃさん案で回避できるのであれば、ProgressBar 継承クラスで、
Value プロパティをシャドウイングしてみては如何でしょうか。

Public Overloads Property Value() As Integer
  Get
    Return MyBase.Value
  End Get
  Set(ByVal value As Integer)
    If value = Me.Maximum Then
      Me.Maximum += 1
      MyBase.Value = value + 1
      MyBase.Value = value
      Me.Maximum -= 1
    Else
      MyBase.Value = value + 1
      MyBase.Value = value
    End If
  End Set
End Property


こうしておけば、
 For i = 1 To 10000
  ProgressBar1.Value = i
 Next
 System.Threading.Thread.Sleep(3000)
であったとしても、100% まで到達するようです。


ただし、元の Value は Overridable では無いため、
この実装は Overloads または Shadows になります。そのため、
 DirectCast(ProgressBar1, ProgressBar).Value = i
のように呼ばれた場合は、アニメーション効果をキャンセルできません。
引用返信 編集キー/
■80719 / inTopicNo.12)  Re[7]: プログレスバーの処理遅延
□投稿者/ ダー (1回)-(2016/08/05(Fri) 12:33:24)
No80715 (真田昌幸 さん) に返信
高速化することを目的にマルチスレッドにするわけじゃないんだから
シングルコアじゃないことを確認することに意味があるとは思えないなあ
引用返信 編集キー/
■80720 / inTopicNo.13)  Re[8]: プログレスバーの処理遅延
□投稿者/ ダー (2回)-(2016/08/05(Fri) 12:49:33)
却下するかどうかは会社内で話してもらって
ここではもらった意見を整理するのに徹したがいんじゃないかな
整理する前に元号の予算とかヒアリングとか他の事情を
混ぜ合わせるとわけわかんないよ
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -