他フォームのイベントとの連結 への返答
投稿で使用できる特殊コードの説明。(別タブで開きます。)
以下の返答は逆順(新しい順)に並んでいます。
投稿者 YuO  (社会人)
投稿日時
2011/12/5 17:51:03
> こんな感じの実装をすると別スレッドを使用せずに実現することも
> 可能だと思います。
私のコードが非同期処理を使っていたのは,元々
> Form1でLoadイベントの中でSystem.Threading.Thread.Sleep()を使用しているとき
ということに対する対処です。
この実装では,Sleepを使ってはいけない or Sleepを使うならその部分を非同期処理にしなければいけない,
という点で,結局は「別スレッドを使用せずに実現」ができていないように思えますがどうでしょうか。
> 可能だと思います。
私のコードが非同期処理を使っていたのは,元々
> 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
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の中身を,例えば
WaitLoad()の中でSleepし続けるためです。
Program.vbでf2.Show()の後にf1.WaitLoad()をするようにして,Sleepの直前にApplication.DoEvents()すれば一応動作しますが,
1000msに1回だけしか処理しないため,ボタンを押すまでの間,動作にタイムラグが発生します。
> 二つの異なるクリックイベント(イベント1とイベント2)があり、片方のイベント(イベント1)が発生したかどうかでもう片方のイベント(イベント2)の処理または結果を違ったものにしたい場合はどうなのでしょう?
> イベント1の結果でイベント2が変化するのなら、その結果をイベント2内でIFなりSelect Caseで分岐させればよいのだと思いますが、イベント1の結果ではなく発生したかしていないかで分岐や追加処理の付加などをしたい場合、どのようにしたら良いのでしょう?可能でしょうか?
イベントが発生したこと自体を結果と見なせば,同じ事です。
単純に,時間のかからない問題であれば,イベントハンドラにそのまま記述することができます。
先のコードのようになったのは,
・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の結果ではなく発生したかしていないかで分岐や追加処理の付加などをしたい場合、どのようにしたら良いのでしょう?可能でしょうか?
コード一つで簡単に出来ることかと思いきや、意外と難しい、というか手順が必要なのですね・・。
>まず,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できないというのが残念。
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の場合
キャンセルの為の標準的な手段は用意されていないので,自分で用意する必要があります。
まず,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が表示するように干渉させることは可能なのでしょうか?
「他のフォーム イベント 呼び出し」 で検索したときにいくつかそれっぽいコードを見つけたりもしたのですが、それが私の求めてる答えなのか分からなかったため改めて質問します。
例えばForm1でLoadイベントの中でSystem.Threading.Thread.Sleep()を使用しているとき、指定した時間内はForm1が表示されることはありませんよね。
それを別のフォーム(Form2)でクリックイベントが発生したらForm1のLoadイベントを中止(所謂Exit Sub?)
してForm1が表示するように干渉させることは可能なのでしょうか?
「他のフォーム イベント 呼び出し」 で検索したときにいくつかそれっぽいコードを見つけたりもしたのですが、それが私の求めてる答えなのか分からなかったため改めて質問します。
Form2でのイベント発生まではForm1での処理はSleepしているので
同時実行される処理はないものとしてサンプルを提示しています。
Form1のSleepまでの処理
↓
Form2のクリックイベント
↓
Form1のSleep後の処理
という動作をすればよいのであればスレッドを分けることはないということです。
Form1の処理1
├────────────┐
↓ ↓
Form1の処理2 Form2のクリック待ち
│ │
│ Form2のクリック
↓ │
Form2の処理2を中断 │
│
┌────────────┘
↓
Form1を表示
だとしたらForm1の処理2を別スレッドにする必要はありますが。