他フォームのイベントとの連結 への返答

投稿で使用できる特殊コードの説明。(別タブで開きます。)
本名は入力しないようにしましょう。
投稿した後で削除するときに使うパスワードです。返答があった後は削除できません。
返答する人が目安にします。相手が小学生か社会人かで返答の仕方も変わります。
最初の投稿が質問の場合、質問者が解決時にチェックしてください。(以降も追加書き込み・返信は可能です。)
※「過去ログ」について書くときはその過去ログのURLも書いてください。

以下の返答は逆順(新しい順)に並んでいます。

投稿者 shu  (社会人) 投稿日時 2011/12/5 21:35:45
今回出ているForm1でのSleepまでの処理中は他の処理は行われず
Form2でのイベント発生まではForm1での処理はSleepしているので
同時実行される処理はないものとしてサンプルを提示しています。

Form1のSleepまでの処理

Form2のクリックイベント

Form1のSleep後の処理

という動作をすればよいのであればスレッドを分けることはないということです。


Form1の処理1
├────────────┐
↓                  ↓
Form1の処理2        Form2のクリック待ち
│                  │
│              Form2のクリック
↓                  │
Form2の処理2を中断      │
                    │                      
┌────────────┘

Form1を表示

だとしたらForm1の処理2を別スレッドにする必要はありますが。
投稿者 YuO  (社会人) 投稿日時 2011/12/5 17:51:03
> こんな感じの実装をすると別スレッドを使用せずに実現することも
> 可能だと思います。

私のコードが非同期処理を使っていたのは,元々
> Form1でLoadイベントの中でSystem.Threading.Thread.Sleep()を使用しているとき
ということに対する対処です。
この実装では,Sleepを使ってはいけない or Sleepを使うならその部分を非同期処理にしなければいけない,
という点で,結局は「別スレッドを使用せずに実現」ができていないように思えますがどうでしょうか。
投稿者 shu  (社会人) 投稿日時 2011/12/5 14:00:27
Form1:Button1とLabel1
Form2:Button1とTextBox1
を配置


それぞれ以下のコードを実装
するとForm1のボタンをクリックするとForm2が表示され
Form2のTextBox1に入力後Form2のボタンをクリックすると
入力した内容がForm1のラベルに表示されます。
こんな感じの実装をすると別スレッドを使用せずに実現することも
可能だと思います。


Public Class Form1

    Private WithEvents pfrm2 As Form2 = Nothing

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        If pfrm2 Is Nothing Then
            pfrm2 = New Form2
            pfrm2.ClickAction = AddressOf ClickAction
            pfrm2.Show()
        End If
    End Sub

    Private Sub ClickAction(Text As String)
        Label1.Text = Text
    End Sub

    Private Sub pfrm2_FormClosed(sender As Object, e As System.Windows.Forms.FormClosedEventArgs) Handles pfrm2.FormClosed
        pfrm2 = Nothing
    End Sub
End Class


Public Class Form2
    Public Property ClickAction As Action(Of String) = Nothing

    Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
        If ClickAction IsNot Nothing Then
            ClickAction.Invoke(TextBox1.Text)
        End If
    End Sub
End Class
投稿者 YuO  (社会人) 投稿日時 2011/12/4 11:43:17
> コード一つで簡単に出来ることかと思いきや、意外と難しい、というか手順が必要なのですね・・。

先のコードのようになったのは,
・Sleepのような,時間のかかる動作が入っていたので非同期処理にした
・Form1とForm2を一応独立させて,相互に監視するようなことをしないように作った
ためです。
# 後者を気にした割にForm2のForm1LoadRequestイベントとか,名前がおかしいですが。


> >まず,UIスレッドを止める行為自体が「やってはいけない」ことです。
> >イベントを止めているつもりかもしれませんが……。
> これは、本来はSystem.Threading.Thread.Sleep()を使ってはいけない、ということでしょうか?

Sleepを使うのはかまいません。UIスレッドで使ってはいけない,というだけです。

ちなみに,UIスレッドを止める行為はSleepに限りません。
「Sleepを使うこと」ではなく,「UIスレッドを止める行為」をやってはいけない,と書いたのはそのためです。
# 正確には,「UIスレッドを長時間専有する行為」ですが。
わかりやすいものではネットワークアクセスは長時間スレッドを止める代表例になります。
長時間の境はとりあえず50msと考えて下さい。次期Windowsで搭載されるWinRTの同期と非同期の境界となる時間です。

