VB.Net (or C#) 2008 Multi Threaded Import - sql-server

I am looking to build a multi-threaded text import facility (generally CSV into SQL Server 2005) and would like to do this in VB.NET but I am not against C#. I have VS 2008 trial and just dont know where to begin. Can anyone point me in the direction of where I can look at and play with the source of a VERY simple multi-threaded application for VS 2008?
Thanks!

This is a great article:
http://www.devx.com/DevX/10MinuteSolution/20365
In particular:
Dim t As Thread
t = New Thread(AddressOf Me.BackgroundProcess)
t.Start()
Private Sub BackgroundProcess()
Dim i As Integer = 1
Do While True
ListBox1.Items.Add("Iterations: " + i)
i += 1
Thread.CurrentThread.Sleep(2000)
Loop
End Sub

The referenced DevX article is from 2001 and .Net Framework 1.1, but today .Net Framework 2.0 provides the BackgroundWorker class. This is the recommended threading class if your application includes a foreground UI component.
From MSDN Threads and Threading:
If you need to run background threads
that interact with the user interface,
the .NET Framework version 2.0
provides a BackgroundWorker component
that communicates using events, with
cross-thread marshaling to the
user-interface thread.
This example from MSDN BackgroundWorker Class shows a background task, progress %, and cancel option. (The example is longer than the DevX sample, but has a lot more functionality.)
Imports System.ComponentModel
Partial Public Class Page
Inherits UserControl
Private bw As BackgroundWorker = New BackgroundWorker
Public Sub New()
InitializeComponent()
bw.WorkerReportsProgress = True
bw.WorkerSupportsCancellation = True
AddHandler bw.DoWork, AddressOf bw_DoWork
AddHandler bw.ProgressChanged, AddressOf bw_ProgressChanged
AddHandler bw.RunWorkerCompleted, AddressOf bw_RunWorkerCompleted
End Sub
Private Sub buttonStart_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
If Not bw.IsBusy = True Then
bw.RunWorkerAsync()
End If
End Sub
Private Sub buttonCancel_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs)
If bw.WorkerSupportsCancellation = True Then
bw.CancelAsync()
End If
End Sub
Private Sub bw_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs)
Dim worker As BackgroundWorker = CType(sender, BackgroundWorker)
For i = 1 To 10
If bw.CancellationPending = True Then
e.Cancel = True
Exit For
Else
' Perform a time consuming operation and report progress.
System.Threading.Thread.Sleep(500)
bw.ReportProgress(i * 10)
End If
Next
End Sub
Private Sub bw_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
If e.Cancelled = True Then
Me.tbProgress.Text = "Canceled!"
ElseIf e.Error IsNot Nothing Then
Me.tbProgress.Text = "Error: " & e.Error.Message
Else
Me.tbProgress.Text = "Done!"
End If
End Sub
Private Sub bw_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs)
Me.tbProgress.Text = e.ProgressPercentage.ToString() & "%"
End Sub
End Class

About the best threading document I ever found was this http://www.albahari.com/threading/
If I may, the problem with simple examples is that that they're often too simple. Once you get past the counting or sort in background demos you generally need to update the UI or similar and there are some gotchas. Similarly you rarely have to deal with resource contention in simple examples and having threads degrade gracefully when a resource isn't available (such as a Db connection) requires thought.
Conceptually you need to decide how you're going to distribute your work across the threads and how many do you want. There's overhead associated with managing threads and some mechanisms use a shared thread pool that could be subject to resource contention itself (for example, any time you run a program that simply displays an empty form, how many threads do you see under task manager).
So for your case, you threads doing the actual uploading need to signal back if they've completed, if they've failed (and what the failure was). The controller needs to be able to deal with those and manage the start/stop processes and so on.
Finally (almost), assuming that making something multithread will increase performance doesn't always hold true. If for example, you chop a file up into segments but it has to travel across a low speed link (ADSL say), you're constrained by external forces and no amount of threading trickery is going to get around that. The same can apply for database updates, web requests, anything invloving large amounts of disk i/o and so on.
Despite all this, I'm not the prophet of doom. The references here are more than adequate to help you achieve what you want but be aware that one of the reasons threading seems complicated is because it can be :)
If you want more control than the BackgroundWorker/Threadpool but don't want to do everything yourself there are at least two very good freebie threading libraries knocking around the place (Wintellect & PowerThreading)
Cheers
Simon

