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

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

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

Re[9]: Invoke 中に FormClosed にな


(過去ログ 126 を表示中)

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

■75173 / inTopicNo.1)  Invoke 中に FormClosed になる場合の対処法
  
□投稿者/ 774RR (231回)-(2015/03/04(Wed) 15:16:18)

分類:[.NET 全般] 

Visual Studio 2010 C# .NET 4.0 Client Profile な Form App を作っています。

通信処理の WorkerThread の中から画面更新をしたいので
void Form1_ReceivedEventHandler(object sender, ReceiveEventArgs arg) {
    Invoke((Action)(()=> { label1.Text = arg.packet.ToString(); }));
}
のように Invoke 経由で UI スレッドを呼ぶコードを作ってみました。

Form1_Load で Form1_ReceivedEventHandler が呼ばれるよう delegate += していて
Form1_FormClosing で Form1_ReceivedEventHandler を呼ばないよう delegate -= しています。
が、それだけでは Form1 を閉じるタイミングで System.ObjectDisposedException が出ます。
複数 Form な App なので Form1 を閉じても通信処理は依然有効なままでなければなりません。

理由は既にわかっていて、ユーザが閉じる操作をしたときたまたま Invoke 中だと
- Invoke 中に FormClosing -> FormClosed の順に処理が進み
- FormClosed の時点で UI 部品は Dispose されているので
- Invoke したい lambda が実行される時点で label1.Text はもう処分済み
なわけですが、どう対処するとよいと思いますか?

try { Invoke...; }
catch (System.ObjectDisposedException e) { 何もしない }
というのは既に試していて問題なさそうなのですが、いかにもださいので代案募集したいです。

引用返信 編集キー/
■75178 / inTopicNo.2)  Re[1]: Invoke 中に FormClosed になる場合の対処法
□投稿者/ shu (711回)-(2015/03/04(Wed) 16:38:07)
No75173 (774RR さん) に返信

Invoke中FlagをPrivate変数で用意して
lockを絡めて待ち状態にするとなんとかならないでしょうか?
引用返信 編集キー/
■75180 / inTopicNo.3)  Re[2]: Invoke 中に FormClosed になる場合の対処法
□投稿者/ 774RR (232回)-(2015/03/04(Wed) 17:18:19)
書き忘れましたが(まあわかるでしょうけど)
通信担当クラスのメンバとして delegate ReceivedEvents を用意して
- 複数のハンドラを += します (Form1_ReceiveEventHandler Form2_ReceiveEventHandler 等)
- 通信スレッドの中から発行します
- Form1/Form2/Form3 はそれぞれ自分自身に対して別な動作をしたい
という状況です。

どのスレッドが何を待つのか、を考えるとかなり悩ましいです。

Invoke されて UI スレッド上で走るはずの lambda (label1.Text = hoge;) の完了を
同じく UI スレッド上で走る FormClosing 中で待ちたい、わけなので
UI スレッドを lock/sleep させるのは意味が無いっていうか deadlock するだけっぽいです。

自分自身向けのイベント通知を delegate -= するのが、そのイベント処理中なので手遅れという。
lambda 式中で if (!label1.IsDisposed) とするのが簡単そうな気がしてきましたがこれもダサい。
引き続き募集中です。
引用返信 編集キー/
■75182 / inTopicNo.4)  Re[3]: Invoke 中に FormClosed になる場合の対処法
□投稿者/ Hongliang (289回)-(2015/03/04(Wed) 17:56:28)
FormClosingイベントで、最初に閉じられるとき
・購読してるイベントのハンドラを解除
・閉じるのをキャンセル
・その上でBeginInvokeで改めてClose
ってやればセーフかなぁ…。
引用返信 編集キー/
■75183 / inTopicNo.5)  Re[4]: Invoke 中に FormClosed になる場合の対処法
□投稿者/ Hongliang (290回)-(2015/03/04(Wed) 18:22:40)
2015/03/04(Wed) 18:37:26 編集(投稿者)
ん、こっちの方が良いかな
EventHandler<ReceiveEventArgs> act = (s, e) => { };
EventHandler<ReceiveEventArgs> act1 = (s, e) => this.Invoke(new EventHandler<ReceiveEventArgs>(this.OnReceived), s, e);
act += act1;
hoge.Received += new EventHandler<ReceiveEventArgs>((s, e) => act(s, e));
f.FormClosing += (s, e) => act -= act1;

