orr*_*gel 1 vb.net crash multithreading focus windows-7
我使用.net lang 在Windows 10上的visual studio中创建了一个文件搜索程序,我的问题从带有" dim frm2 as form2 = new form2"调用的form1开始,在显示新表单后我在form1上启动了一个while循环,将数据提供到表单2中的列表框中:
1)form1调用form2并显示它.
2)form1启动一个while循环.
3)在frm2中将while循环数据输入到listbox1
现在一切都在Windows 10上运行,while循环可以运行尽可能多的任何麻烦,窗口可以松散焦点并重新获得焦点而不显示任何"Not Responding.." msgs or white\black screens..
但是,当我将软件带到运行Windows 7的朋友计算机时,安装所有必需的框架和visual studio本身,在调试模式下从.sln运行它,并在相同的文件夹上执行相同的搜索结果:
1) while循环运行平稳,只要表格2不松动焦点(在Windows 10上不会发生的事情)
2)当我点击屏幕上的任何地方时软件松散焦点导致1)发生(黑屏\白屏\无响应等)
3)如果我等待循环所需的时间并且不点击任何其他地方它仍然保持运行,更新像它的标签应该与找到的文件的数量..甚至完成循环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 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
Run Code Online (Sandbox Code Playgroud)
我的结论很简单!
1)windows 7不支持从按钮调用的while循环更新live ui(标签)...
2)windows 7可能支持运行相同循环的新线程
我认为mabye如果我在线程mabye中运行所有代码,ui将保持响应
(顺便说一句,UI在Windows 10中没有响应,但我仍然看到标签刷新,并且在形成松散焦点时没有任何崩溃 ..)
所以我知道该怎么做但我也知道如果我这样做一个线程将无法更新表单中的列表框或标签并刷新它..
所以线程需要用数据更新外部文件,而form2需要从文件中实时读取数据,但它会产生同样的问题吗?我不知道该怎么做..可以使用一些帮助和提示.谢谢!
我必须指出这样一个事实,即循环正在Windows 10上工作而没有响应的UI意味着我无法点击任何按钮但我仍然可以看到标签刷新但是在Windows 7上一切都工作相同除非我点击某处,无论我在哪里点击在Windows上循环崩溃
我正在使用框架4.6.2开发人员
虽然我很高兴你找到了解决方案,但我建议不要使用,Application.DoEvents()因为这是不好的做法.
请参阅此博客文章:保持您的UI响应和Application.DoEvents的危险.
简单地说,Application.DoEvents()是一个脏的解决方法,使您的UI看起来响应,因为它强制UI线程处理所有当前可用的窗口消息.WM_PAINT是这些消息之一,这就是你的窗口重绘的原因.
然而,这有一些后盾...例如:
如果您在此"后台"过程中关闭表单,则很可能会出错.
另一个缺点是,如果ScanButtonInForm1()通过单击按钮调用该方法,您可以再次单击该按钮(除非您设置Enabled = False)并再次启动该过程,这将我们带到另一个背后:
越多Application.DoEvents()你开始-loops越多,你占据了UI线程,这将导致你的CPU使用率,而迅速上升.由于每个循环都在同一个线程中运行,因此您的处理器无法在不同的内核和线程上调度工作,因此您的代码将始终在一个内核上运行,尽可能多地占用CPU.
当然,替换是适当的多线程(或任务并行库,无论你喜欢哪个).实际的多线程实际上并不难实现.
为了创建一个新线程,您只需要声明一个Thread类的实例,并将一个委托传递给您希望该线程运行的方法:
Dim myThread As New Thread(AddressOf <your method here>)
Run Code Online (Sandbox Code Playgroud)
...然后你应该将它的IsBackground属性设置为True如果你希望它在程序关闭时自动关闭(否则它会保持程序打开直到线程结束).
然后你打电话Start(),你有一个正在运行的后台线程!
Dim myThread As New Thread(AddressOf myThreadMethod)
myThread.IsBackground = True
myThread.Start()
Run Code Online (Sandbox Code Playgroud)
关于多线程的棘手部分是编组对UI线程的调用.后台线程通常无法访问UI线程上的元素(控件),因为这可能会导致并发问题(两个线程同时访问同一个控件).因此,您必须通过调度它们在UI线程本身上执行来封送对UI的调用.这样您将不再有并发风险,因为所有UI相关代码都在UI线程上运行.
要对UI线程进行marhsal调用,请使用其中一个Control.Invoke()或多个Control.BeginInvoke()方法.BeginInvoke()是异步版本,这意味着它不会等待UI调用完成后才能让后台线程继续其工作.
还应该确保检查Control.InvokeRequired属性,该属性告诉您是否已经在UI线程上(在这种情况下,调用是非常不必要的)或不是.
基本InvokeRequired/Invoke模式看起来像这样(主要用于参考,请继续阅读以下更短的方法):
'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
Run Code Online (Sandbox Code Playgroud)
New UpdateTextBoxDelegate(AddressOf UpdateTextBox)创建一个UpdateTextBoxDelegate指向我们的UpdateTextBox方法的新实例(在UI上调用的方法).
但是,从Visual Basic 2010(10.0)及更高版本开始,您可以使用Lambda表达式,这使得调用更加容易:
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
Run Code Online (Sandbox Code Playgroud)
现在你所要做的就是键入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)
Else
TextBox1.Text = "Status update!"
Me.Text = "Hello world!"
Label1.Location = New Point(128, 32)
ProgressBar1.Value += 1
End If
Run Code Online (Sandbox Code Playgroud)
这就是你如何编组UI线程的调用!
为了使它甚至更简单的调用你可以创建一个用户界面扩展方法,做调用和InvokeRequired检查你.
将其放在单独的代码文件中:
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
Run Code Online (Sandbox Code Playgroud)
现在,您只需要在要访问UI时调用此单个方法,无需额外If-Then-Else要求:
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
Run Code Online (Sandbox Code Playgroud)
InvokeIfRequired()使用我的InvokeIfRequired()扩展方法,您还可以以简单的方式从UI线程返回对象或数据.例如,如果您想要标签的宽度:
Dim LabelWidth As Integer = Me.InvokeIfRequired(Function() Label1.Width)
Run Code Online (Sandbox Code Playgroud)
以下代码将递增一个计数器,告诉您线程运行了多长时间:
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
Run Code Online (Sandbox Code Playgroud)
希望这可以帮助!
| 归档时间: |
|
| 查看次数: |
1674 次 |
| 最近记录: |