Forms in threads

  • Thread starter Thread starter Klaus Löffelmann
  • Start date Start date
K

Klaus Löffelmann

Hello,

I understand, that it is allowed to create a new form in a seperate thread
as long as this form gets its own message pump via application.run.
(See
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetcomp/html/casoast.asp,
even if this example is for the compact framework and here
http://www.dotnet247.com/247reference/msgs/33/165734.aspx).
If I then want to execute an extensive operation in that form, I have to
apply Application.DoEvents to keep that form "alive". But on what message
pump(s?) does the DoEvents take effect?
The reason I ask is: When I ran some tests in a Virtual PC, DoEvents caused
a Overflow-Exception once in a while. On my "real" machine it ran ok every
time a tried.
Is that the right method to solve this problem, at all? (Keep in mind that I
want to create a generic, reusable class that has to show its own UI no
matter in which winforms application it's hosted in. So, creating a form
from the UI-Thread, running the thread independently from the UI, and only
updating controls via invoke would be to avoid, if possible).

Maybe someone knows some other links to articles that are dealing with this
stuff.

Thanks,

Klaus
 
Why do you need to run a form on a separate thread? It is possible to just
run a thread and have it call delegates on the owner forms thread. That way
you don't have to run message pumps, which kind-of defeats the object of
using a language like VB in the first place.
 
Robin,

as I already said:

I like to create a class, that I can use right away *and* that's
encapsulating a certain "problem solver" including a UI, so that it is
reusable without having the main UI prepared every time, I want to use it,
but which is thread safe, though.

Klaus
 
Yes, this is all fine. But I don't see why the form class cannot
encapsulate a thread object, that calls delegates on the form, while the
form itself is running on the main process thread. You don't have to run a
message pump in this case. I'm doing this with my current application. I
have a thread that is managed by a form and calls delegates on it (its
fetching thumbnails from a database and putting them into an owner draw list
box on the form). The form itself is created by the mainform and the UI
is of course responsive.
 
I have an interface, which my forms implement if they want to support worker
threads. The worker thread is created and started by the form and when it
completes an operation, it calls a delegate for one of these interface
procedures. Inside the interface procedure, the form handles the data
coming in from the thread (WorkerParameters). I'm not sure if this is
optimal, but it does work quite well.

Public Interface IWorkerThreadBase

'
/////////////////////////////////////////////////////////////////////////
' // Callback started operation
'
/////////////////////////////////////////////////////////////////////////
Sub StartedOperation(ByVal Parameters As WorkerParameters)

'
/////////////////////////////////////////////////////////////////////////
' // Callback performed operation
'
/////////////////////////////////////////////////////////////////////////
Sub PerformedOperation(ByVal nCurrentItem As Integer, ByVal Parameters
As WorkerParameters)

'
/////////////////////////////////////////////////////////////////////////
' // Callback finished operation
'
/////////////////////////////////////////////////////////////////////////
Sub FinishedOperation(ByVal Parameters As WorkerParameters)

'
/////////////////////////////////////////////////////////////////////////
' // The operation did not complete correctly.
'
/////////////////////////////////////////////////////////////////////////
Sub FailedOperation(ByVal Parameters As WorkerParameters)

'
/////////////////////////////////////////////////////////////////////////
' // Return the form object for this instance of the interface
'
/////////////////////////////////////////////////////////////////////////
Function GetForm() As System.Windows.Forms.Form

End Interface
 
Robin,

the problem is: I want to have a form that is able to display status
messages in my App no matter what thread called it (something like the
output window of the environment, only always available and bound to my
App). To this end, you normally you would create a form with a singleton
pattern, that creates and possibly displays itself, when its shared function
GetInstance didn't yet created an instance of the form. The problem is: When
GetInstance is called for the first time from an other thread, and the form
hasn't been created yet, it would be created on a thread different from the
ui-thread, wouldn't it?

By the way, I just realized, that creating a new message pump only sort of
"postpones" the problem; but maybe you have a better (and totaly different)
idea to solve that problem at hand?

Again, I really like to have this form independent, so that any other
developer can use it right away, without having it prepared in the main form
of the app.

Thanks,

Klaus
 
Well it can't create itself, so it has to be created somewhere. I mean you
can't just drop the class in and expect it to be usable without creating an
instance of it somewhere. Why not create the form in your main program
(with "new") but not have it visible until it is called by a thread?
 
Robin,

well actually, it can, in fact in its static constrcutor. What I achieved
with the attached class (based on a form, contains a multiline textbox,
nothing else) is that you can Write or WriteLine from what ever thread
without having anything instanciated or prepared. When the class is used for
the first time, its static constructor creates a new thread which only then
sets up an instance of the actually form which again has its own message
pump. The new thread is necessary for not having to bind the form to the
calling thread. Some synchronisation code was necessary, though, for
ensuring not to use the form before it actually got its handle.

Question is: Is that a "proper" way to do it?

Thanks for your thoughts!

Klaus

Imports System.Threading

Public Class ADThreadSafeInfoBox
Inherits System.Windows.Forms.Form

'Designer Created Stuff is left out

Private Shared myText As String
Private Shared myInstance As ADThreadSafeInfoBox
Private Shared myThread As Thread
Private Shared myManualResetEvent As ManualResetEvent

Private Delegate Sub TSWriteActallyDelegate()

Shared Sub New()
Console.WriteLine("Static Contructor")
myText = ""
myManualResetEvent = New ManualResetEvent(False)
CreateInstanceThroughThread()
End Sub

Shared Sub CreateInstanceThroughThread()
myThread = New Thread(New ThreadStart(AddressOf
CreateInstanceThroughThreadActually))
myThread.Name = "HelpingThread"
myThread.Start()
End Sub

Shared Sub CreateInstanceThroughThreadActually()
Debug.WriteLine("CurrentThread.Name:" + Thread.CurrentThread.Name)
myInstance = New ADThreadSafeInfoBox
AddHandler Application.ThreadExit, AddressOf ThreadExitHandler
AddHandler myInstance.HandleCreated, AddressOf HandleCreatedHandler
AddHandler myInstance.HandleDestroyed, AddressOf
HandleDestroyedHandler
Application.Run(myInstance)
End Sub

Shared Sub ThreadExitHandler(ByVal sender As Object, ByVal e As
EventArgs)
Console.WriteLine("ThreadExit")
myManualResetEvent.Reset()
myThread.Abort()
myInstance.Close()
Application.Exit()
End Sub

Shared Sub HandleCreatedHandler(ByVal sender As Object, ByVal e As
EventArgs)
Console.WriteLine("HandleCreated")
myManualResetEvent.Set()
End Sub

'Just for testing purposes
Shared Sub HandleDestroyedHandler(ByVal sender As Object, ByVal e As
EventArgs)
Console.WriteLine("HandleDestroyed")
End Sub

Public Shared Sub TSWriteLine(ByVal Message As String)
SyncLock (GetType(ADThreadSafeInfoBox))
Message += vbNewLine
TSWrite(Message)
End SyncLock
End Sub

Public Shared Sub TSWrite(ByVal Message As String)
SyncLock (GetType(ADThreadSafeInfoBox))
If Not myManualResetEvent.WaitOne(100, True) Then
Exit Sub
End If
myText += Message
myInstance.Invoke(New TSWriteActallyDelegate(AddressOf
TSWriteActually))
End SyncLock
End Sub

Private Shared Sub TSWriteActually()
myInstance.txtOutput.Text = myText
myInstance.txtOutput.SelectionStart = myText.Length - 1
myInstance.txtOutput.ScrollToCaret()
myInstance.txtOutput.Refresh()
'Debug.WriteLine("CurrentThread.Name:" + Thread.CurrentThread.Name)
End Sub
End Class
 
Back
Top