【问题标题】:Async in VB not working as expectedVB中的异步未按预期工作
【发布时间】:2017-11-05 02:39:54
【问题描述】:

更新: 看起来罪魁祸首是状态报告

ffStatus = procFFMPEG.StandardError         'Send standard error to ffStatus
strFFout = ffStatus.ReadLine                'Read every line of output and send to strFFout

看起来它打破了那部分的异步。注释掉后,选取框滚动条的行为符合预期。

有没有办法在不中断异步显示选框进度条的情况下获取这些数据并更新状态?

原帖

这个问题我也有类似的问题 VB.NET Marquee Progress Until Process Exits

我使用接受的答案并获取此代码

Public Async Sub GoConvert(theVCodec As String, theHeight As String)
    Dim theOptions As String, theApp As String, theSourcePath As String, theDestPath As String
    Dim theFilename As String, theACodec As String, theFormat As String, theLosslessOpt As String
    Dim theNewFilename As String, theNewFileTag As String, theInterlaced As String, theMsg As String
    Dim thePreset As String, theCRF As String

    Dim ffStatus As StreamReader, strFFout As String

    'On Error GoTo Handler
    'SET DEFAULT VALUES

    theApp = "ffmpeg.exe"
    theSourcePath = txtSource.Text
    theDestPath = txtOutput.Text & "\"
    theACodec = "libmp3lame"
    thePreset = "veryfast"
    theCRF = "22"

    theInterlaced = ""
    theNewFileTag = ""
    theLosslessOpt = ""

    Select Case theVCodec
        Case "libx264"
            theNewFileTag = "x264"
        Case "libxvid"
            theNewFileTag = "xvid"
        Case "libx265"
            theNewFileTag = "x265"
    End Select

    If cmbUseCodec.Text = "x264 vegas" Then
        theACodec = "aac"
        theNewFileTag = "x264forVegas"
    End If

    theMsg = "IF FILE EXISTS, IT WILL BE OVERWRITTEN!" & vbCrLf & vbCrLf & "Please make sure that there is no filename conflict in the destination folder," & vbCrLf & "Encoder will overwrite existing files." _
        & vbCrLf & vbCrLf & "Do you want to continue?"

    If MessageBox.Show(theMsg, "WARNING!", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) = DialogResult.Yes Then

        For i As Integer = 0 To lstSourceFiles.Items.Count - 1

            If chkToFileType.CheckedItems.Count <> 0 Then
                Dim x As Integer
                Dim forVegas As String

                For x = 0 To chkToFileType.CheckedItems.Count - 1

                    theFormat = chkToFileType.CheckedItems(x).ToString

                    'GET FILENAMES ON FILES LISTBOX
                    theFilename = lstSourceFiles.Items(i).ToString
                    theNewFilename = System.IO.Path.GetFileNameWithoutExtension(theFilename)

                    If chkSameOutputFolder.CheckedItems.Count > 0 Then
                        theDestPath = Path.GetDirectoryName(theFilename) & "\"
                    End If

                    If (theVCodec = "libx265") Then
                        theCRF = "28"
                        thePreset = "medium"
                    End If
                    If (chkLossLess.CheckedItems.Count > 0) And (theVCodec = "libx265") Then
                        theLosslessOpt = "-x265-params lossless=1 "
                    End If


                    If theFormat = "mp3" Then
                        '-i "%%a" -qa 0 - map a "%%~na.mp3"
                        theOptions = " -i " & Chr(34) & theFilename & Chr(34) & " -y -q:a 0 -map a " & Chr(34) & theDestPath & theNewFilename & "." & theFormat & Chr(34)
                    Else
                        'PREPARE NEW FILENAME OF CONVERTED FILE
                        theNewFilename = theNewFilename & "-" & theNewFileTag & "-" & theHeight & "p"

                        theOptions = " -i " & Chr(34) & theFilename & Chr(34) & " -y -vcodec " & theVCodec
                        theOptions = theOptions & " -vf " & theInterlaced & "scale=" & Chr(34) & "trunc(oh*a/2)*2:" & theHeight & Chr(34)

                        If cmbUseCodec.Text = "x264 vegas" Then
                            forVegas = " -strict experimental -tune fastdecode -pix_fmt yuv420p -b:a 192k -ar 48000"
                            theOptions = theOptions & " -preset " & thePreset & " -crf " & theCRF & " -acodec " & theACodec & forVegas
                            theOptions = theOptions & " -threads 4 " & theLosslessOpt & Chr(34) & theDestPath & theNewFilename & "." & theFormat & Chr(34)
                        Else
                            theOptions = theOptions & " -b 1750k -preset " & thePreset & " -crf " & theCRF & " -acodec " & theACodec
                            theOptions = theOptions & " -ac 2 -ab 160k -threads 4 " & theLosslessOpt & Chr(34) & theDestPath & theNewFilename & "." & theFormat & Chr(34)
                        End If
                    End If
                    theOptions = theOptions & " -loglevel error -stats"


                    'LET'S GET READY TO CONVERT
                    ConvertProcessInfo.FileName = theApp
                    ConvertProcessInfo.Arguments = theOptions

                    'LET'S TRY TO CAPTURE STATUS
                    ConvertProcessInfo.RedirectStandardError = True
                    ConvertProcessInfo.RedirectStandardOutput = True
                    ConvertProcessInfo.UseShellExecute = False
                    ConvertProcessInfo.CreateNoWindow = True

                    'LET'S PROVIDE SOME MEANINGFUL INFO
                    procFFMPEG.StartInfo = ConvertProcessInfo
                    lstStatus.Items(i) = "Encoding: " & theFormat
                    txtProcessInfo.Text = "Encoding file: " & theNewFilename & "." & theFormat

                    'LET'S DISABLE CONTROLS WHILE CONVERT IS WORKING AND ENABLE PROGRESSBAR
                    prgrssConvert.Visible = True
                    DisableControls()

                    'LET'S CONVERT
                    procFFMPEG.Start()

                    Do
                        Application.DoEvents()
                        ffStatus = procFFMPEG.StandardError         'Send standard error to ffStatus
                        strFFout = ffStatus.ReadLine                'Read every line of output and send to strFFout
                        Debug.Print(strFFout)
                        txtProcessInfo.Text = strFFout

                        'THESE LINES IS NOT NEEDED IF ASYNC WILL WORK
                        txtProcessInfo.Refresh()
                        lstSourceFiles.Refresh()
                        lstStatus.Refresh()
                        prgrssConvert.Refresh()

                    Loop Until procFFMPEG.HasExited

                    'LET'S WAIT FOR PROCESS TO EXIT
                    Await Task.Run(Sub() procFFMPEG.WaitForExit())

                    'UPDATE STATUS AFTER EVERY FILE
                    prgrssConvert.Visible = False
                    lstStatus.Items(i) = "DONE"
                Next
            End If
        Next
        'WHEN ALL FILES DONE, UPDATE STATUS
        txtProcessInfo.Text = "Encoding completed. Waiting for new task"
        EnableControls()
    End If