追記
ああ全然駄目ですねこれ。

引用返信 編集キー/
■75184 / inTopicNo.6)  Re[4]: Invoke 中に FormClosed になる場合の対処法
□投稿者/ 774RR (233回)-(2015/03/04(Wed) 18:23:57)
おおっなんだか良さそうなアイデアが・・・と思ったのですが

> ・購読してるイベントのハンドラを解除
> ・閉じるのをキャンセル
して FormClosing から return するんですよね?

> ・その上でBeginInvokeで改めてClose
誰がどこでこれを発行します?
通信スレッドはフォームの有無は知りません(知りたくないです)
Form1 のどのハンドラが this.Close すればいいでしょうか?

引用返信 編集キー/
■75185 / inTopicNo.7)  Re[5]: Invoke 中に FormClosed になる場合の対処法
□投稿者/ Hongliang (291回)-(2015/03/04(Wed) 18:28:31)
>>・その上でBeginInvokeで改めてClose
> 誰がどこでこれを発行します?
FormClosingイベントで。
ただ、じっくり考えると、別スレッドで動いてるのが
var handler = this.Received;
if (handler != null) handler(this, e);
の中でhandler確保してからhandler呼び出すまでに
.Received -= Form_Received からCloseまで走ったときにエラーになりますね。
(現実的には考えづらいですけど)
引用返信 編集キー/
■75186 / inTopicNo.8)  Re[5]: Invoke 中に FormClosed になる場合の対処法
□投稿者/ Atata!! (6回)-(2015/03/04(Wed) 20:46:47)
Atata!!です。

開発プロジェクトでそのような要件が存在する場合、
私は画面の更新はタイマーで行うことを検討します。
タイムクリティカルな通知を人に伝えなければならない場合、
ラベルを更新するだけでは利用者が気づかない可能性があり
音や光(ウィンドウのフラッシュ)などで知らせた方が良いと考えられるからです。

提示されている例の場合、ワーカースレッドはフォームクラスのプライベート変数に値を書き込むだけで、
タイマー処理はそれをラベルに反映するだけという実装にすると思います。

引用返信 編集キー/
■75188 / inTopicNo.9)  Re[1]: Invoke 中に FormClosed になる場合の対処法
□投稿者/ Azulean (442回)-(2015/03/04(Wed) 22:51:02)
No75173 (774RR さん) に返信
> try { Invoke...; }
> catch (System.ObjectDisposedException e) { 何もしない }
> というのは既に試していて問題なさそうなのですが、いかにもださいので代案募集したいです。

本来あるべきところではなく、隅っこをつくようでアレですが、念のため。

Invoke 呼び出す時点で Control.IsDisposed == true の状況(厳密には !IsHandleCreated)があり得た場合、飛んでくる例外は InvalidOperation になります。
(FormClosing でイベントを外していれば、これの発生確率は非常に低くなるとは思いますが)
http://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Control.cs,7620

※昔あった Invoke メソッドの呼び出しでタイミング依存でハングするという問題は、Reference Source を見る限り、すでに修正されているように見えました。参考までに。
http://bbs.wankuma.com/index.cgi?mode=al2&namber=6843&page=20&KLOG=18
http://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Control.cs,4329

-----
それだけでは難ですので一案として。
いっそのこと、static メソッドなどで「ださい」部分を隠蔽してしまうとか。


public partial class Form1 : Form
{
    public void Received(object sender, EventArgs e)
    {
        this.SafeInvoke(() => { /* 実行したい処理 */ });
        
        // 拡張メソッドが避ける場合:
        // ControlExtension.SafeInvoke(this, () => { /* 実行したい処理 */ });
    }
}

public static class ControlExtension
{
    public static void SafeInvoke(this Control control, Action action)
    {
        try
        {
            control.Invoke(action);
        }
        catch (ObjectDisposedException)
        { }
        catch (InvalidOperationException)
        { }
    }
}

引用返信 編集キー/
■75189 / inTopicNo.10)  Re[2]: Invoke 中に FormClosed になる場合の対処法
□投稿者/ 通りすがり (11回)-(2015/03/05(Thu) 01:48:08)
盛り上がっているようですが...
>通信スレッドはフォームの有無は知りません(知りたくないです)
なんだからフォーム側で例外が発生しても全然おかしくないでしょ。
このような場合、操作対象のオブジェクトが有効かどうかを調べる
のはプログラマの責任かと。

