.net而崩溃时,松散的焦点WIN7 [英] .net while crash when loose focus win7
问题描述
我使用.net lang,
在 windows 10 的visual studio中创建了一个文件搜索程序。我的问题从form1开始,并带有一个 dim frm2 as form2 =新的form2
调用,
后,新的窗体显示我开始一个while循环form1,将数据馈送到表单2中的列表框:
<1> form1调用form2并显示它。
$ b 2)form1开始一个while循环
3)在while循环内部数据被馈送到frm2中的listbox1
现在,所有的工作都在 window 10 ,while循环可以运行尽可能多的,没有任何麻烦,窗口可以松散焦点和重新获得焦点没有显示任何不响应。 。msgs或white \ black screen ..
当我把软件带给我的朋友运行Windows 7的计算机,安装所有必需的框架和Visual Studio本身,运行它在调试模式下输入.sln,然后在同一个文件夹中搜索结果:
lockquote
1 p < while循环运行平稳,只要表单2不松散焦点
(在Windows 10上没有发生的事情)
2)当我点击屏幕上的任何地方软件松散的焦点是什么
导致1)发生(黑屏\白屏\不响应等)。 b
$ b
3)如果我等待循环所需的时间,不要点击其他任何地方 代码示例: 我的结论很简单! b 1) windows 7 不支持从while循环进行实时UI(标签)更新 我想mabye如果我运行一个线程中的所有代码mabye ui将保持响应 (通过UI在 windows 10 中没有响应的方式,但我仍然看到 所以线程将需要用数据更新外部文件,form2将需要从文件中读取数据,但是它会造成同样的问题吗?我不知道该怎么办..可以使用一些帮助和提示。 谢谢! 我必须指出循环正在进行的事实窗户10没有一个响应式用户界面意味着我不能点击任何按钮,但我可以 尽管我很高兴找到了解决方案,但我建议不要使用 请参阅以下博文: 保持您的用户界面响应和应用程序的危险。 DoEvents 。 简单地说, 然而,这有一些背面。 。例如: 如果在这个后台过程中关闭表单,很可能会抛出一个错误。另一个缺点是,如果通过点击按钮来调用 替换当然是正确的多线程(或者 Task Parallel Library >,无论你喜欢什么)。正常的多线程实际上并不是很难实现。 为了创建一个新的线程,你只需要声明一个 ...那么您应该设置它的 然后你可以打电话给 关于多线程的棘手部分是将调用封送到UI线程。后台线程通常无法访问UI线程上的元素(控件),因为这可能会导致并发问题(两个线程同时访问相同的控件)。因此,您必须通过安排在UI线程本身执行 将您的调用编组到UI中。这样你就不会再有并发的风险,因为所有与UI相关的代码都在UI线程上运行。 为了调用UI线程,你可以使用 还应该确保检查 基本的 然而, Visual Basic 2010(10.0)及以上 ,您可以使用 Lambda表达式 使得 更简单:
它会一直运行, strong>更新标签就像它应该发现
的文件量,甚至可以 100%成功完成循环
(再次除非我点击某处)
Sub ScanButtonInForm1()
Dim frm2 As Form2 = New Form2
frm2.Show()
Dim AlreadyScanned As HashSet(Of String)= New HashSet(Of String)
Dim stack As New Stack(Of String)
stack.Push(... Directoy从...开始搜索)
Do(stack.Count> 0)
frm2.Label4.Text = - 映射文件... - 文件夹左:+ stack.Count.ToString + - 找到的文件:+ frm2.ListBox1.Items.Count.ToString + -
frm2.Label4.Refresh()
Dim ScanDir As String = stack.Pop
If AlreadyScanned.Add(ScanDir)Then
Try
Try
尝试
Dim directoryName As String
对于每个directoryName在System.IO.Directory.GetDirectories(ScanDir)
stack.Push(directoryName)
frm2.Label4.Text = - 映射文件... - 文件夹左:+ stack.Count.ToString + - 找到的文件:+ frm2.ListBox1.Items.Count.ToString + -
frm2。 Label4.Refresh()
Next
frm2.ListBox1.Items.AddRange(System.IO.Directory.GetFiles(ScanDir,*。*,System.IO.SearchOption.AllDirectories))
捕获ex5作为UnauthorizedAccessException
结束尝试
捕获ex2作为System.IO.PathTooLongException
结束尝试
捕获ex4作为System.IO.DirectoryNotFoundException
结束尝试
End If
Loop
End Sub
$ b
从一个按钮调用...
2)windows 7可能支持一个新的
线程运行相同的循环
标签刷新和在形成松散焦点时没有任何崩溃..)所以我知道如何做到这一点,但我也知道,如果我这样做一个线程将无法更新列表框或标签在一个窗体中刷新它。
仍然看到标签刷新但在Windows 7上的一切工作相同
除非我点击某处,无论我点击Windows循环
崩溃
$ b $使用框架4.6.2开发者
Application.DoEvents()
,因为 这是不好的做法
Application.DoE vents()
是一个肮脏的解决方法,使您的UI看起来很敏感,因为它迫使UI线程处理所有当前可用的窗口消息。 WM_PAINT
是这些消息之一,这就是为什么你的窗口重新绘制。
ScanButtonInForm1()
方法,再次点击那个按钮(除非你设置了 Enabled = False
)并且再次启动这个过程,这又带来了另一个背面:
更多 Application.DoEvents()
- 开始你占用UI线程的次数越多,这会导致你的CPU用法上升很快。由于每个循环都在同一个线程中运行,因此您的处理器无法在不同的内核或线程上安排工作,因此您的代码将始终在一个内核上运行,并占用尽可能多的CPU可能。
基本知识
Thread
class 并传递将线程运行的方法委托给它:
Dim myThread As New Thread(AddressOf< your method here>)
IsBackground
属性 到 True
,如果你想在程序关闭的时候自动关闭它(否则程序一直打开,直到线程结束)。
开始()
和你有一个正在运行的后台线程!
$ b $ pre $ Dim myThread As New Thread(AddressOf myThreadMethod)
myThread.IsBackground = True
myThread.Start()
访问UI线程
控制。 Invoke()
或 Control.BeginInvoke()
方法。 BeginInvoke()
是异步版本,这意味着它不会等待UI调用完成,然后让后台线程继续工作。
InvokeRequired / Invoke
模式如下所示:
'这个委托将被用来告诉Control.Invoke()我们想要在UI线程上调用哪个方法。
Private Delegate Sub UpdateTextBoxDelegate(ByVal TargetTextBox As TextBox,ByVal Text As String)
Private Sub myThreadMethod()'线程运行的方法。
'做一些背景的东西...
如果Me.InvokeRequired = True那么''我'是当前的形式。
Me.Invoke(新UpdateTextBoxDelegate(AddressOf UpdateTextBox),TextBox1,状态更新!)'我们在后台线程,因此我们必须调用。
else
UpdateTextBox(TextBox1,Status update!)'我们在UI线程上,不需要调用。
End If
'做一些更多的背景资料...
End Sub
'这是Control.Invoke()将执行的方法。
Private Sub UpdateTextBox(ByVal TargetTextBox As TextBox,ByVal Text As String)
TargetTextBox.Text = Text
End Sub
$ b 新的UpdateTextBoxDelegate(AddressOf UpdateTextBox)
创建的新实例UpdateTextBoxDelegate
指向我们的 UpdateTextBox
方法(在UI上调用的方法)。
$ b
Private Sub myThreadMethod()
'做一些背景资料...
如果Me.InvokeRequired = True那么''我'是目前的形式。
Me.Invoke(Sub()TextBox1.Text =Status update!)'我们在后台线程,因此我们必须调用。
其他
TextBox1.Text =状态更新! '我们在UI线程上,不需要调用。
End If
'做一些更多的背景资料...
End Sub
现在,您只需键入 Sub()
,然后继续输入代码,就像使用常规方法一样:
If Me.InvokeRequired = True Then
Me.Invoke(Sub()
TextBox1.Text =Status update!
Me.Text =Hello world!
Label1.Location = New Point(128,32)
ProgressBar1.Value + = 1
End Sub)
否则
TextBox1.Text =状态更新!
Me.Text =Hello world!
Label1.Location =新点(128,32)
ProgressBar1.Value + = 1
End If
这就是你调用UI线程的方式!
简化
为了使 更简单,可以创建一个 扩展方法
将其放在一个单独的代码文件中:
Imports System.Runtime.CompilerServices
公共模块扩展
'''<总结>
'''在调用控件的线程上调用指定的方法(如有必要,否则在当前线程上)。
'''< / summary>
'''< param name =Control>控制线程来调用方法。< / param>
'''< param name =Method>要调用的方法。< / param>
'''< param name =参数>传递给方法的参数(可选)< / param>
'''<备注>< /备注>
< Extension()> _
Public Function InvokeIfRequired(ByVal Control As Control,ByVal Method As [Delegate],ByVal ParamArray Parameters As Object())As Object
如果参数IsNot Nothing AndAlso _
Parameters.Length = 0然后Parameters = Nothing
如果Control.InvokeRequired = True然后
返回Control.Invoke(方法,参数)
其他
返回Method.DynamicInvoke(参数)
End If
End Function
End Module
现在你只需要当你想访问UI时调用这个方法,不需要额外的 If-Then-Else
:
<$ p $ ()
$ b $ TextBuffer1.Text =Status更新!
Me.Text =Hello world!
Label1.Location = New Point(128 ,32)
End Sub)
'做更多的背景资料...
End Sub
$ b
用从UI返回对象/数据InvokeIfRequired() code $
使用我的 InvokeIfRequired()
扩展方法,您也可以从UI中返回对象或数据线程以简单的方式。例如,如果你想要一个标签的宽度:
Dim LabelWidth As Integer = Me.InvokeIfRequired(Function()Label1.Width )
下面的代码会增加一个计数器,告诉你线程已经运行了多久:
Private Sub Button1_Click(sender As System.Object,e As System.EventArgs)Handles Button1.Click
Dim CounterThread As New Thread(AddressOf CounterThreadMethod)
CounterThread.IsBackground = True
CounterThread.Start()
Button1.Enabled = False'使按钮不可点击(以便我们不能启动另一个线程)。
End Sub
$ b Private Sub CounterThreadMethod()
Dim Time As Integer = 0
While True
Thread.Sleep(1000)'等待约1000毫秒(1秒)。
Time + = 1
Me.InvokeIfRequired(Sub()Label1.Text =线程已经运行:& Time&seconds。)
End而
结束小组
希望这有助于!
I made a file search program in visual studio on windows 10 using .net lang,
My problem starts from form1 with a "dim frm2 as form2 = new form2
" call,
after the new form being shown i start a while loop on form1 that feeds data into a listbox in form 2:
1)form1 call form2 and show it.
2)form1 start a while loop.
3)inside the while loop data being fed to listbox1 in frm2
Now everything works on windows 10, the while loop can run as much as it needs without any trouble, the window can loose focus and regain focus without showing any "Not Responding.." msgs or white\black screens..
But, when i take the software to my friend computer which is running windows 7, install all required frameworks and visual studio itself, run it from the .sln in debug mode, and do the same search on the same folder the results are:
1) the while loop runs smoothly as long as form 2 dont loose focus
(something that doesnt happen on windows 10)
2) when i click anywhere on the screen the software loose focus what
causes 1) to happen (black screen\white screen\not responding etc..)
3) if i wait the time needed for the loop and dont click anywhere else
it keeps running smoohtly, updating a label like it should with the
amount of files found.. and even finish the loop with 100% success
(again unless i click somewhere)
Code Example:
Sub ScanButtonInForm1()
Dim frm2 As Form2 = New Form2
frm2.Show()
Dim AlreadyScanned As HashSet(Of String) = New HashSet(Of String)
Dim stack As New Stack(Of String)
stack.Push("...Directoy To Start The Search From...")
Do While (stack.Count > 0)
frm2.Label4.Text = "-- Mapping Files... -- Folders Left:" + stack.Count.ToString + " -- Files Found:" + frm2.ListBox1.Items.Count.ToString + " --"
frm2.Label4.Refresh()
Dim ScanDir As String = stack.Pop
If AlreadyScanned.Add(ScanDir) Then
Try
Try
Try
Dim directoryName As String
For Each directoryName In System.IO.Directory.GetDirectories(ScanDir)
stack.Push(directoryName)
frm2.Label4.Text = "-- Mapping Files... -- Folders Left:" + stack.Count.ToString + " -- Files Found:" + frm2.ListBox1.Items.Count.ToString + " --"
frm2.Label4.Refresh()
Next
frm2.ListBox1.Items.AddRange(System.IO.Directory.GetFiles(ScanDir, "*.*", System.IO.SearchOption.AllDirectories))
Catch ex5 As UnauthorizedAccessException
End Try
Catch ex2 As System.IO.PathTooLongException
End Try
Catch ex4 As System.IO.DirectoryNotFoundException
End Try
End If
Loop
End Sub
My conclusions was simple!
1) windows 7 dont support live ui (label) update from a while loop
called from a button...
2) windows 7 could possibly support a new
thread running the same loop
i think mabye if i run all the code in a thread mabye the ui will remain responsive
(by the way the UI is not responsive in windows 10 but i still see
the label refresh and nothing crashes when form loose focus..)
so i know how to do that but i also know that if i do that a thread will not be able to update a listbox or a label in a form and refresh it..
so the thread will need to update an external file with the data and the form2 will need to read that data live from the file but will it make the same problems? i have no idea what to do.. can use some help and tips. THANK YOU!
I must menttion the fact that the loop is working on windows 10 without a responsive UI means i cant click on any button but i can
still see the label refresh BUT on windows 7 everything works the same
UNLESS i click somewhere, no matter where i click on windows the loop
crashes
im using framework 4.6.2 developer
解决方案 While I'm glad you found a solution, I advise against using Application.DoEvents()
because it is bad practice.
Please see this blog post: Keeping your UI Responsive and the Dangers of Application.DoEvents.
Simply put, Application.DoEvents()
is a dirty workaround that makes your UI seem responsive because it forces the UI thread to handle all currently available window messages. WM_PAINT
is one of those messages which is why your window redraws.
However this has some backsides to it... For instance:
If you were to close the form during this "background" process it would most likely throw an error.
Another backside is that if the ScanButtonInForm1()
method is called by the click of a button you'd be able to click that button again (unless you set Enabled = False
) and starting the process once more, which brings us to yet another backside:
The more Application.DoEvents()
-loops you start the more you occupy the UI thread, which will cause your CPU usage to rise rather quickly. Since every loop is run in the same thread your processor cannot schedule the work over different cores nor threads, so your code will always run on one core, eating as much CPU as possible.
The replacement is, of course, proper multithreading (or the Task Parallel Library, whichever you prefer). Regular multithreading actually isn't that hard to implement.
The basics
In order to create a new thread you only need to declare an instance of the Thread
class and pass a delegate to the method you want the thread to run:
Dim myThread As New Thread(AddressOf <your method here>)
...then you should set its IsBackground
property to True
if you want it to close automatically when the program closes (otherwise it keeps the program open until the thread finishes).
Then you just call Start()
and you have a running background thread!
Dim myThread As New Thread(AddressOf myThreadMethod)
myThread.IsBackground = True
myThread.Start()
Accessing the UI thread
The tricky part about multithreading is to marshal calls to the UI thread. A background thread generally cannot access elements (controls) on the UI thread because that might cause concurrency issues (two threads accessing the same control at the same time). Therefore you must marshal your calls to the UI by scheduling them for execution on the UI thread itself. That way you will no longer have the risk of concurrency because all UI related code is run on the UI thread.
To marhsal calls to the UI thread you use either of the Control.Invoke()
or Control.BeginInvoke()
methods. BeginInvoke()
is the asynchronous version, which means it doesn't wait for the UI call to complete before it lets the background thread continue with its work.
One should also make sure to check the Control.InvokeRequired
property, which tells you if you already are on the UI thread (in which case invoking is extremely unnecessary) or not.
The basic InvokeRequired/Invoke
pattern looks like this:
'This delegate will be used to tell Control.Invoke() which method we want to invoke on the UI thread.
Private Delegate Sub UpdateTextBoxDelegate(ByVal TargetTextBox As TextBox, ByVal Text As String)
Private Sub myThreadMethod() 'The method that our thread runs.
'Do some background stuff...
If Me.InvokeRequired = True Then '"Me" being the current form.
Me.Invoke(New UpdateTextBoxDelegate(AddressOf UpdateTextBox), TextBox1, "Status update!") 'We are in a background thread, therefore we must invoke.
Else
UpdateTextBox(TextBox1, "Status update!") 'We are on the UI thread, no invoking required.
End If
'Do some more background stuff...
End Sub
'This is the method that Control.Invoke() will execute.
Private Sub UpdateTextBox(ByVal TargetTextBox As TextBox, ByVal Text As String)
TargetTextBox.Text = Text
End Sub
New UpdateTextBoxDelegate(AddressOf UpdateTextBox)
creates a new instance of the UpdateTextBoxDelegate
that points to our UpdateTextBox
method (the method to invoke on the UI).
However as of Visual Basic 2010 (10.0) and above you can use Lambda expressions which makes invoking much easier:
Private Sub myThreadMethod()
'Do some background stuff...
If Me.InvokeRequired = True Then '"Me" being the current form.
Me.Invoke(Sub() TextBox1.Text = "Status update!") 'We are in a background thread, therefore we must invoke.
Else
TextBox1.Text = "Status update!" 'We are on the UI thread, no invoking required.
End If
'Do some more background stuff...
End Sub
Now all you have to do is type Sub()
and then continue typing code like if you were in a regular method:
If Me.InvokeRequired = True Then
Me.Invoke(Sub()
TextBox1.Text = "Status update!"
Me.Text = "Hello world!"
Label1.Location = New Point(128, 32)
ProgressBar1.Value += 1
End Sub)
Else
TextBox1.Text = "Status update!"
Me.Text = "Hello world!"
Label1.Location = New Point(128, 32)
ProgressBar1.Value += 1
End If
And that's how you marshal calls to the UI thread!
Making it simpler
To make it even more simple to invoke to the UI you can create an Extension method that does the invoking and InvokeRequired
check for you.
Place this in a separate code file:
Imports System.Runtime.CompilerServices
Public Module Extensions
''' <summary>
''' Invokes the specified method on the calling control's thread (if necessary, otherwise on the current thread).
''' </summary>
''' <param name="Control">The control which's thread to invoke the method at.</param>
''' <param name="Method">The method to invoke.</param>
''' <param name="Parameters">The parameters to pass to the method (optional).</param>
''' <remarks></remarks>
<Extension()> _
Public Function InvokeIfRequired(ByVal Control As Control, ByVal Method As [Delegate], ByVal ParamArray Parameters As Object()) As Object
If Parameters IsNot Nothing AndAlso _
Parameters.Length = 0 Then Parameters = Nothing
If Control.InvokeRequired = True Then
Return Control.Invoke(Method, Parameters)
Else
Return Method.DynamicInvoke(Parameters)
End If
End Function
End Module
Now you only need to call this single method when you want to access the UI, no additional If-Then-Else
required:
Private Sub myThreadMethod()
'Do some background stuff...
Me.InvokeIfRequired(Sub()
TextBox1.Text = "Status update!"
Me.Text = "Hello world!"
Label1.Location = New Point(128, 32)
End Sub)
'Do some more background stuff...
End Sub
Returning objects/data from the UI with InvokeIfRequired()
With my InvokeIfRequired()
extension method you can also return objects or data from the UI thread in a simple manner. For instance if you want the width of a label:
Dim LabelWidth As Integer = Me.InvokeIfRequired(Function() Label1.Width)
Example
The following code will increment a counter that tells you for how long the thread has run:
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim CounterThread As New Thread(AddressOf CounterThreadMethod)
CounterThread.IsBackground = True
CounterThread.Start()
Button1.Enabled = False 'Make the button unclickable (so that we cannot start yet another thread).
End Sub
Private Sub CounterThreadMethod()
Dim Time As Integer = 0
While True
Thread.Sleep(1000) 'Wait for approximately 1000 ms (1 second).
Time += 1
Me.InvokeIfRequired(Sub() Label1.Text = "Thread has been running for: " & Time & " seconds.")
End While
End Sub
Hope this helps!
这篇关于.net而崩溃时,松散的焦点WIN7的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
使用我的 InvokeIfRequired()
扩展方法,您也可以从UI中返回对象或数据线程以简单的方式。例如,如果你想要一个标签的宽度:
Dim LabelWidth As Integer = Me.InvokeIfRequired(Function()Label1.Width )
下面的代码会增加一个计数器,告诉你线程已经运行了多久:
Private Sub Button1_Click(sender As System.Object,e As System.EventArgs)Handles Button1.Click
Dim CounterThread As New Thread(AddressOf CounterThreadMethod)
CounterThread.IsBackground = True
CounterThread.Start()
Button1.Enabled = False'使按钮不可点击(以便我们不能启动另一个线程)。
End Sub
$ b Private Sub CounterThreadMethod()
Dim Time As Integer = 0
While True
Thread.Sleep(1000)'等待约1000毫秒(1秒)。
Time + = 1
Me.InvokeIfRequired(Sub()Label1.Text =线程已经运行:& Time&seconds。)
End而
结束小组
希望这有助于!
I made a file search program in visual studio on windows 10 using .net lang,
My problem starts from form1 with a "dim frm2 as form2 = new form2
" call,
after the new form being shown i start a while loop on form1 that feeds data into a listbox in form 2:
1)form1 call form2 and show it.
2)form1 start a while loop.
3)inside the while loop data being fed to listbox1 in frm2
Now everything works on windows 10, the while loop can run as much as it needs without any trouble, the window can loose focus and regain focus without showing any "Not Responding.." msgs or white\black screens..
But, when i take the software to my friend computer which is running windows 7, install all required frameworks and visual studio itself, run it from the .sln in debug mode, and do the same search on the same folder the results are:
1) the while loop runs smoothly as long as form 2 dont loose focus (something that doesnt happen on windows 10)
2) when i click anywhere on the screen the software loose focus what causes 1) to happen (black screen\white screen\not responding etc..)
3) if i wait the time needed for the loop and dont click anywhere else it keeps running smoohtly, updating a label like it should with the amount of files found.. and even finish the loop with 100% success (again unless i click somewhere)
Code Example:
Sub ScanButtonInForm1()
Dim frm2 As Form2 = New Form2
frm2.Show()
Dim AlreadyScanned As HashSet(Of String) = New HashSet(Of String)
Dim stack As New Stack(Of String)
stack.Push("...Directoy To Start The Search From...")
Do While (stack.Count > 0)
frm2.Label4.Text = "-- Mapping Files... -- Folders Left:" + stack.Count.ToString + " -- Files Found:" + frm2.ListBox1.Items.Count.ToString + " --"
frm2.Label4.Refresh()
Dim ScanDir As String = stack.Pop
If AlreadyScanned.Add(ScanDir) Then
Try
Try
Try
Dim directoryName As String
For Each directoryName In System.IO.Directory.GetDirectories(ScanDir)
stack.Push(directoryName)
frm2.Label4.Text = "-- Mapping Files... -- Folders Left:" + stack.Count.ToString + " -- Files Found:" + frm2.ListBox1.Items.Count.ToString + " --"
frm2.Label4.Refresh()
Next
frm2.ListBox1.Items.AddRange(System.IO.Directory.GetFiles(ScanDir, "*.*", System.IO.SearchOption.AllDirectories))
Catch ex5 As UnauthorizedAccessException
End Try
Catch ex2 As System.IO.PathTooLongException
End Try
Catch ex4 As System.IO.DirectoryNotFoundException
End Try
End If
Loop
End Sub
My conclusions was simple!
1) windows 7 dont support live ui (label) update from a while loop called from a button...
2) windows 7 could possibly support a new thread running the same loop
i think mabye if i run all the code in a thread mabye the ui will remain responsive
(by the way the UI is not responsive in windows 10 but i still see the label refresh and nothing crashes when form loose focus..)
so i know how to do that but i also know that if i do that a thread will not be able to update a listbox or a label in a form and refresh it..
so the thread will need to update an external file with the data and the form2 will need to read that data live from the file but will it make the same problems? i have no idea what to do.. can use some help and tips. THANK YOU!
I must menttion the fact that the loop is working on windows 10 without a responsive UI means i cant click on any button but i can still see the label refresh BUT on windows 7 everything works the same UNLESS i click somewhere, no matter where i click on windows the loop crashes
im using framework 4.6.2 developer
While I'm glad you found a solution, I advise against using Application.DoEvents()
because it is bad practice.
Please see this blog post: Keeping your UI Responsive and the Dangers of Application.DoEvents.
Simply put, Application.DoEvents()
is a dirty workaround that makes your UI seem responsive because it forces the UI thread to handle all currently available window messages. WM_PAINT
is one of those messages which is why your window redraws.
However this has some backsides to it... For instance:
If you were to close the form during this "background" process it would most likely throw an error.
Another backside is that if the
ScanButtonInForm1()
method is called by the click of a button you'd be able to click that button again (unless you setEnabled = False
) and starting the process once more, which brings us to yet another backside:The more
Application.DoEvents()
-loops you start the more you occupy the UI thread, which will cause your CPU usage to rise rather quickly. Since every loop is run in the same thread your processor cannot schedule the work over different cores nor threads, so your code will always run on one core, eating as much CPU as possible.
The replacement is, of course, proper multithreading (or the Task Parallel Library, whichever you prefer). Regular multithreading actually isn't that hard to implement.
The basics
In order to create a new thread you only need to declare an instance of the Thread
class and pass a delegate to the method you want the thread to run:
Dim myThread As New Thread(AddressOf <your method here>)
...then you should set its IsBackground
property to True
if you want it to close automatically when the program closes (otherwise it keeps the program open until the thread finishes).
Then you just call Start()
and you have a running background thread!
Dim myThread As New Thread(AddressOf myThreadMethod)
myThread.IsBackground = True
myThread.Start()
Accessing the UI thread
The tricky part about multithreading is to marshal calls to the UI thread. A background thread generally cannot access elements (controls) on the UI thread because that might cause concurrency issues (two threads accessing the same control at the same time). Therefore you must marshal your calls to the UI by scheduling them for execution on the UI thread itself. That way you will no longer have the risk of concurrency because all UI related code is run on the UI thread.
To marhsal calls to the UI thread you use either of the Control.Invoke()
or Control.BeginInvoke()
methods. BeginInvoke()
is the asynchronous version, which means it doesn't wait for the UI call to complete before it lets the background thread continue with its work.
One should also make sure to check the Control.InvokeRequired
property, which tells you if you already are on the UI thread (in which case invoking is extremely unnecessary) or not.
The basic InvokeRequired/Invoke
pattern looks like this:
'This delegate will be used to tell Control.Invoke() which method we want to invoke on the UI thread.
Private Delegate Sub UpdateTextBoxDelegate(ByVal TargetTextBox As TextBox, ByVal Text As String)
Private Sub myThreadMethod() 'The method that our thread runs.
'Do some background stuff...
If Me.InvokeRequired = True Then '"Me" being the current form.
Me.Invoke(New UpdateTextBoxDelegate(AddressOf UpdateTextBox), TextBox1, "Status update!") 'We are in a background thread, therefore we must invoke.
Else
UpdateTextBox(TextBox1, "Status update!") 'We are on the UI thread, no invoking required.
End If
'Do some more background stuff...
End Sub
'This is the method that Control.Invoke() will execute.
Private Sub UpdateTextBox(ByVal TargetTextBox As TextBox, ByVal Text As String)
TargetTextBox.Text = Text
End Sub
New UpdateTextBoxDelegate(AddressOf UpdateTextBox)
creates a new instance of the UpdateTextBoxDelegate
that points to our UpdateTextBox
method (the method to invoke on the UI).
However as of Visual Basic 2010 (10.0) and above you can use Lambda expressions which makes invoking much easier:
Private Sub myThreadMethod()
'Do some background stuff...
If Me.InvokeRequired = True Then '"Me" being the current form.
Me.Invoke(Sub() TextBox1.Text = "Status update!") 'We are in a background thread, therefore we must invoke.
Else
TextBox1.Text = "Status update!" 'We are on the UI thread, no invoking required.
End If
'Do some more background stuff...
End Sub
Now all you have to do is type Sub()
and then continue typing code like if you were in a regular method:
If Me.InvokeRequired = True Then
Me.Invoke(Sub()
TextBox1.Text = "Status update!"
Me.Text = "Hello world!"
Label1.Location = New Point(128, 32)
ProgressBar1.Value += 1
End Sub)
Else
TextBox1.Text = "Status update!"
Me.Text = "Hello world!"
Label1.Location = New Point(128, 32)
ProgressBar1.Value += 1
End If
And that's how you marshal calls to the UI thread!
Making it simpler
To make it even more simple to invoke to the UI you can create an Extension method that does the invoking and InvokeRequired
check for you.
Place this in a separate code file:
Imports System.Runtime.CompilerServices
Public Module Extensions
''' <summary>
''' Invokes the specified method on the calling control's thread (if necessary, otherwise on the current thread).
''' </summary>
''' <param name="Control">The control which's thread to invoke the method at.</param>
''' <param name="Method">The method to invoke.</param>
''' <param name="Parameters">The parameters to pass to the method (optional).</param>
''' <remarks></remarks>
<Extension()> _
Public Function InvokeIfRequired(ByVal Control As Control, ByVal Method As [Delegate], ByVal ParamArray Parameters As Object()) As Object
If Parameters IsNot Nothing AndAlso _
Parameters.Length = 0 Then Parameters = Nothing
If Control.InvokeRequired = True Then
Return Control.Invoke(Method, Parameters)
Else
Return Method.DynamicInvoke(Parameters)
End If
End Function
End Module
Now you only need to call this single method when you want to access the UI, no additional If-Then-Else
required:
Private Sub myThreadMethod()
'Do some background stuff...
Me.InvokeIfRequired(Sub()
TextBox1.Text = "Status update!"
Me.Text = "Hello world!"
Label1.Location = New Point(128, 32)
End Sub)
'Do some more background stuff...
End Sub
Returning objects/data from the UI with InvokeIfRequired()
With my InvokeIfRequired()
extension method you can also return objects or data from the UI thread in a simple manner. For instance if you want the width of a label:
Dim LabelWidth As Integer = Me.InvokeIfRequired(Function() Label1.Width)
Example
The following code will increment a counter that tells you for how long the thread has run:
Private Sub Button1_Click(sender As System.Object, e As System.EventArgs) Handles Button1.Click
Dim CounterThread As New Thread(AddressOf CounterThreadMethod)
CounterThread.IsBackground = True
CounterThread.Start()
Button1.Enabled = False 'Make the button unclickable (so that we cannot start yet another thread).
End Sub
Private Sub CounterThreadMethod()
Dim Time As Integer = 0
While True
Thread.Sleep(1000) 'Wait for approximately 1000 ms (1 second).
Time += 1
Me.InvokeIfRequired(Sub() Label1.Text = "Thread has been running for: " & Time & " seconds.")
End While
End Sub
Hope this helps!
这篇关于.net而崩溃时,松散的焦点WIN7的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!