End Sub

我的问题是进度条 (prgrssConvert.Visible = True) 没有异步更新,这就是为什么我必须在 DO LOOP 中添加刷新,但它在视觉上并不吸引人,因为它是“机器人”而不是流畅的选取框。

在我看来,异步并没有发挥作用。我希望在等待 ffmpeg 进程完成时保持进度条选取框运行。

知道为什么异步无法处理我的代码吗?

谢谢

【问题讨论】:

  • 不要不要使用Application.DoEvents()。它可能会导致代码出现各种问题,并且很难调试。在循环中使用它只会使问题更加复杂。你需要使用计时器来获得你想要的结果。
  • 嗨@Enigmativity,你能详细说明一下计时器吗? Application.DoEvents() 是出于沮丧而使用的 :) 删除它不会改变任何事情。
  • StandardError.ReadLine() 是一个阻塞调用。如果你做对了,这可能需要很长时间,希望这个过程不会产生很多错误消息。这会挂起您的 UI 线程并停止更新进度条。使用 BeginReadLine() 或将此代码移动到工作线程中。并修复另一个错误,您没有阅读 StandardOutput。这可能会导致程序永远挂起。
  • 您的问题是您正在尝试获取编码进度,但您需要一个函数来返回异步调用的进度。
  • 如果您想要一个没有异步功能的进度条来更新您的进度条,那么请使用进度条在您的文件列表上显示进度以进行编码。

标签: vb.net asynchronous progress-bar


【解决方案1】:

一个好的问题有完美的答案。我没有看到您将输出返回到文本框。对不起。您必须使用 readlineasync 否则您正在等待输出中的一行可能只在最后出现。如果它永远不会出现,您的应用将被卡在那里。

这是用于读取错误 procFFMPEG.StandardError 如果你真的想要你的过程的输出使用这个 procMMFPEG.StandardOutput 改为或两者兼而有之,但您需要调整您的代码以适应它

some reference for StandardOutput

Public Async Sub GoConvert(theVCodec As String, theHeight As String)
    Dim theOptions As String, theApp As String, theSourcePath As String, theDestPath As String
    Dim theFilename As String, theACodec As String, theFormat As String, theLosslessOpt As String
    Dim theNewFilename As String, theNewFileTag As String, theInterlaced As String, theMsg As String
    Dim thePreset As String, theCRF As String

    Dim ffStatus As StreamReader, strFFout As String

    'On Error GoTo Handler
    'SET DEFAULT VALUES

    theApp = "ffmpeg.exe"
    theSourcePath = txtSource.Text
    theDestPath = txtOutput.Text & "\"
    theACodec = "libmp3lame"
    thePreset = "veryfast"
    theCRF = "22"

    theInterlaced = ""
    theNewFileTag = ""
    theLosslessOpt = ""

    Select Case theVCodec
        Case "libx264"
            theNewFileTag = "x264"
        Case "libxvid"
            theNewFileTag = "xvid"
        Case "libx265"
            theNewFileTag = "x265"
    End Select

    If cmbUseCodec.Text = "x264 vegas" Then
        theACodec = "aac"
        theNewFileTag = "x264forVegas"
    End If

    theMsg = "IF FILE EXISTS, IT WILL BE OVERWRITTEN!" & vbCrLf & vbCrLf & "Please make sure that there is no filename conflict in the destination folder," & vbCrLf & "Encoder will overwrite existing files." _
    & vbCrLf & vbCrLf & "Do you want to continue?"

    If MessageBox.Show(theMsg, "WARNING!", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation) = DialogResult.Yes Then
        'LET'S DISABLE CONTROLS WHILE CONVERT IS WORKING AND ENABLE PROGRESSBAR
        prgrssConvert.Visible = True
        DisableControls()
        prgrssConvert.value = 0 'I assumed this was a progressbar
        prgrssConvert.maximum = lstSourceFiles.Items.Count * chkToFileType.CheckedItems.Count

        For i As Integer = 0 To lstSourceFiles.Items.Count - 1

            If chkToFileType.CheckedItems.Count <> 0 Then
                Dim x As Integer
                Dim forVegas As String

                For x = 0 To chkToFileType.CheckedItems.Count - 1

                    theFormat = chkToFileType.CheckedItems(x).ToString

                    'GET FILENAMES ON FILES LISTBOX
                    theFilename = lstSourceFiles.Items(i).ToString
                    theNewFilename = System.IO.Path.GetFileNameWithoutExtension(theFilename)

                    If chkSameOutputFolder.CheckedItems.Count > 0 Then
                        theDestPath = Path.GetDirectoryName(theFilename) & "\"
                    End If

                    If (theVCodec = "libx265") Then
                        theCRF = "28"
                        thePreset = "medium"
                    End If
                    If (chkLossLess.CheckedItems.Count > 0) And (theVCodec = "libx265") Then
                        theLosslessOpt = "-x265-params lossless=1 "
                    End If


                    If theFormat = "mp3" Then
                        '-i "%%a" -qa 0 - map a "%%~na.mp3"
                        theOptions = " -i " & Chr(34) & theFilename & Chr(34) & " -y -q:a 0 -map a " & Chr(34) & theDestPath & theNewFilename & "." & theFormat & Chr(34)
                    Else
                        'PREPARE NEW FILENAME OF CONVERTED FILE
                        theNewFilename = theNewFilename & "-" & theNewFileTag & "-" & theHeight & "p"

                        theOptions = " -i " & Chr(34) & theFilename & Chr(34) & " -y -vcodec " & theVCodec
                        theOptions = theOptions & " -vf " & theInterlaced & "scale=" & Chr(34) & "trunc(oh*a/2)*2:" & theHeight & Chr(34)

                        If cmbUseCodec.Text = "x264 vegas" Then
                            forVegas = " -strict experimental -tune fastdecode -pix_fmt yuv420p -b:a 192k -ar 48000"
                            theOptions = theOptions & " -preset " & thePreset & " -crf " & theCRF & " -acodec " & theACodec & forVegas
                            theOptions = theOptions & " -threads 4 " & theLosslessOpt & Chr(34) & theDestPath & theNewFilename & "." & theFormat & Chr(34)
                        Else
                            theOptions = theOptions & " -b 1750k -preset " & thePreset & " -crf " & theCRF & " -acodec " & theACodec
                            theOptions = theOptions & " -ac 2 -ab 160k -threads 4 " & theLosslessOpt & Chr(34) & theDestPath & theNewFilename & "." & theFormat & Chr(34)
                        End If
                    End If
                    theOptions = theOptions & " -loglevel error -stats"


                    'LET'S GET READY TO CONVERT
                    ConvertProcessInfo.FileName = theApp
                    ConvertProcessInfo.Arguments = theOptions

                    'LET'S TRY TO CAPTURE STATUS
                    ConvertProcessInfo.RedirectStandardError = True
                    ConvertProcessInfo.RedirectStandardOutput = True
                    ConvertProcessInfo.UseShellExecute = False
                    ConvertProcessInfo.CreateNoWindow = True

                    'LET'S PROVIDE SOME MEANINGFUL INFO
                    procFFMPEG.StartInfo = ConvertProcessInfo
                    lstStatus.Items(i) = "Encoding: " & theFormat
                    txtProcessInfo.Text = "Encoding file: " & theNewFilename & "." & theFormat


                    'LET'S CONVERT
                    procFFMPEG.Start()
                    Do
                        ffStatus = procFFMPEG.StandardError         'Send standard error to ffStatus
                        strFFout = Await(ffStatus.ReadLineAsync())               'Read every line of output and send to strFFout
                        Debug.Print(strFFout)
                        txtProcessInfo.Text = strFFout
                    Loop Until procFFMPEG.HasExited = True



                    'UPDATE STATUS AFTER EVERY FILE
                    prgrssConvert.value += 1
                Next
                lstStatus.Items(i) = "DONE"
            End If
        Next
        prgrssConvert.Visible = False

        'WHEN ALL FILES DONE, UPDATE STATUS
        txtProcessInfo.Text = "Encoding completed. Waiting for new task"
        EnableControls()
    End If

