在 BackgroundWorker 中使用 Await 会引发 WorkerComplete 事件
Using Await in BackgroundWorker causes WorkerComplete event to be raised
我有这个奇怪的问题。我需要从后台工作人员中调用一个进程
Private Shared _process As Process
Private Shared _StartInfo As ProcessStartInfo
Private WithEvents _bwConvertMedia As New BackgroundWorker
这是 DoWorkAsync 中的工作
Private Async Sub _bwConvertMedia_DoWorkAsync(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles _bwConvertMedia.DoWork
For AI = 1 To 100
_StartInfo = New ProcessStartInfo(".\mycmd.exe", "-1")
_StartInfo.RedirectStandardOutput = True
_StartInfo.UseShellExecute = False
_StartInfo.CreateNoWindow = True
_StartInfo.RedirectStandardError = True
_process = New Process() With {.EnableRaisingEvents = True, .StartInfo = _StartInfo}
AddHandler _process.OutputDataReceived, AddressOf OutputHandler
AddHandler _process.ErrorDataReceived, AddressOf ErrorHandler
AddHandler _process.Exited, AddressOf Exited
Try
aSuccess = Await AwaitProcess()
Catch ex As Exception
End Try
_bwConvertMedia.ReportProgress(ai)
Next
这里是
Private Shared Async Function AwaitProcess() As Task(Of Integer)
_tcs = New TaskCompletionSource(Of Integer)
_status.Converting = True
_Error.Clear()
_process.Start()
_process.BeginErrorReadLine()
_process.BeginOutputReadLine()
Return Await _tcs.Task
End Function
问题是,当执行 Await _tcs.Task 时,会执行 _bwConvertMedia RunWorkerCompleted 过程,所以当我调用 _bwConvertMedia.ReportProgress(ai)
我收到一个错误消息,工作人员已经完成。
这是为什么?你能帮帮我吗?
发生的事情是
- DoWork - 迭代 1
- 在等待进程 1
- RunWorkerComplete
- DoWork 迭代 2-100
正确的行为是后台工作程序调用 100 次进程,然后完成执行并调用 RunWorkerCompleted
我对之前链接的代码进行了一些修改,这里有两个示例,分别是顺序非阻塞 Async/Await 过程和使用 Task.Factory
.[=58= 的非阻塞并行过程]
Since I can't test your program, I simply used Tracert.exe
to
simulate a stdout result to update the User Interface.
为了将 运行 tasks/threads 与 UI 同步,我在第一种情况下使用进程的 .SynchronizingObject
,在第二种情况下使用 TaskScheduler 方法 TaskScheduler.FromCurrentSynchronizationContext()
.
Tracert.exe
的输出被传递到两个文本框。
在并行示例中,我在任务之间插入了 1 秒的延迟,以查看两个文本框是如何更新的。
Async/Await 示例可以修改为不同的工作方式,因为您不需要等待一个任务完成才能开始另一个任务。
使用 List(Of ProcessStartInfo)
和 List(Of Process)
将 ProcessStartInfo
和 Process
对象添加到 Pool。
These are used in both examples. Define a correct scope.
Public psInfoPool As List(Of ProcessStartInfo)
Public ProcessPool As List(Of Process)
顺序Async/Await
The delegate is used with SynchronizingObject.BeginInvoke if
InvokeRequired = true
Public Delegate Sub UpdUI(_object As TextBox, _value As String)
Public Sub UpdateUIDelegate(control As TextBox, _input As String)
control.AppendText(_input)
End Sub
Dim NumberOfProcesses As Integer
For x = 0 To 1
Dim OutCtl As TextBox = If(x = 0, Me.TextBox1, Me.TextBox2)
Dim _result As Integer = Await Task.Run(Async Function() As Task(Of Integer)
Return Await Test_SequentialAsync("192.168.1.1", OutCtl)
End Function)
NumberOfProcesses += _result
Next
The MediaToConvert
parameter would be the name of the file to
convert when you adapt the examples to your needs. The OutCtl
parameter is just the TextBox used for the output
Public Async Function Test_SequentialAsync(ByVal MediaToConvert As String, OutCtl As TextBox) As Task(Of Integer)
Dim _CurrentProcessInfo As Integer
Dim _CurrentProcess As Integer
Dim ExitCode As Integer = Await Task.Run(Function() As Integer
Dim _processexitcode As Integer
psInfoPool.Add(New ProcessStartInfo)
_CurrentProcessInfo = psInfoPool.Count - 1
psInfoPool(_CurrentProcessInfo).RedirectStandardOutput = True
psInfoPool(_CurrentProcessInfo).CreateNoWindow = True
psInfoPool(_CurrentProcessInfo).UseShellExecute = False
'Name of the executable to start
psInfoPool(_CurrentProcessInfo).FileName = "Tracert" 'psInfo.FileName = ".\mycmd.exe"""
'Parameter(s) to pass to the executable
psInfoPool(_CurrentProcessInfo).Arguments = MediaToConvert
psInfoPool(_CurrentProcessInfo).WindowStyle = ProcessWindowStyle.Hidden
ProcessPool.Add(New Process)
_CurrentProcess = ProcessPool.Count - 1
ProcessPool(_CurrentProcess) = New Process() With {.StartInfo = psInfoPool(_CurrentProcessInfo),
.EnableRaisingEvents = True,
.SynchronizingObject = Me}
ProcessPool(_CurrentProcess).Start()
ProcessPool(_CurrentProcess).BeginOutputReadLine()
AddHandler ProcessPool(_CurrentProcess).OutputDataReceived,
Sub(sender As Object, e As DataReceivedEventArgs)
If e.Data IsNot Nothing Then
If ProcessPool(_CurrentProcess).SynchronizingObject.InvokeRequired Then
ProcessPool(_CurrentProcess).SynchronizingObject.BeginInvoke(
New UpdUI(AddressOf UpdateUIDelegate),
New Object() {OutCtl,
e.Data + Environment.NewLine})
Else
OutCtl.AppendText(e.Data + Environment.NewLine)
End If
End If
End Sub
'Add an event handler for the Exited event
AddHandler ProcessPool(_CurrentProcess).Exited,
Sub(source As Object, ev As EventArgs)
_processexitcode = ProcessPool(_CurrentProcess).ExitCode
Console.WriteLine("The process has exited. Code: {0} Time: {1}",
_processexitcode,
ProcessPool(_CurrentProcess).ExitTime)
End Sub
ProcessPool(_CurrentProcess).WaitForExit()
ProcessPool(_CurrentProcess).Close()
Return _processexitcode
End Function)
Return If(ExitCode = 0, 1, 0)
End Function
使用Task.Fatory
的并行进程
定义一个调度程序并将其与当前上下文相关联
Public _Scheduler As TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
To use Await Task.Delay(1000)
you must be in an Async method, but
it's just for testing the output, it's not needed.
For x = 0 To 1
Dim OutCtl As TextBox = If(x = 0, Me.TextBox1, Me.TextBox2)
Dim _result As Integer = Test_ParallelTasks("192.168.1.1", OutCtl)
Await Task.Delay(1000)
NumberOfProcesses += _result
Next
Note that a new Task is created when the OutputDataReceived
event
handler reports that new data has been received. The UI is updated
accordingly using DataReceivedEventArgs
e.Data.
Private Function Test_ParallelTasks(ByVal MediaToConvert As String, OutCtl As TextBox) As Integer
Dim _processexitcode As Integer
Dim _CurrentProcessInfo As Integer
Dim _CurrentProcess As Integer
Task.Factory.StartNew(Function()
psInfoPool.Add(New ProcessStartInfo)
_CurrentProcessInfo = psInfoPool.Count - 1
psInfoPool(_CurrentProcessInfo).RedirectStandardOutput = True
psInfoPool(_CurrentProcessInfo).CreateNoWindow = True
psInfoPool(_CurrentProcessInfo).UseShellExecute = False
psInfoPool(_CurrentProcessInfo).FileName = "Tracert" 'psInfo.FileName = ".\mycmd.exe"
psInfoPool(_CurrentProcessInfo).Arguments = MediaToConvert
psInfoPool(_CurrentProcessInfo).WindowStyle = ProcessWindowStyle.Hidden
ProcessPool.Add(New Process)
_CurrentProcess = ProcessPool.Count - 1
ProcessPool(_CurrentProcess) = New Process() With {.StartInfo = psInfoPool(_CurrentProcessInfo),
.EnableRaisingEvents = True,
.SynchronizingObject = Me}
ProcessPool(_CurrentProcess).Start()
ProcessPool(_CurrentProcess).BeginOutputReadLine()
AddHandler ProcessPool(_CurrentProcess).OutputDataReceived,
Sub(sender As Object, e As DataReceivedEventArgs)
If e.Data IsNot Nothing Then
Try
'Update the UI or report progress
Dim UpdateUI As Task = Task.Factory.StartNew(Sub()
Try
OutCtl.AppendText(e.Data + Environment.NewLine)
Catch exp As Exception
'An exception may raise if the form is closed
End Try
End Sub, CancellationToken.None, TaskCreationOptions.PreferFairness, _Scheduler)
UpdateUI.Wait()
Catch exp As Exception
'Do something here
End Try
End If
End Sub
'Add an event handler for the Exited event
AddHandler ProcessPool(_CurrentProcess).Exited,
Sub(source As Object, ev As EventArgs)
_processexitcode = ProcessPool(_CurrentProcess).ExitCode
Console.WriteLine("The process has exited. Code: {0} Time: {1}",
_processexitcode,
ProcessPool(_CurrentProcess).ExitTime)
End Sub
ProcessPool(_CurrentProcess).WaitForExit()
ProcessPool(_CurrentProcess).Close()
Return _processexitcode
End Function, TaskCreationOptions.LongRunning, CancellationToken.None)
Return If(_processexitcode = 0, 1, 0)
End Function
我有这个奇怪的问题。我需要从后台工作人员中调用一个进程
Private Shared _process As Process
Private Shared _StartInfo As ProcessStartInfo
Private WithEvents _bwConvertMedia As New BackgroundWorker
这是 DoWorkAsync 中的工作
Private Async Sub _bwConvertMedia_DoWorkAsync(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles _bwConvertMedia.DoWork
For AI = 1 To 100
_StartInfo = New ProcessStartInfo(".\mycmd.exe", "-1")
_StartInfo.RedirectStandardOutput = True
_StartInfo.UseShellExecute = False
_StartInfo.CreateNoWindow = True
_StartInfo.RedirectStandardError = True
_process = New Process() With {.EnableRaisingEvents = True, .StartInfo = _StartInfo}
AddHandler _process.OutputDataReceived, AddressOf OutputHandler
AddHandler _process.ErrorDataReceived, AddressOf ErrorHandler
AddHandler _process.Exited, AddressOf Exited
Try
aSuccess = Await AwaitProcess()
Catch ex As Exception
End Try
_bwConvertMedia.ReportProgress(ai)
Next
这里是
Private Shared Async Function AwaitProcess() As Task(Of Integer)
_tcs = New TaskCompletionSource(Of Integer)
_status.Converting = True
_Error.Clear()
_process.Start()
_process.BeginErrorReadLine()
_process.BeginOutputReadLine()
Return Await _tcs.Task
End Function
问题是,当执行 Await _tcs.Task 时,会执行 _bwConvertMedia RunWorkerCompleted 过程,所以当我调用 _bwConvertMedia.ReportProgress(ai)
我收到一个错误消息,工作人员已经完成。
这是为什么?你能帮帮我吗?
发生的事情是
- DoWork - 迭代 1
- 在等待进程 1
- RunWorkerComplete
- DoWork 迭代 2-100
正确的行为是后台工作程序调用 100 次进程,然后完成执行并调用 RunWorkerCompleted
我对之前链接的代码进行了一些修改,这里有两个示例,分别是顺序非阻塞 Async/Await 过程和使用 Task.Factory
.[=58= 的非阻塞并行过程]
Since I can't test your program, I simply used
Tracert.exe
to simulate a stdout result to update the User Interface.
为了将 运行 tasks/threads 与 UI 同步,我在第一种情况下使用进程的 .SynchronizingObject
,在第二种情况下使用 TaskScheduler 方法 TaskScheduler.FromCurrentSynchronizationContext()
.
Tracert.exe
的输出被传递到两个文本框。
在并行示例中,我在任务之间插入了 1 秒的延迟,以查看两个文本框是如何更新的。
Async/Await 示例可以修改为不同的工作方式,因为您不需要等待一个任务完成才能开始另一个任务。
使用 List(Of ProcessStartInfo)
和 List(Of Process)
将 ProcessStartInfo
和 Process
对象添加到 Pool。
These are used in both examples. Define a correct scope.
Public psInfoPool As List(Of ProcessStartInfo)
Public ProcessPool As List(Of Process)
顺序Async/Await
The delegate is used with SynchronizingObject.BeginInvoke if InvokeRequired = true
Public Delegate Sub UpdUI(_object As TextBox, _value As String)
Public Sub UpdateUIDelegate(control As TextBox, _input As String)
control.AppendText(_input)
End Sub
Dim NumberOfProcesses As Integer
For x = 0 To 1
Dim OutCtl As TextBox = If(x = 0, Me.TextBox1, Me.TextBox2)
Dim _result As Integer = Await Task.Run(Async Function() As Task(Of Integer)
Return Await Test_SequentialAsync("192.168.1.1", OutCtl)
End Function)
NumberOfProcesses += _result
Next
The
MediaToConvert
parameter would be the name of the file to convert when you adapt the examples to your needs. TheOutCtl
parameter is just the TextBox used for the output
Public Async Function Test_SequentialAsync(ByVal MediaToConvert As String, OutCtl As TextBox) As Task(Of Integer)
Dim _CurrentProcessInfo As Integer
Dim _CurrentProcess As Integer
Dim ExitCode As Integer = Await Task.Run(Function() As Integer
Dim _processexitcode As Integer
psInfoPool.Add(New ProcessStartInfo)
_CurrentProcessInfo = psInfoPool.Count - 1
psInfoPool(_CurrentProcessInfo).RedirectStandardOutput = True
psInfoPool(_CurrentProcessInfo).CreateNoWindow = True
psInfoPool(_CurrentProcessInfo).UseShellExecute = False
'Name of the executable to start
psInfoPool(_CurrentProcessInfo).FileName = "Tracert" 'psInfo.FileName = ".\mycmd.exe"""
'Parameter(s) to pass to the executable
psInfoPool(_CurrentProcessInfo).Arguments = MediaToConvert
psInfoPool(_CurrentProcessInfo).WindowStyle = ProcessWindowStyle.Hidden
ProcessPool.Add(New Process)
_CurrentProcess = ProcessPool.Count - 1
ProcessPool(_CurrentProcess) = New Process() With {.StartInfo = psInfoPool(_CurrentProcessInfo),
.EnableRaisingEvents = True,
.SynchronizingObject = Me}
ProcessPool(_CurrentProcess).Start()
ProcessPool(_CurrentProcess).BeginOutputReadLine()
AddHandler ProcessPool(_CurrentProcess).OutputDataReceived,
Sub(sender As Object, e As DataReceivedEventArgs)
If e.Data IsNot Nothing Then
If ProcessPool(_CurrentProcess).SynchronizingObject.InvokeRequired Then
ProcessPool(_CurrentProcess).SynchronizingObject.BeginInvoke(
New UpdUI(AddressOf UpdateUIDelegate),
New Object() {OutCtl,
e.Data + Environment.NewLine})
Else
OutCtl.AppendText(e.Data + Environment.NewLine)
End If
End If
End Sub
'Add an event handler for the Exited event
AddHandler ProcessPool(_CurrentProcess).Exited,
Sub(source As Object, ev As EventArgs)
_processexitcode = ProcessPool(_CurrentProcess).ExitCode
Console.WriteLine("The process has exited. Code: {0} Time: {1}",
_processexitcode,
ProcessPool(_CurrentProcess).ExitTime)
End Sub
ProcessPool(_CurrentProcess).WaitForExit()
ProcessPool(_CurrentProcess).Close()
Return _processexitcode
End Function)
Return If(ExitCode = 0, 1, 0)
End Function
使用Task.Fatory
的并行进程定义一个调度程序并将其与当前上下文相关联
Public _Scheduler As TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
To use
Await Task.Delay(1000)
you must be in an Async method, but it's just for testing the output, it's not needed.
For x = 0 To 1
Dim OutCtl As TextBox = If(x = 0, Me.TextBox1, Me.TextBox2)
Dim _result As Integer = Test_ParallelTasks("192.168.1.1", OutCtl)
Await Task.Delay(1000)
NumberOfProcesses += _result
Next
Note that a new Task is created when the
OutputDataReceived
event handler reports that new data has been received. The UI is updated accordingly usingDataReceivedEventArgs
e.Data.
Private Function Test_ParallelTasks(ByVal MediaToConvert As String, OutCtl As TextBox) As Integer
Dim _processexitcode As Integer
Dim _CurrentProcessInfo As Integer
Dim _CurrentProcess As Integer
Task.Factory.StartNew(Function()
psInfoPool.Add(New ProcessStartInfo)
_CurrentProcessInfo = psInfoPool.Count - 1
psInfoPool(_CurrentProcessInfo).RedirectStandardOutput = True
psInfoPool(_CurrentProcessInfo).CreateNoWindow = True
psInfoPool(_CurrentProcessInfo).UseShellExecute = False
psInfoPool(_CurrentProcessInfo).FileName = "Tracert" 'psInfo.FileName = ".\mycmd.exe"
psInfoPool(_CurrentProcessInfo).Arguments = MediaToConvert
psInfoPool(_CurrentProcessInfo).WindowStyle = ProcessWindowStyle.Hidden
ProcessPool.Add(New Process)
_CurrentProcess = ProcessPool.Count - 1
ProcessPool(_CurrentProcess) = New Process() With {.StartInfo = psInfoPool(_CurrentProcessInfo),
.EnableRaisingEvents = True,
.SynchronizingObject = Me}
ProcessPool(_CurrentProcess).Start()
ProcessPool(_CurrentProcess).BeginOutputReadLine()
AddHandler ProcessPool(_CurrentProcess).OutputDataReceived,
Sub(sender As Object, e As DataReceivedEventArgs)
If e.Data IsNot Nothing Then
Try
'Update the UI or report progress
Dim UpdateUI As Task = Task.Factory.StartNew(Sub()
Try
OutCtl.AppendText(e.Data + Environment.NewLine)
Catch exp As Exception
'An exception may raise if the form is closed
End Try
End Sub, CancellationToken.None, TaskCreationOptions.PreferFairness, _Scheduler)
UpdateUI.Wait()
Catch exp As Exception
'Do something here
End Try
End If
End Sub
'Add an event handler for the Exited event
AddHandler ProcessPool(_CurrentProcess).Exited,
Sub(source As Object, ev As EventArgs)
_processexitcode = ProcessPool(_CurrentProcess).ExitCode
Console.WriteLine("The process has exited. Code: {0} Time: {1}",
_processexitcode,
ProcessPool(_CurrentProcess).ExitTime)
End Sub
ProcessPool(_CurrentProcess).WaitForExit()
ProcessPool(_CurrentProcess).Close()
Return _processexitcode
End Function, TaskCreationOptions.LongRunning, CancellationToken.None)
Return If(_processexitcode = 0, 1, 0)
End Function