-
Jan 20th, 2022, 03:34 PM
#1
Thread Starter
Lively Member
Making the Paint routine fire?
I am writing a simple Visual Basic application. The application create a form, and in the form_Load routine, another subroutine is called. The subroutine is defined using Async because ‘Await’ is used within the sub. The subroutine ends with Me.Refresh. The problems are these:
- form_Paint is being called before the subroutine has executed and the subroutine creates information that is needed by the form_Paint routine.
- The command Me.Refresh does not cause form_Paint to be called. (I am wondering if this issue is caused by form_Paint’s having been called already but without the needed info.)
Suggestions?
-
Jan 20th, 2022, 04:29 PM
#2
Hyperactive Member
Re: Making the Paint routine fire?
I would try form1_show instead of form1_load
I would also try putting application create a form in its own sub.
I have had trouble putting things in the form.load.
Last edited by georgesutfin; Jan 20th, 2022 at 04:48 PM.
-
Jan 20th, 2022, 05:04 PM
#3
Re: Making the Paint routine fire?
I wouldn't be calling Refresh at all. What that does is force a redraw. It's a bit rude. You do that when you have to interrupt something else to do a redraw. That doesn't sound appropriate for what you described. Invalidate should be sufficient. With Invalidate, painting will happen when the UI thread has some time to process messages, in this case, the paint message. That time will come once the sub is done, but not while the sub is still running. Refresh will force the drawing right away, before the sub completes.
However, you might consider a few other points. There is the constructor, the Load event, and the Shown event. I'd say that you probably chose the worst one, but I'm not quite sure about that. The constructor is called when the form is created. The other two fire as the form is shown. The creation of the form could be done well before the form is shown, depending on how your program works. If the form is the startup form, then it will be shown right after creation, and there's not much you can do about that. If you are showing the form later, then you might be able to create it long before you show it.
It sounds like the sub might be a good candidate to put in the constructor (Sub New), rather than the Load event.
Additionally, the load event is happening before the form is shown. Does it really make sense to do either invalidate OR refresh at that point? Perhaps those could be moved later...or left out entirely. If you are doing the work early on, what's to invalidate? The form hasn't even drawn the first time, let alone had a need to repaint.
My usual boring signature: Nothing
-
Jan 21st, 2022, 07:19 AM
#4
Re: Making the Paint routine fire?
If the sub is Asynchronous, I assume that means it runs on its own thread. Normally non-GUI threads shouldn't try to perform GUI actions and I would think Me.Refresh would qualify as a GUI action.
You may have to Invoke the call, but I haven't worked with async subs, so may be mistaken.
"Anyone can do any amount of work, provided it isn't the work he is supposed to be doing at that moment" Robert Benchley, 1930
-
Jan 21st, 2022, 07:31 AM
#5
Re: Making the Paint routine fire?
Originally Posted by groston
I am writing a simple Visual Basic application. The application create a form, and in the form_Load routine, another subroutine is called. The subroutine is defined using Async because ‘Await’ is used within the sub. The subroutine ends with Me.Refresh. The problems are these:
- form_Paint is being called before the subroutine has executed and the subroutine creates information that is needed by the form_Paint routine.
- The command Me.Refresh does not cause form_Paint to be called. (I am wondering if this issue is caused by form_Paint’s having been called already but without the needed info.)
Suggestions?
I would suggest that you look into how Await/Async actually works. It doesn't work the way you may think it does.
Await actually breaks a method into discrete executable blocks to be executed at different times. When an Await is performed, it will actually yield to the application's message loop which among other things will allow events like Paint to fire.
You can test this for yourself using this code:-
Code:
Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Debug.WriteLine("Entered Form_Load")
Debug.WriteLine("Exiting Form_Load")
End Sub
Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
Debug.WriteLine("Form_Paint")
End Sub
End Class
The above code is pretty predictable. It will output this:-
Code:
Entered Form_Load
Exiting Form_Load
Form_Paint
However if you did this:-
Code:
Public Class Form1
Private Async Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Debug.WriteLine("Entered Form_Load")
Await Task.Delay(500)
Debug.WriteLine("Exiting Form_Load")
End Sub
Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
Debug.WriteLine("Form_Paint")
End Sub
End Class
You will get this output:-
Code:
Entered Form_Load
Form_Paint
Exiting Form_Load
The Paint event gets executed before Form_Load has exited because the Await stops execution of the current method at that point and yields to the application's message loop which is why the Paint event is fired before Form_Load is finished executing. This is the fundamental problem with your code.
-
Jan 21st, 2022, 10:42 AM
#6
Re: Making the Paint routine fire?
I think we might all be making assumptions based on not seeing the code and interpreting what was written in different ways. I felt that Load called a Sub, which had an Await somewhere in it, but also ended with a call to .Refresh. If so, that would suggest that the Sub itself is on the UI thread, though it launches something on a different thread. I also thought that the Refresh was coming at the end of the Sub, so whether the messages could be processed would be irrelevant. Both of those are just interpretation, though, and both could be wrong.
My usual boring signature: Nothing
-
Jan 21st, 2022, 03:25 PM
#7
Re: Making the Paint routine fire?
Eh....I think he was very clear about what he was describing. Lets look:-
form_Load routine, another subroutine is called.
The subroutine is defined using Async because ‘Await’ is used within the sub.
The subroutine ends with Me.Refresh.
That is pretty clear to me. Here is a mock up of what those 3 quotes above describe:-
Code:
Public Class Form1
Private Async Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Await SomeSub()
End Sub
Private Async Function SomeSub() As Task
'Lets pretend this is some long running task
Await Task.Delay(1000)
Me.Refresh()
End Function
End Class
Now let's look at the problem he has:-
form_Paint is being called before the subroutine has executed and the subroutine creates information that is needed by the form_Paint routine.
If we test this using this code:-
Code:
Public Class Form1
Private Async Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Debug.WriteLine("Form_Load entered")
Await SomeSub()
Debug.WriteLine("Form_Load exit.")
End Sub
Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
Debug.WriteLine("Form_Paint")
End Sub
Private Async Function SomeSub() As Task
Debug.WriteLine("SomeSub entered")
'Lets pretend this is some long running task
Await Task.Delay(1000)
Debug.WriteLine("SomeSub exit")
Me.Refresh()
End Function
End Class
We get this output:-
Code:
Form_Load entered
SomeSub entered
Form_Paint
SomeSub exit
Form_Paint
Form_Load exit.
Highlighted in red is exactly the problem he describes. The Form's Paint event is being fired before his asynchronous subroutine returns. He makes it very clear that his subroutine, represented by SomeSub in the mock up, creates data that Paint depends on. This means that he would not want the Paint event to be raised before the subroutine has finished doing it's thing.
As for his second problem:-
The command Me.Refresh does not cause form_Paint to be called.
It actually does call Form_Paint. I honestly don't know why he thinks it didn't.
Originally Posted by Shaggy Hiker
If so, that would suggest that the Sub itself is on the UI thread, though it launches something on a different thread.
Not quite. You see, he specifically said that his subroutine has an Await inside it. What this means is that whatever the is being Awaited is what is probably what is running on a different thread but the sub that called Await, is going to be signed up as a continuation by the compiler which will be executed on the UI thread which is why you can safely call Refresh in that sub.
I think you fundamentally misunderstand how Async/Await works. Await doesn't magically make a sub or function multithreaded. It's actually just syntactic sugar. The compiler re-arranges the method into a series of continuations when it encounters Awaits. For example if I did something like this:-
Code:
Public Class Form1
Private Async Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim l As New Label() With {.Left = 0, .Top = 0}
Me.Controls.Add(l)
l.Text = "1"
Await Task.Delay(700)
l.Text = "2"
Await Task.Delay(700)
l.Text = "2"
Await Task.Delay(700)
l.Text = "3"
Await Task.Delay(700)
l.Text = "4"
Await Task.Delay(700)
l.Text = "5"
Await Task.Delay(700)
l.Text = "6"
Await Task.Delay(700)
l.Text = "7"
End Sub
End Class
You would notice than you can freely move the Form while Load is still executing. It can leave the impression that Form_Load is multithreaded but it's actually not. As proof, notice I can update the Label directly. That produces no error because it's not a cross thread call. The compiler breaks up Form_Load into a series of continuations that are executed in sequential by the UI thread itself. Note that, Task.Delay itself could be running on another thread. I don't think it actually does but lets pretend it is since a quite a few Tasks actually do. Even in such a case it's not a problem because the Task itself is not what is trying to update the UI, it's the code in-between the Awaits that does this and this code is executed on the UI thread.
Still though, you do raise a great point and we are still assuming. Perhaps I got it all wrong and OP's problem is completely different. He should come and clear things up. But if I go by what he said, his problem is pretty clear to me.
-
Jan 21st, 2022, 03:53 PM
#8
Re: Making the Paint routine fire?
That seems like a pretty clear description.
I didn't think that Paint would be called at all that early, but I've certainly never looked at it. What's the point of a paint message before the form is visible? There probably isn't a point, aside from consistency.
My usual boring signature: Nothing
-
Jan 21st, 2022, 04:05 PM
#9
Re: Making the Paint routine fire?
Originally Posted by Shaggy Hiker
What's the point of a paint message before the form is visible?
Actually, the Form would become visible as soon as Form_Load executes the first Await. You can test with this:-
Code:
Public Class Form1
Private Async Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Dim l As New Label() With {.Left = 0, .Top = 0}
Me.Controls.Add(l)
l.Text = "Hello world."
'The Form is shown as soon as this is executed
Await Task.Delay(5000)
l.Text = "Exiting Form_Load"
End Sub
End Class
If you executed the above code, you would see Form shown and fully operational before Form_Load has actually finished executing. This is because the compiler has actually split the Form_Load sub into two parts and the Await statement is the dividing line. So in a sense Form_Load has finished executing after Await is called. The rest of the Form_Load method which is whatever comes after Await is signed up as a continuation that is executed after Task.Delay has completed.
-
Jan 21st, 2022, 11:02 PM
#10
Re: Making the Paint routine fire?
Guess I haven't played around with Asynch Await enough.
My usual boring signature: Nothing
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|