引用返信 編集キー/
■75191 / inTopicNo.11)  Re[3]: Invoke 中に FormClosed になる場合の対処法
□投稿者/ 774RR (234回)-(2015/03/05(Thu) 09:13:31)
No75189 通りすがり さん
> 操作対象のオブジェクトが有効かどうかを調べるのはプログラマの責任かと。
そこんところは御意。

俺の主観でのダサい度を自分自身のために整理してみました。上のほうがよりダサい。

・必然性の無い catch を書く(他人のコードが throw するのを受け取って隠蔽する)
・必然性の無い throw を自ら書く
----- 容認したくない壁 -----
・必然のある throw を自ら書く
・必然のある catch を書く
・そもそも例外が不要なロジックを考える

なので Form1 側で「そもそも例外が不要なロジックはないかな」のがスレ趣旨なのです。
IsDisposed は「例外発生させない」ためのロジックではあるので採用に値するレベルではあります。

No75186 Atata!! さん
> 私は画面の更新はタイマーで行うことを検討します。
> ワーカースレッドはフォームクラスのプライベート変数に値を書き込むだけ
この解はきわめて妥当で現実的だと思います。画面更新が早すぎても操作者には見えないわけですし。
ただ、1年後の自分や他人が見たとき「何を遠回りしてんだ?」と思うのは必至でしょう。
これを採用するなら要コメント残しですね。

No75188 Azulean さん
とりあえず100回例外発生させてみたレベルでは
ObjectDisposedException 100回
InvalidOperation 0回
でした。ご指摘のように確率はかなり低いものと思われます。
でも0%ではないのであれば対処が必要ですし
> 「ださい」部分を隠蔽してしまうとか。
のはアリでしょう。

アイデア募集はもうしばらく継続してみますし、こちらでも検討ならびに試験は続けてみます。
引用返信 編集キー/
■75192 / inTopicNo.12)  Re[4]: Invoke 中に FormClosed になる場合の対処法
□投稿者/ shu (714回)-(2015/03/05(Thu) 10:44:32)
No75191 (774RR さん) に返信

役に立つかわかりませんが、こんな感じのはどうでしょう?

    Private _dowork As Boolean
    Private _action As Action = Nothing
    Private _tsk As Threading.Tasks.Task

    Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load
        _dowork = True
        _tsk = New Threading.Tasks.Task(AddressOf TaskMain)
        _tsk.Start()
    End Sub

    Private Sub TaskMain()
        Do While _dowork
            Invoke(Sub()
                       Label1.Text = Date.Now.ToString("HH:mm:ss")
                   End Sub)
        Loop
        If _action IsNot Nothing Then _action()
    End Sub

    Private Sub Form1_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
        If _dowork Then
            _dowork = False
            _action = Sub() Invoke(Sub() Me.Close())
            e.Cancel = True
        End If
    End Sub

引用返信 編集キー/
■75193 / inTopicNo.13)  Re[2]: Invoke 中に FormClosed になる場合の対処法
□投稿者/ れい (8回)-(2015/03/05(Thu) 13:47:15)
なんか昔考えたようなと思ってたら

No75188 (Azulean さん) に返信
> ※昔あった Invoke メソッドの呼び出しでタイミング依存でハングするという問題は、Reference Source を見る限り、すでに修正されているように見えました。参考までに。
> http://bbs.wankuma.com/index.cgi?mode=al2&namber=6843&page=20&KLOG=18
> http://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Control.cs,4329

懐かしい話題が。
修正はいったという話は来ていないですし.Net4でもダメだったように思います。
デッドロックの頻度は減っているように感じますが調査していません。

漁れば当時のソースが出てくるかもしれませんが、今まで出てきた方法はだいたい調査したように思います。

うまくデザインできないというような問題もあり、
結局Invoke系ではどうしてもうまく実装できなかったため、
いまでもPostMessageや同期オブジェクト+タイマーで処理しています。