Related

vb.Net: Creating UI in a BackgroundWorker

I'm working on an application to be able to monitor production information on a 3x3 (so 9 screen) video wall. One of the screen sets that I'm working on right now retrieves information and then formats for display on the screen. It takes about 2 seconds to retrieve and format this data (just a rough guess, not actually measured). Because it does 9 screen, one after the other, there is a very noticeable amount of time to switch to this screen set. The PC driving this video wall has 8 processing cores, so while one processor is chugging away doing all this work, there's plenty of processing power just sitting idle.
My first thought is that I need to use multi-threading. Unfortunately, I'm very new to this concept. I really have only used it one other time. I tried creating a BackgroundWorker and having the DoWork routine generate my UI. Unfortunately, it crashes the first time I try to create a UI element (Dim grLine as New Grid). I did manage to get around that by having a dummy DoWork routine and generating all my UI in the RunWorkerCompleted routine. This does allow my blank window to show up immediately, but none of the UI I'm generating shows up until it has all been rendered.
Here's a very cleaned up version of what I'm trying to do:
For i As Integer = 1 to 9
Dim win As New MyCustomWindow
win.DisplayScreen = i ' This function in MyCustomWindow sets the Bounds
win.MyShow({1, 2}) ' Sample args
Globals.VideoWall.Windows(i) = win
Next
The MyCustomWindow Class:
Class MyCustomWindow
Public Sub MyShow(a() as Integer)
Me.Show() ' Has a "Loading..." TextBlock
Dim bw as New ComponentModel.BackgroundWorker
AddHandler bw.DoWork, AddressOf Generate_UI_DoWork
AddHandler bw.RunWorkerCompleted, AddressOf Generate_UI_Complete
bw.RunWorkerAsync(a)
End Sub
Private Sub Generate_UI_DoWork((sender As Object, e As ComponentModel.DoWorkEventArgs)
' Pass our arguments to the Complete routine.
e.Result = e.Argument
End Sub
Private Sub Generate_OpsMarket_Complete(sender As Object, e As ComponentModel.RunWorkerCompletedEventArgs)
Dim IDs() as Integer
IDs = e.Result
Dim grLine As New Grid ' We crash here if this code is in DoWork instead of RunWorkerCompleted
For Each id As Integer In IDs
grLine.RowDefinitions.Add(New RowDefinition)
Dim txt as New TextBlock ' For a header
grLine.Children.Add(txt)
grLine.RowDefinitions.Add(New RowDefinition)
Dim MyCtrl as New MyCustomControl()
MyCustomControl.GetData(id)
grLine.Children.Add(MyCtrl.MyGrid)
txt.Text = MyCtrl.Header
Next
txLoading.Visibility = Visibility.Hidden
grRoot.Children.Add(grLine)
End Sub
End Class
I tried to leave enough detail in the code so hopefully it'll be evident what I'm trying to accomplish, but keeping it small enough to not be overwhelming.
Edited to add:
The bulk of the work happens in MyCustomControl.GetData(id) ... that Sub downloads the data from a web server (in JSON format), parses the JSON, then generates the rows (3) and columns (30 or 31, depending on the month) for the Grid and fills in the data it received from the web server.
Your current BackgroundWorker implementation give you no benefits, as you noticed by your self.
Your main problem is that your current code/logic tightly depend on UI controls. Which strict you with UI thread. Because creating/updating UI controls can be done only on UI thread - that's why you got Exception when trying create/update UI controls in BackgroundWorker.DoWork handler.
Suggest to separate logic which retrieve monitoring information in parallel and then you can create/update control on UI with already formatted data.
This is raw/pseudo example
Class DataService
Public Function GetData(ids As Integer()) As YourData
' Get data from web service
' Validate and Format it to YourData type or List(Of Yourdata)
Return data
End Function
End Class
Class MyCustomWindow
Public Sub MyShow(a() as Integer)
Me.Show() ' Has a "Loading..." TextBlock
Dim bw as New ComponentModel.BackgroundWorker
AddHandler bw.DoWork, AddressOf Generate_UI_DoWork
AddHandler bw.RunWorkerCompleted, AddressOf Generate_UI_Complete
bw.RunWorkerAsync(a)
End Sub
Private Sub Generate_UI_DoWork((sender As Object, e As ComponentModel.DoWorkEventArgs)
Dim service As New DataService()
Dim data = service.GetData(e.Argument)
e.Result = data
End Sub
Private Sub Generate_OpsMarket_Complete(sender As Object,
e As ComponentModel.RunWorkerCompletedEventArgs)
Dim data As Yourdata = DirectCast(e.Result, YourData)
'Update UI controls with already formatted data
End Sub
End Class
Update on Sub downloads the data from a web server
In this case you don't need multi-threading/parallel at all. Because you loading time is waiting time for response. In this case my advice will be using async/await approach, which will release UI thread(make it responsive) while you waiting for response from web-service.
Class DataService
Public Async Function GetDataAsync(ids As Integer()) As Task(Of YourData)
Using client As HttpClient = New HttpClient()
Dim response As HttpResponseMessage = Await client.GetAsync(yourUrl)
If response.IsSuccessStatusCode = True Then
Return Await response.Content.ReadAsAsync<YourData>()
End If
End Using
End Function
End Class
Then in the view you don't need BackgroundWorker
Class MyCustomWindow
Public Async Sub MyShow(a() as Integer) As Task
Me.Show() ' Has a "Loading..." TextBlock
Dim service As New DataService()
Dim data As YourData = Await service.GetDataAsync(a)
UpdateControlsWithData(data)
End Sub
Private Sub UpdateControlsWithData(data As YourData)
' Update controls with received data
End Sub
End Class
For what it's worth, here are a few examples which do 9 x 500ms of data operations, then simple UI operation.
The first example runs on a background thread but the main loop runs in sequence. The messagebox at the end shows that it takes around 4500 ms because of the 500 ms thread sleep is run sequentially. Notice how the DoWork method has nothing to do with the UI. It uses two threads: the UI thread and one background worker. Since it's not doing work on the UI, the form is responsive while the background worker is working.
Private bw_single As New BackgroundWorker()
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
AddHandler bw_single.DoWork, AddressOf bw_single_DoWork
AddHandler bw_single.RunWorkerCompleted, AddressOf bw_single_Complete
bw_single.RunWorkerAsync()
End Sub
Private Sub bw_single_DoWork(sender As Object, e As DoWorkEventArgs)
' runs on background thread
Dim data As New List(Of Integer)()
Dim sw As New Stopwatch
sw.Start()
For i As Integer = 1 To 9
' simulate downloading data, etc.
Threading.Thread.Sleep(500)
data.Add(i)
Next
sw.Stop()
e.Result = New Result(data, sw.ElapsedMilliseconds)
End Sub
Private Sub bw_single_Complete(sender As Object, e As RunWorkerCompletedEventArgs)
RemoveHandler bw_single.DoWork, AddressOf bw_single_DoWork
RemoveHandler bw_single.RunWorkerCompleted, AddressOf bw_single_Complete
' runs on UI thread
Dim res = CType(e.Result, Result)
Me.DataGridView1.DataSource = res.Data
MessageBox.Show(
String.Format("Performed on bw (single), took {0} ms, data: {1}",
res.Elapsed, String.Join(", ", res.Data)))
End Sub
(This is the class which holds the result of the background worker)
Private Class Result
Public Property Data As IEnumerable(Of Integer)
Public Property Elapsed As Long
Public Sub New(data As IEnumerable(Of Integer), elapsed As Long)
Me.Data = data
Me.Elapsed = elapsed
End Sub
End Class
The second example runs on a background thread but the main loop runs in parallel. The messagebox at the end shows that it takes around 1000 ms ... why? Because my machine like yours has 8 logical cores but we are sleeping 9 times. So at least one core is doing two sleeps and this will gate the entire operation. Again, there is one thread for the UI, one for the background worker, but for the parallel loop, the OS will allocate CPU time from the remaining cores to each additional thread. The UI is responsive and it takes a fraction of the time of the first example to do the same thing
Private bw_multi As New BackgroundWorker()
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
AddHandler bw_multi.DoWork, AddressOf bw_multi_DoWork
AddHandler bw_multi.RunWorkerCompleted, AddressOf bw_multi_Complete
bw_multi.RunWorkerAsync()
End Sub
Private Sub bw_multi_DoWork(sender As Object, e As DoWorkEventArgs)
' runs on background thread
Dim data As New ConcurrentBag(Of Integer)()
Dim sw As New Stopwatch
sw.Start()
Parallel.For(1, 9,
Sub(i)
data.Add(i)
Threading.Thread.Sleep(500)
End Sub)
sw.Stop()
e.Result = New Result(data, sw.ElapsedMilliseconds)
End Sub
Private Sub bw_multi_Complete(sender As Object, e As RunWorkerCompletedEventArgs)
RemoveHandler bw_multi.DoWork, AddressOf bw_multi_DoWork
RemoveHandler bw_multi.RunWorkerCompleted, AddressOf bw_multi_Complete
' runs on UI thread
Dim res = CType(e.Result, Result)
Me.DataGridView1.DataSource = res.Data
MessageBox.Show(
String.Format("Performed on bw (multi), took {0} ms, data: {1}",
res.Elapsed, String.Join(", ", res.Data)))
End Sub
Since the above two examples utilize background workers to do their work, they will not freeze the UI thread. The only code running on the UI is in the button click handlers and the RunWorkerCompleted handler.
Lastly, this example uses only a single UI thread. It will freeze the UI while it's running for 4500 seconds. Just so you know what to avoid...
Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
Dim data As New List(Of Integer)()
Dim sw As New Stopwatch
sw.Start()
For i As Integer = 1 To 9
' simulate downloading data, etc.
Threading.Thread.Sleep(500)
data.Add(i)
Next
sw.Stop()
Dim res = New Result(data, sw.ElapsedMilliseconds)
Me.DataGridView1.DataSource = res.Data
MessageBox.Show(
String.Format("Performed on bw (single), took {0} ms, data: {1}",
res.Elapsed, String.Join(", ", res.Data)))
End Sub
Summarily, you should figure out how to separate the data layer from the UI. See separation of concerns and the SO question, Why is good UI design so hard for some Developers?, and this one What UI design principles like “separation of concerns” can I use to convince developers that the UI needs fixing?

WPF VB.Net ProgressBar updating in an Async Method

I've seen a few other posts about this but I seem to be confused since I've seen it done several ways and haven't gotten it correct any of the ways, so I thought I would ask specifically for my case so I can learn what I am doing wrong. After learning and switching to C# for most of my programming, VB.net seems so clunky in its syntax with a lot of things, especially lambda and "on-the-fly" functions.
I have a long running task for a football game I am working on where it generates players. Its an async method utilizing the Task.Factory.StartNew method.
Here is the pertinent code:
Private Sub CreateDraft_Click(sender As Object, e As RoutedEventArgs) Handles CreateDraft.Click
TimeIt(Sub() ReallyGenNewPlayers())
End Sub
'Uses a stopwatch to time how long it takes and sends to console.
Private Sub TimeIt(MyAction As Action)
Dim SW As New Stopwatch
SW.Start()
MyAction()
Console.WriteLine($"Total Time Generating Players: {SW.Elapsed} seconds")
SW.Stop()
End Sub
Private Async Sub GenNewPlayersASync()
Dim myvalue = 0
'Generate the Players on an Async Thread
Dim x As Integer
For i As Integer = 1 To NumPlayers
x = i 'avoids issue with using the iteration variable inside the delegate
CollegePlayers.GenDraftPlayers(i, MyDraft, DraftDT, DraftClass, PosCount)
'Prog_ValueChanged(DBNull.Value, New RoutedPropertyChangedEventArgs(Of Double))
'Calls a delegate to update the progress bar to avoid having the variable locked by the background thread
Dispatcher.Invoke(Sub()
worker.ReportProgress((x / NumPlayers) * 100)
End Sub)
Next i
End Sub
'Creates a task to run the player generation code and wait til its finished
Private Sub ReallyGenNewPlayers()
Dim mytask = Task.Factory.StartNew(Sub() GenNewPlayersASync())
Task.WaitAll(mytask)
End Sub
So here is what I would like to do:
I have a progressbar I created in XAML that has a Progress_Changed Event. This is what I have for it so far based on another post, but the issue is when I have to call the function inside GenNewPlayersAsync() where it wants a RoutedPropertyChangeEventArgs as a double which I'm not exactly sure what to do...I tried creating one using .New(old value, new value) but that didn't work either and threw an error.
Public Async Sub Prog_ValueChanged(sender As Object, e As RoutedPropertyChangedEventArgs(Of Double))
Dim progress = New Progress(Of Integer)(Function(percent)
Prog.Value = e.NewValue
Return Prog.Value
End Function)
Await Task.Run(Sub() DoProcessing(progress))
End Sub
Public Sub DoProcessing(progress As IProgress(Of Integer))
Dim i As Integer = 0
While i <> 100
Thread.Sleep(100)
' CPU-bound work
If progress IsNot Nothing Then
progress.Report(i)
End If
End While
End Sub
I would like for the progressbar to be bound to an INotifyChanged Property and update itself automatically when the value gets changed. None of it seems to be working. The UI is still unresponsive and when I set different parts to ASync, I start getting Exceptions where it appears certain parts of the generation algorithm aren't working as they are returning null...very confused with all of this, and I am sure the answer is probably pretty simple...
If you guys could give several examples of different ways to get this to work so I can learn different methods and maybe state the pros and cons of the method I would greatly appreciate it...

Updating Textbox based off SQL InfoMessages results

Branching off a previous question... Retrieving only PRINT command from SQL Server procedure in VB.NET
I was able to dump all the text from SQL Server's messages and use what I need but for some reason I am unable to update my textbox with the events.
Private Shared Sub OnInfoMessage(ByVal sender As Object, ByVal e As System.Data.SqlClient.SqlInfoMessageEventArgs)
Dim Counter As Integer
Counter = 1
For Each line In e.Message.Split(vbNewLine)
If (line.Contains("====")) Then
RestoreTool.txtTRNStatus.Text = "TRN #" & Counter & "Restored"
Using LogFile As IO.StreamWriter = New IO.StreamWriter("C:\SDBT\worklog.ft", True)
LogFile.WriteLine(line)
End Using
Counter += 1
End If
Next
Everything runs smoothly but the textbox (txtTRNStatus) does not update with anything and remains blank (Where it should be showing "TRN #1 Restored")
I am utilizing a Background Worker as well to call the procedure that actually performs the restoration of a database. That procedure is what contains the event handler for the SQLInfoMessages.
Dim SQL As SqlCommand
Dim DBConn As New SqlConnection(ConnectionString)
SQL = New SqlCommand(DBScript, DBConn)
AddHandler DBConn.InfoMessage, New SqlInfoMessageEventHandler(AddressOf OnInfoMessage)
Dim SQLResult As IAsyncResult = SQL.BeginExecuteNonQuery()
Try
Catch e As Exception
MessageBox.Show(e.Message)
End Try
SQL.EndExecuteNonQuery(SQLResult)
CompletedTask = True
DBConn.Close()
I have a feeling the only way I can get the text to update is through the addition of something in the DoWork() portion of the Backgroundworker but cannot figure out what exactly. Can the OnInfoMessages be called at will when an update is needed or is it only specific to where the Event Handler is?
As you can see in the image, It's during the actual "restoration" that the tool would normally report which transaction log has been restored based off of the SQL Message received but it remains blank...
Thank you once again!
EDIT: My progress update for my background worker is set up correctly, I just am having trouble calling events from the OnInfoMessage Sub as a progress update which is where gets the next line from SQL.
Make sure you use the backgroundworker properly. In the "doWork" there must not have any line of code that affect the UI(UserInterface) as this thread isn't the main thread of the application. If it does, it will either crash or do nothing.
So if you need to change the UI from the background worker you will need to tell the main thread to do so from the "DoWork" method. This can be achieved by hooking a callback to event "ProgressChanged" of your backgroundWorker object.
' This event handler updates the UI. '
Private Sub backgroundWorker1_ProgressChanged( _
ByVal sender As Object, ByVal e As ProgressChangedEventArgs) _
Handles backgroundWorker1.ProgressChanged
Me.txtTRNStatus.Text = e.UserState.ToString()//Update UI
End Sub
From the "DoWork" method, raise this event like shown below to tell main thread to update UI. Use the second parameter to pass whatever is needed by main thread to do so.
Private Sub backgroundWorker1_DoWork(sender As Object, e As DoWorkEventArgs)
' This method will run on a thread other than the UI thread. '
' Be sure not to manipulate any Windows Forms controls created'
' on the UI thread from this method.'
Dim SomeObject As [Object]
//some stuff
backgroundWorker.ReportProgress(1, SomeObject)
//some stuff
backgroundWorker.ReportProgress(2, SomeObject)
End Sub
See MSDN for more detail on backgroundworker usage.
Or you could just use "invoke" like #Mark suggested in the comment. This tell main thread execute a sub. As long as the UI update is done by the main thread you will be fine.

asynchronous threads and anonymous delegates

Ok, now I can use 2 techniques to launch my threads: Dispatcher and BackgroundWorker.
Dispatcher:
' I launch the asynchronous process
Dim a As New Action(AddressOf operazioneLunga)
a.BeginInvoke(Nothing, Nothing)
' I update the GUI passing some parameters
Dim a As New Action(Of Integer, String)(AddressOf aggiorna_UI)
Me.Dispatcher.BeginInvoke(DispatcherPriority.Normal, a, 5, "pippo")
BackgroundWorker:
Private bw As BackgroundWorker = Nothing
Private Sub initial()
bw = New BackgroundWorker
AddHandler bw.DoWork, AddressOf longOp
AddHandler bw.RunWorkerCompleted, AddressOf endBGW
bw.RunWorkerAsync ()
End Sub
Private Sub longOp(ByVal sender As Object, ByVal e As DoWorkEventArgs)
Dim l As List(Of miaClasse2) = <Long Operation ...>
e.Result = l
End Sub
Private Sub endBGW(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
Dim l As List(Of miaClasse2) = e.Result
Dim be As BindingExpression = BindingOperations.GetBindingExpression(mioDatagrid, DataGrid.ItemsSourceProperty)
Dim m As miaClasse1 = DirectCast(be.DataItem, miaClasse1)
m.GetData (l)
mioDatagrid.UpdateLayout()
mioDatagrid.ScrollIntoView (mioDatagrid.Items(0))
RemoveHandler bw.DoWork, AddressOf massiccia
RemoveHandler bw.RunWorkerCompleted, AddressOf fineBGW
bw.Dispose()
End Sub
I don't know what is better, but I think I'll use BackgroundWorker, because I suppose there are other argouments about Dispatcher I have to know and I don't feel safe.
Pileggi
My previous post:
Hi everyone!
My application is in WPF / Vb framework 3.5 SP1. I need to execute some methods on asynchronous threads. I know this way:
Private Delegate Sub dMassiccia()
Private Delegate Sub dAggiornaUI()
Private Sub iniziale()
Dim massicciaTemp As New dMassiccia(AddressOf massiccia)
massicciaTemp.BeginInvoke(Nothing, Nothing)
End Sub
Private Sub massiccia()
'long operations...
Me.Dispatcher.BeginInvoke(DispatcherPriority.Normal, _
New dAggiornaUI(AddressOf aggiornaUI))
End Sub
Private Sub aggiornaUI()
'update the UI...
End Sub
But in this way I have to declare a delegate for every mothod I want to launch on an asynchronous thread, and it's very uncomfortable. I have a lot of method to launch in this way. I know there are the anonymous delegates, but I don't know how to use them in this case.
Can you help me?
Pileggi
PS. Other information: in this moment I don't need to lookup the status of the process launched in the asynchronous thread. The long operations are some requests to a webservice that can take some seconds every time. There is no problem for the number of threads, because I limit the possibilities for the user to start new threads until one of them is finished. I need the asyncronous threads, among other reasons, because I don't wont to block the application, I want to replace the mouse cursor with a user-control, etc..
What is it that you're trying to do, that requires you to launch all of these threads? It looks like you're creating the secondary thread just to be able to do GUI updates.
First of all, if you have to create a lot of threads, then you run the risk of running out of available threads pretty quickly. I thought the max was only 64, but the documentation says 250 per process, and it's also settable via GetMaxThreads and SetMaxThreads. Regardless, you need to decide if using the ThreadPool threads (which is what's used when you use BeginInvoke/EndInvoke) is appropriate for you.
How long do your GUI updates take? Are they going to run the entire duration of your application? Can you use a regular thread instead? Look into using a BackgroundWorker for GUI updates if you just need to update status information periodically. In some cases, even DispatcherTimer might do the trick. It just depends on what you want to do.
You also don't show all of your code, but in what's posted, EndInvoke is not called. If you do this and end up throwing an exception, you won't be able to catch it and handle the error properly.

Closing form from different thread happening too soon, causing exception

I've got the following situation,
Private Sub MyButton_Click(sender as Object, args as EventArgs) Handles MyButton.Click
Me.pleaseWaitFrm = New PleaseWaitForm()
' Fire up new thread to do some work in (AddressOf DoMyWork)
Me.pleaseWaitFrm.ShowDialog()
End Sub
Private Sub DoMyWork()
Dim log = Me.DoTheActualWork()
Me.pleaseWaitFrm.Close()
Using logFrm as New LogViewer(log)
logFrm.ShowDialog()
End Using
End Sub
If the DoTheActualWork() call exits fast enough, the Me.pleaseWaitFrm.Close() call is happening during the Me.pleaseWaitFrm.ShowDialog() call. The result, no surprise, is this exception:
An unhandled exception of type 'System.InvalidOperationException' occurred in System.Windows.Forms.dll
Additional information: Value Close() cannot be called while doing CreateHandle().
Obviously this is the old "you cannot .Close() a WinForm while it's in the process of loading" problem. But what isn't obvious to me is how best to prevent that from happening in this case? How does one safely and reliably delay the .Close() until it is safe to do so in this situation?
Me.pleaseWaitFrm.Close()
That's an illegal call, you are not allowed to close a form from another thread. Not sure how you got away with it, that should raise an IllegalOperationException when you run with a debugger. Review your code and delete any assignment to the Control.CheckForIllegalCrossThreadCalls property.
This exception you're getting is a clear side-effect of this. You must use Control.Invoke() to get the dialog closed. This automatically solves your problem, the delegate target cannot execute until the dialog is loaded.
Leverage the BackgroundWorker class, it makes this easy. You can close the dialog in a RunWorkerCompleted event handler.
Why not set a "ShouldClose" flag which can be checked when it's safe to close the form - and close it if required?
With regards to a code example, I'd implement it slightly differently but let me know if this breaks any other requirements and we can modify it...
''in PleaseWaitForm:
Public Property ShouldClose as boolean = false
Private Sub frmSplash_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Me.Close()
End Sub
''Your posted code
Private Sub MyButton_Click(sender as Object, args as EventArgs) Handles MyButton.Click
Me.pleaseWaitFrm = New PleaseWaitForm()
'' Fire up new thread to do some work in (AddressOf DoMyWork)
Me.pleaseWaitFrm.ShowDialog()
End Sub
Private Sub DoMyWork()
Dim log = Me.DoTheActualWork()
Me.pleaseWaitFrm.ShouldClose = True
If Me.pleaseWaitFrm.Created Then
Me.pleaseWaitFrm.Created.Close
End If
Using logFrm as New LogViewer(log)
logFrm.ShowDialog()
End Using
End Sub
In short, if we Can close the form, we do - otherwise, set a flag and the form will do it when it finishes loading.
I tested and didn't get any issues calling Me.Close inside the frmSplash.Load() but if you do encounter any problems, you can make it cast-iron by having a worked on frmSplash which checks the value rather than use the Load event
Edit: Spotted and fixed a bug

Resources