在BackgroundWorker中使用Await会引发WorkerComplete事件 [英] Using Await in BackgroundWorker causes WorkerComplete event to be raised

查看:132
本文介绍了在BackgroundWorker中使用Await会引发WorkerComplete事件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有这个奇怪的问题.我需要从后台工作人员内部调用流程

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)ProcessStartInfoProcess对象添加到 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. 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的并行流程

定义调度程序并将其与当前上下文关联

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 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

这篇关于在BackgroundWorker中使用Await会引发WorkerComplete事件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