いつ死ぬかわからないWindowとの通信でInvokeはダメパターンで使ってはいけない。
と、私のメモ帳には書いてあります。
引用返信 編集キー/
■75194 / inTopicNo.14)  Re[3]: Invoke 中に FormClosed になる場合の対処法
□投稿者/ 774RR (235回)-(2015/03/05(Thu) 15:28:40)
No75192 shu さん
これはれいさんの過去ログで言うところの FormClosing 差し戻しパターンっすね。
れいさんの過去ログでは結果的にダメでした、となっていますね。

No75193 れい さん
Control.Invoke 全否定っすか? PostMessage にするのはありえないレベルで面倒っす。

この件、最終的に FormN を実装するのは俺でなくて後輩君なのです。
最小限の負担で最大の効果が出る方策を探っているのはその辺に理由があったりして・・・

さて Control.Invoke は直っているのか直っていないのかどっちなんでしょ?とりあえず
Invoke((Action)(()=>{ if (!label1.IsDisposed) label1.Text=hoge; })));
としたところ今のところは問題出ていません。(試行回数10回くらい)
※念を入れるなら if (!this.IsDisposed) Invoke... でしょうか?

Designer の動作を阻害させない+後輩君がコピペで実装できる、と考えると
Azulean さんの SafeInvoke がこの案件には適切な気がしてきたです。
例外の握りつぶしは嫌いなのですがこの際しかたないかも。

# あるいは Invoke ばっさりやめちゃうか?それも面倒な気が
引用返信 編集キー/
■75196 / inTopicNo.15)  Re[4]: Invoke 中に FormClosed になる場合の対処法
□投稿者/ れい (9回)-(2015/03/05(Thu) 16:49:05)
全否定じゃないですよー
スレッドの方が長生きする場合はだめ、
スレッド側からUI変化を通知するのがだめ、
ってことです。

フォーム側から通知するぶんにはok。

デザインとしても、処理をするスレッド側が画面更新を通知するのはあまりおいしくないと思います。
スレッド側がいちいち画面をきにして更新するなら、スレッドに分ける必要性が薄れます。

進捗を通知したいなら、フォーム側が定期的に監視するデザインがいいと思いますね。
タイマーで定期的に読むだけ。
PCやプログラムは将来はやくなるかもしれませんが、UIは人間が超進化しない限り10ms程度のレスポンスがあれば十分です。
スレッドからの通知だとusec単位でinvokeされたりする場合も考えられますし。

引用返信 編集キー/
■75205 / inTopicNo.16)  Re[4]: Invoke 中に FormClosed になる場合の対処法
□投稿者/ yukihiro (1回)-(2015/03/05(Thu) 22:03:51)
本題に関してはme too で申し訳ないのですが、

No75189 通りすがりさん
> このような場合、操作対象のオブジェクトが有効かどうかを調べる
> のはプログラマの責任かと。

に全く同意で、
IsDisposed()するのが極自然で素直で読みやすいと私には思え、
それ自体は「ダサい」とは感じないですね。
別スレッドからInvoke()された処理はどのタイミングで呼ばれるかわからないわけで、
当然の前提条件チェックだと思います。

同じことを複数の箇所で書くのがダサいということであれば
(それは確かにダサい、と私も感じます。)
Azuleanさん案の「ダサいものにはフタ」拡張メソッドがいいと思います。

# 拡張メソッドの中身について、
# 私だったら例外キャッチよりcontrol.IsDisposed()チェックを選ぶわけですが、
# 「controlは生きてるけど、actionの中で触っている他のコントロールは死んでた」
# というレアなケースも握りつぶしたい、という意図であれば
# 例外キャッチもありだと思います。

以上、me tooでした。

で、本題でない箇所で気になったところがあります。

> Form1_FormClosing で Form1_ReceivedEventHandler を呼ばないよう delegate -= しています。

FormClosingイベント中ではまだフォームが消えるかどうかわからないので、
そこでそんなことをやるべきじゃない、と直観的には感じます。

何か理由があるんでしょうか。

引用返信 編集キー/
■75206 / inTopicNo.17)  Re[3]: Invoke 中に FormClosed になる場合の対処法
□投稿者/ Azulean (443回)-(2015/03/05(Thu) 22:11:24)
No75193 (れい さん) に返信
> 修正はいったという話は来ていないですし.Net4でもダメだったように思います。
> デッドロックの頻度は減っているように感じますが調査していません。

過去のスレッドで指摘されていた、「スレッド ID の取得・比較のアプローチがダメ」という部分は .NET 4 で変わっていたため、直っているとみていました。
(IsDisposed で判定するようになっている)
ほかの部分でハングする要因が残っているのかな?