WinRTではファイルアクセスすらも非同期処理になっています。
# まぁ,コンパイラによるAwaitのサポートによってそれほどコード上の違いはなくなるでしょうけれど。


> インターネットのほかのサイトでも普通にSystem.Threading.Thread.Sleep()が紹介されていたり使われていたので必要ならやっても構わないことだと思っていましたが・・。

UIスレッド上でSleepが必要になることがおかしいのだと思って下さい。
UIスレッド上でSleepをする,ということは,「UI自体をSleepする」ことです。
わざわざ「応答なし」にする必要はないですよね。

++C++;の岩永氏の,
> 0.5秒固まったら「使いにくい」、3秒固まったら「バグだ」、10秒固まったら「パソコンが壊れた」と言われる。
http://ufcpp.net/study/csharp/misc_uithread.html
は中々に真実を言っていると思います。

.NET 1.0から既にスレッドプールが,.NET 2.0でBackgroundWorkerが,.NET 4でTaskやPLINQが,
次期VB/C#ではAsync(async)/Await(await)が入るのには,スレッドを簡単に使うための方法でもあります。

ただ,現状Task使うにしてもBackgroundWorker使うにしても,入門で取り扱うには難しい,という問題があります。
そのため,入門系の記事ではこのあたりを取り扱っていない傾向にあります。
# ワーカースレッドからUIを扱うことができない(読み取りであっても)など,UI/ワーカースレッドのどちらかを気にする必要がある。


Taskを使う版のForm1.vbの中身を,例えば
    Private _canceled As Boolean

    Public Sub WaitLoad()
        While Not _canceled
            Thread.Sleep(1000)
        End While
    End Sub

    Public Sub CancelWaitLoad()
        _canceled = True

        Me.Show()
    End Sub
とすると,Form2すら表示されなくなります。
WaitLoad()の中でSleepし続けるためです。

Program.vbでf2.Show()の後にf1.WaitLoad()をするようにして,Sleepの直前にApplication.DoEvents()すれば一応動作しますが,
1000msに1回だけしか処理しないため,ボタンを押すまでの間,動作にタイムラグが発生します。


> 二つの異なるクリックイベント(イベント1とイベント2)があり、片方のイベント(イベント1)が発生したかどうかでもう片方のイベント(イベント2)の処理または結果を違ったものにしたい場合はどうなのでしょう?
> イベント1の結果でイベント2が変化するのなら、その結果をイベント2内でIFなりSelect Caseで分岐させればよいのだと思いますが、イベント1の結果ではなく発生したかしていないかで分岐や追加処理の付加などをしたい場合、どのようにしたら良いのでしょう?可能でしょうか? 

イベントが発生したこと自体を結果と見なせば,同じ事です。
Private _event1Raised As Boolean = False

Private Sub Event1 (sender As Object, e As EventArgs)
    _event1Raised = True
End Sub

Private Sub Event2 (sender As Object, e As EventArgs)
    If _event1Raised Then
        ' Event1 発生済み 
    Else
        ' Event1 未発生 
    End If
End Sub

単純に,時間のかからない問題であれば,イベントハンドラにそのまま記述することができます。
投稿者 もも  (学生) 投稿日時 2011/12/4 06:19:09
YuOさんわざわざ有難うございます。
コード一つで簡単に出来ることかと思いきや、意外と難しい、というか手順が必要なのですね・・。

>まず,UIスレッドを止める行為自体が「やってはいけない」ことです。
イベントを止めているつもりかもしれませんが……。
これは、本来はSystem.Threading.Thread.Sleep()を使ってはいけない、ということでしょうか?
インターネットのほかのサイトでも普通にSystem.Threading.Thread.Sleep()が紹介されていたり使われていたので必要ならやっても構わないことだと思っていましたが・・。

もう一つ質問させてください。
二つの異なるクリックイベント(イベント1とイベント2)があり、片方のイベント(イベント1)が発生したかどうかでもう片方のイベント(イベント2)の処理または結果を違ったものにしたい場合はどうなのでしょう?

イベント1の結果でイベント2が変化するのなら、その結果をイベント2内でIFなりSelect Caseで分岐させればよいのだと思いますが、イベント1の結果ではなく発生したかしていないかで分岐や追加処理の付加などをしたい場合、どのようにしたら良いのでしょう?可能でしょうか?
投稿者 YuO  (社会人) 投稿日時 2011/12/4 02:03:19
とりあえずのサンプルを作ってみました (用)。
http://pub.idisk-just.com/fview/e01LkoEMFB1iHnl-ETN-9tYq2mjdsDzQRQiY3MDs1BzYMbXh151CPauuiIa4QVvVMcFkTHMbRlMjB2E7iNwWfsiKDDryjCpI.zip

