0

I have 3 form ;

Form1 - Main Form

Form2 - Sub form ( contains progress bar and timer)

Form3 - Sub form with heavy contains which takes time for loading ( like parsing data from webpage and writing it to Datagridview at form load event)

I need to show form2 with a progress bar running while form3 is loading

I have following codes at Form1 ;

Me.Hide
Form2.Show()
Form3.Show()

codes from Form 2 ;

Public Class Form2
Private Sub Form2_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    LoadingTimer.Enabled = True
End Sub

Private Sub LoadingTimer_Tick(sender As Object, e As EventArgs) Handles LoadingTimer.Tick

    If MyProgressBar.Value <= MyProgressBar.Maximum - 1 Then
        MyProgressBar.Value += 10
    End If
    If MyProgressBar.Value = 100 Then
        LoadingTimer.Enabled = False
        Me.Close()
    End If
    If Label1.ForeColor = Color.LimeGreen Then
        Label1.ForeColor = Color.White
    Else
        Label1.ForeColor = Color.LimeGreen
    End If
End Sub
End Class

The problem is progress bar starting but freezing at the beginning while Form3 is loading

Any idea for solution?

Ali
  • 33
  • 9

2 Answers2

1

Try making the process asynchronous, it is in my understanding that the timer tick is already asynchronous but in the form1, you could use could have that code inside an Task

Me.Hide
Task.Run(Function() Form2.Show())
Form3.Show()

I never reached this far on vb.net since i started to program on c# but this should do the trick