> いつ死ぬかわからないWindowとの通信でInvokeはダメパターンで使ってはいけない。
> と、私のメモ帳には書いてあります。

これは私も同意見です。

代案をあえて出すなら、SynchronizationContext.Send/Post はマシかもしれません。
確か、特定の Form に依存せず、ウィンドウを持っているはずなので。

-----
一案?

public partial class Form1 : Form
{
    private readonly SafeInvoker _safeInvoker;

    public Form1()
    {
        InitializeComponent();
        _safeInvoker = new SafeInvoker(this);
    }

    private void Received(object sender, EventArgs e)
    {
        _safeInvoker.Invoke(() => { /* 実行したい処理  */ });
    }
}

public class SafeInvoker
{
    private readonly Control _referenceControl;
    private readonly SynchronizationContext _synchronizationContext;

    public SafeInvoker(Control referenceControl)
    {
        _referenceControl = referenceControl;
        _synchronizationContext = SynchronizationContext.Current;
    }

    public void Invoke(Action action)
    {
        _synchronizationContext.Send(o =>
        {
            if (_referenceControl.IsDisposed) return;
            action();
        }, null);
    }
}

引用返信 編集キー/
■75207 / inTopicNo.18)  Re[5]:
□投稿者/ Azulean (444回)-(2015/03/05(Thu) 22:14:52)
2015/03/05(Thu) 22:29:47 編集(投稿者)

No75205 (yukihiro さん) に返信
> # 拡張メソッドの中身について、
> # 私だったら例外キャッチよりcontrol.IsDisposed()チェックを選ぶわけですが、
> # 「controlは生きてるけど、actionの中で触っている他のコントロールは死んでた」
> # というレアなケースも握りつぶしたい、という意図であれば
> # 例外キャッチもありだと思います。

Invoke メソッドの処理途中で内部的な PostMessage(やその先の action 実行)に至るまでの間に Dispose されてしまうと、例外が飛ぶことがあるということです。
これは外見からは「Invoke メソッドが例外を投げた」ということになります。

Invoke 直前で IsDisposed をチェックするという考え方もできますが、結局、判定時と実行時に状態が変化する可能性を否定できないので、呼んでみるというアプローチを案としていました。
(IsDisposed の判定のタイミングから、Invoke で実際に UI スレッドにリクエストが飛ぶまでの間、ウィンドウが Dispose されないことを保障することができない)
引用返信 編集キー/
■75210 / inTopicNo.19)  Re[6]:
□投稿者/ yukihiro (2回)-(2015/03/06(Fri) 00:26:49)
No75207 (Azulean さん) に返信
あ、いえ言葉足らずですみません。
私は単にInvoke()された先のUIスレッドでのIsDisposedチェックを意図してました。

public static void SafeInvoke(this Control control, Action action)
{
    Action wrapper = ()=>{
        if(control.IsDisposed)
            return;

        action();
    };

    control.Invoke(wrapper);
}

が、やはり汎用的にするにはおっしゃるとおりInvokeの外側で例外チェックが必要ですね。
適当な事を書いてしまった気がします。
ごめんなさい。>ALL

引用返信 編集キー/
■75211 / inTopicNo.20)  Re[4]: Invoke 中に FormClosed にな
 
□投稿者/ れい (10回)-(2015/03/06(Fri) 03:09:29)
2015/03/06(Fri) 04:06:01 編集(投稿者)
2015/03/06(Fri) 04:05:53 編集(投稿者)

No75206 (Azulean さん) に返信
> 過去のスレッドで指摘されていた、「スレッド ID の取得・比較のアプローチがダメ」という部分は .NET 4 で変わっていたため、直っているとみていました。
> (IsDisposed で判定するようになっている)
> ほかの部分でハングする要因が残っているのかな?

しっかり調べる時間はありませんでしたが、デッドロックは治っているようです。
(メモリリークが残ってるようにも見えますが…

追記。

デッドロックは起きませんが、クラッシュしますね…orz

1.1だとすぐにデッドロック
2.0だとまれにデッドロック
4.5だとまれにクラッシュ

引用返信 編集キー/

次の20件>
トピック内ページ移動 / << 0 | 1 >>

管理者用

- Child Tree -