在BackgroundWorker中使用Await会引发WorkerComplete事件 [英] Using Await in BackgroundWorker causes WorkerComplete event to be raised
问题描述
我有这个奇怪的问题.我需要从后台工作人员内部调用流程
I have this weird problem. I need to invoke a process from within a background worker
Private Shared _process As Process
Private Shared _StartInfo As ProcessStartInfo
Private WithEvents _bwConvertMedia As New BackgroundWorker
这是DoWorkAsync中的工作
Here is the work in 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)
The issue is that when the Await _tcs.Task is executed the _bwConvertMedia RunWorkerCompleted procedure is executed so when I do call the _bwConvertMedia.ReportProgress(ai)
我收到一个错误,表明工作人员已经完成.
I get an error that the worker is already finished.
那是为什么?你能帮我吗?
Why is that? can you help me?
会发生什么事
- DoWork-迭代1
- 在等待过程1中
- RunWorkerComplete
- DoWork迭代2-100
正确的行为是后台工作程序调用该进程100次,然后完成执行并调用RunWorkerCompleted
The correct behavior is that the background worker invokes 100 times the process and THEN it finishes the execution and calls the RunWorkerCompleted
推荐答案
我对以前链接的代码进行了一些修改,这是使用顺序无阻塞Async/Await过程和使用无阻塞并行过程的两个示例Task.Factory
.
I made some modifications to the code I previously linked and here are two examples of a sequential non-blocking Async/Await procedure and a non-blocking Parallel procedure using Task.Factory
.
由于我无法测试您的程序,因此我只是使用
Tracert.exe
模拟标准输出结果以更新用户界面.
Since I can't test your program, I simply used
Tracert.exe
to simulate a stdout result to update the User Interface.
要使正在运行的任务/线程与UI同步,在第一种情况下,我使用了进程的.SynchronizingObject
,在第二种情况下,我使用了TaskScheduler方法TaskScheduler.FromCurrentSynchronizationContext()
.
To synchronize the running tasks/threads with the UI, I used in the first case the .SynchronizingObject
of the processes and in the second the TaskScheduler method TaskScheduler.FromCurrentSynchronizationContext()
.
Tracert.exe
的输出传递到两个文本框.
在并行示例中,我在Task之间插入了1秒的延迟,以查看如何更新两个TextBox.
The output from Tracert.exe
is passed to two TextBoxes.
In the parallel example, I inserted a delay of 1 second between Tasks, to see how the two TextBoxes are updated.
可以修改Async/Await示例以使其工作方式不同,因为您无需等待任务完成就可以启动另一个任务.
The Async/Await example can be modified to work differently, since you don't need to wait for a task to complete to start another.
使用List(Of ProcessStartInfo)
和List(Of Process)
将ProcessStartInfo
和Process
对象添加到 Pool 中.
The ProcessStartInfo
and Process
objects are added to a Pool using a List(Of ProcessStartInfo)
and a List(Of Process)
.
在两个示例中都使用了它们.定义正确的范围.
These are used in both examples. Define a correct scope.
Public psInfoPool As List(Of ProcessStartInfo)
Public ProcessPool As List(Of Process)
顺序异步/等待
该委托与SynchronizingObject.BeginInvoke一起使用,如果 InvokeRequired = true
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
MediaToConvert
参数将是要 当您根据需要调整示例时进行转换.OutCtl
参数只是用于输出的TextBox
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的并行流程
定义调度程序并将其与当前上下文关联
Define a Scheduler and associate it with the current context
Public _Scheduler As TaskScheduler = TaskScheduler.FromCurrentSynchronizationContext()
要使用
Await Task.Delay(1000)
,您必须处于Async方法中,但是 只是用于测试输出,不需要.
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
请注意,当
OutputDataReceived
事件发生时,将创建一个新任务. 处理程序报告已接收到新数据.用户界面已更新 相应地使用DataReceivedEventArgs
e.Data.
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
这篇关于在BackgroundWorker中使用Await会引发WorkerComplete事件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!