nalnpir
  • 1,167
  • 6
  • 14
  • You shouldn't modify UI elements in a different thread/task. Doing so will eventually break things, even if it works now. All UI-related work should be done on the UI thread only. – Visual Vincent Oct 18 '18 at 13:06
  • Your thoughts are correct, but not your code. Only move the heavy processing and other non-UI work to a task, _not_ the entire form. – Visual Vincent Oct 18 '18 at 13:14
  • @nalnpir, hi, your code is giving error, can you please recheck? – Ali Oct 18 '18 at 15:56
  • Ali Try instead of Function() using Sub(). @VisualVincent well being honest you re right i would always use Invoke in my c# code, i dont think a simple task like this could give any error though but Ali you should investigate more into the usage of the invoke method. – nalnpir Oct 18 '18 at 16:16
  • @nalnpir thank you but can you please give me a hint just one step more? could be in C## if you dont mind – Ali Oct 18 '18 at 17:37
  • _"i dont think a simple task like this could give any error"_ - This is exactly why the simple nature of .NET's multithreading techniques are problematic. It might seem so simple, but under the hood there's a lot going on and there's many of rules that needs to be followed in order to do things correctly. The slightest miss can break your application and very easily cause what's known as [_Undefined Behaviour_](https://en.wikipedia.org/wiki/Undefined_behavior). Thankfully the .NET runtime has methods in place that helps to protect against this, _but they're not foolproof_! – Visual Vincent Oct 18 '18 at 18:06
  • The so small action of showing a form on another thread can be a fatal problem. Your entire UI is controlled by a single message pump that operates in a certain thread (the UI thread) which's task is to dispatch and handle all messages that are sent to and generated by the UI. This can be mouse events, keyboard events, system events, window changes, the click of a button, the focusing of a window, the mere updating of the text in a text box, and many, many more. Everything you see and interact with (and even more than that) is controlled by this single message pump that runs on the UI thread. – Visual Vincent Oct 18 '18 at 18:14
  • By creating and showing a form on a different thread you are tieing it, and every control inside it, to that specific thread. Everything that needs to be synchronized will be so with that other thread instead of the UI thread, and now, suddenly, the message pump has to deal with messages across two different threads. What happens then when your new thread and the UI thread tries to access your form, or any of its controls, at the exact same time? Which one should be allowed to access it first, and what happens when one does it before the other? You've now got yourself a race condition. – Visual Vincent Oct 18 '18 at 18:24
  • Hoppfully, the framework would throw an exception before this happens, but as I said its detection methods aren't foolproof. In the case that the framework doesn't detect the problem, the outcome would be completely unpredictable and differ from time to time (_Undefined Behaviour_). So as you see, the golden rule _"All UI-related work should be done on the UI thread only"_ perhaps isn't so bad after all. It's better to obey it than have to deal with the consequences of not doing so later. -- I recommend checking jmcilhinney's answer for an example of how this can be done _more_ correctly. – Visual Vincent Oct 18 '18 at 18:34
1

If you're new to programming then this may be a bit confusing but the answer is to push the code from the Load event handler of Form3 into an Async method and await it. Your UI freezes because you're doing work synchronously on the UI thread. You need to either use a secondary thread explicitly or use Async/Await. This:

Private Sub Form3_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    'Do some work.
End Sub

would become this:

Private Async Sub Form3_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    Await DoWork()
End Sub

Private Async Function DoWork() As Task
    Await Task.Run(Sub()
                       'Do some work.
                   End Sub).ConfigureAwait(False)
End Function

Actually, that's probably more complex than necessary and this should work fine:

Private Async Sub Form3_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    Await Task.Run(Sub()
                       'Do some work.
                   End Sub).ConfigureAwait(False)
End Sub

Having reread your question, what you probably need to do is have your async method be a function that retrieves and returns the data from the web page or whatever and then you load that data into your DataGridView synchronously afterwards, e.g.

Private Async Sub Form3_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    DataGridView1.DataSource = Await GetDataAsync()
End Sub

Private Async Function GetDataAsync() As Task(Of DataTable)
    Return Await Task.Run(Function()
                              Dim table As New DataTable

                              'Populate table here.

                              Return table
                          End Function).ConfigureAwait(False)
End Function

So the GetDataAsync method returns a Task(Of DataTable), i.e. a Task that asynchronously executes a function that returns a DataTable. In the Load event handler, you call that method and await the Task, which means that your code will wait until the Task has executed and returned its data but without blocking the UI thread as a synchronous call would do. The synchronous equivalent would be this:

Private Sub Form3_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    DataGridView1.DataSource = GetData()
End Sub

Private Function GetData() As DataTable
    Dim table As New DataTable

    'Populate table here.

    Return table
End Function
jmcilhinney
  • 50,448
  • 5
  • 26
  • 46
  • sorry for my novice level question but what should i add for 'do some work part at Async Sub, would form2.show enough or need to add timer to form3? – Ali Oct 18 '18 at 15:56
  • @Ali : Inside `Task.Run()` you should put all heavy-processing code that _**doesn't**_ interact with the UI (User Interface). Anything that directly involves accessing your forms or any of their controls needs to be done before or after `Task.Run()`. – Visual Vincent Oct 18 '18 at 18:38
  • @Ali : If you absolutely must access the UI inside the task, you can use `Control.Invoke()` to access and return information from the UI thread (more info about that [here](https://stackoverflow.com/a/45571728/3740093), under _Accessing the UI thread_). **But beware!** Only access as small portions as possible at a time and leave as much processing as possible to the task. For instance, if you need to process a collection of items from one of your controls, clone it from the UI and let your task take care of the looping and such. – Visual Vincent Oct 18 '18 at 18:45
  • @VisualVincent, Thank you very much for your prompt answer, I am using visual studio 2017 and target net framework as 4.6.1. I believe the subject is going to be difficult as i have some simple question, like how the form load order should be, from Form1 ->load Form3 and from Form3 -> load Form2 (@ the outside of the task.run. I dont need any user interaction during that process, as the form3 is reading data from webpage and parsing at the begining – Ali Oct 18 '18 at 19:08
  • @Ali : If you're using tasks as jmcilhinney suggested the long-running code won't interrupt the rest of your code, thus the load order shouldn't matter unless you specifically need one form to be open before another (for example if Form3 needs to update Form2). However, having Form3 open Form2 like you suggested would make things more dynamic, as it would take care of showing the loading form automatically even if you were to open Form3 from other places in your code. – Visual Vincent Oct 18 '18 at 21:43
  • I've made an edit and added a new code example that may be helpful to you. – jmcilhinney Oct 18 '18 at 22:37