タイミング調整の都合上,Taskを使う方のコードがあまり綺麗では無いですが……。
Taskの作成直後にContinueWithしたいのにApplication.Run前である都合上,
そのタイミングではContinueWithできないというのが残念。
投稿者 YuO  (社会人) 投稿日時 2011/12/4 00:24:23
> 例えばForm1でLoadイベントの中でSystem.Threading.Thread.Sleep()を使用しているとき、指定した時間内はForm1が表示されることはありませんよね。

まず,UIスレッドを止める行為自体が「やってはいけない」ことです。
イベントを止めているつもりかもしれませんが……。

イベントハンドラの処理は,UI全体で一つのスレッドしか使われません。
そんとあめ,UIを処理するスレッドでSleepした場合,UIに関する処理が全てストップします。

時間がかかるなら,別にスレッドを立てて,そちらを経由します。
まぁ,現状自分でThreadクラスを使うことはなく,BackgroundWorkerとか,Taskとか,デリゲートのBeginInvokeとかで済ましますが。


> それを別のフォーム(Form2)でクリックイベントが発生したらForm1のLoadイベントを中止(所謂Exit Sub?)
> してForm1が表示するように干渉させることは可能なのでしょうか?

UIスレッドを止めていますから,不可能です。
# 別途STAなThreadを先に作ってそちらで呼び出していればできなくもないのですが……。

・BackgroundWorkerの場合 (時間のかかる処理が一つだけで再入しない場合に有効。少数であれば,複数BackgroundWorkerを用意してもよい)
WorkerSupportsCancellationプロパティを事前にTrueにしておく
UIスレッドからはCancelAsyncメソッドを呼び出す
ワーカースレッド(=DoWorkのイベントハンドラ)はCancellationPendingプロパティを時々見に行き,CancellationPendingプロパティがTrueであれば,e.CancelをTrueにしてスレッドを抜ける

MSDN: WorkerSupportsCancellation プロパティ
http://msdn.microsoft.com/ja-jp/library/system.componentmodel.backgroundworker.workersupportscancellation.aspx
MSDN: CancelAsync メソッド
http://msdn.microsoft.com/ja-jp/library/system.componentmodel.backgroundworker.cancelasync.aspx
MSDN: CancellationPending プロパティ
http://msdn.microsoft.com/ja-jp/library/system.componentmodel.backgroundworker.cancellationpending.aspx

・Taskの場合
CancellationTokenSourceのインスタンスを用意しておく
用意したCancellationTokenSourceインスタンスのTokenプロパティを使ってTaskを生成しておく
UIスレッドからはCancellationTokenSourceインスタンスのCancelメソッドを呼び出す
ワーカースレッド(=Taskの引数のデリゲート)では定期的にCancellationTokenSourceインスタンスのTokenプロパティのThrowIfCancellationRequestedメソッドを呼び出す

MSDN: CancellationTokenSource コンストラクター
http://msdn.microsoft.com/ja-jp/library/dd321699.aspx
MSDN: Task コンストラクター (Action, CancellationToken)
http://msdn.microsoft.com/ja-jp/library/dd783029.aspx
MSDN: Cancel メソッド
http://msdn.microsoft.com/ja-jp/library/dd321955.aspx
MSDN: ThrowIfCancellationRequested メソッド
http://msdn.microsoft.com/ja-jp/library/system.threading.cancellationtoken.throwifcancellationrequested.aspx

・BeginInvokeの場合
キャンセルの為の標準的な手段は用意されていないので,自分で用意する必要があります。
投稿者 もも  (学生) 投稿日時 2011/12/3 12:53:50
複数のフォームがあるとき、一つのフォームのイベントの発生中、別のフォームのイベントが発生したら、元から発生したイベントに干渉させることって出来るのでしょうか?

例えばForm1でLoadイベントの中でSystem.Threading.Thread.Sleep()を使用しているとき、指定した時間内はForm1が表示されることはありませんよね。
それを別のフォーム(Form2)でクリックイベントが発生したらForm1のLoadイベントを中止(所謂Exit Sub?)
してForm1が表示するように干渉させることは可能なのでしょうか?

「他のフォーム イベント 呼び出し」 で検索したときにいくつかそれっぽいコードを見つけたりもしたのですが、それが私の求めてる答えなのか分からなかったため改めて質問します。