End Sub

【讨论】:

  • 为了那些将来会看这个答案的人,为了节省您寻找解决问题的时间是这条线。 strFFout = Await(ffStatus.ReadLineAsync())
【解决方案2】:

这是未经测试的,但你可以试试这个:

                Do
                    Await Task.Delay(TimeSpan.FromSeconds(0.1))
                    ffStatus = procFFMPEG.StandardError         'Send standard error to ffStatus
                    strFFout = ffStatus.ReadLine                'Read every line of output and send to strFFout
                    Debug.Print(strFFout)
                    txtProcessInfo.Text = strFFout

                Loop Until Await Task.Run(Function() procFFMPEG.HasExited)

这里有一段简单的代码来测试它是否有效:

Dim process As New Process()

process.StartInfo = New ProcessStartInfo("C:\Windows\system32\WindowsPowerShell\v1.0\powershell.exe")
process.Start()

Do
    Await Task.Delay(TimeSpan.FromSeconds(0.1))
    Console.WriteLine("!")
Loop Until Await Task.Run(Function() process.HasExited)

在 PowerShell 控制台关闭之前,它会生成 ! 行。

【讨论】:

  • 同样的结果。进度条仅在循环内更新,使其表现得像一个更新每个循环的计时器,而不是平滑地运行选取框
  • 请在原帖上查看我的 cmets。 do while 事件运行良好。我不喜欢的是它的动画方式。它就像一个tic tac tic tac 动画不平滑滚动。目前我有一个文本框,通过显示当前时间和帧来更新当前转换进度。现在这很好。问题是选框进度条在循环期间被冻结。谢谢
  • @Wayne。带有 readline() 的行是行阻塞。你试过我提供的答案了吗?
猜你喜欢
  • 1970-01-01
  • 2018-05-29
  • 2019-04-19
  • 2020-03-04
  • 2019-04-07
  • 2020-08-11
  • 2012-11-14
  • 2022-01-22
  • 2018-12-02
相关资源
最近更新 更多