Process.Start() 一个可执行文件和 return 一个 DialogResult 给调用者
Process.Start() an executable and return a DialogResult to the caller
当我想显示消息时,我从我的主程序(调用程序)调用消息显示子程序(EXE)。我无法将调用 exe 对话框结果发送给调用者。
Dim psiProcessInfo As New ProcessStartInfo
With psiProcessInfo
.FileName = "DisplayMessage"
.Arguments = ("FormName$C$lblMessageLine01$lblMessageLine02$lblMessageLine03")
End With
Process.Start(psiProcessInfo)
上面我显示调用部分。
Private Sub dlgDisplayMessage_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' Input Parameter Accepted
strInputMessage = Command()
' Composite Parameter Seperator
Dim strParaSeperator As String = "$"
Dim strCompersitePara As String = Microsoft.VisualBasic.Interaction.Command
' Parameter Split & Assign To Array
Dim arParameter() As String = strCompersitePara.Split(strParaSeperator.ToCharArray)
With pbPictureBox
Select Case lblMessageType.Text
Case Is = "C" ' Critical
.Image = My.Resources.Critical
Case Is = "E" ' Exclamation
.Image = My.Resources.Exclamation
Case Is = "Q" ' Question
.Image = My.Resources.Question
End Select
.Visible = True
End With
With txtMessageBody
.Multiline = True
.Size = New Size(386, 215)
.Location = New Point(24, 53)
.ScrollBars = ScrollBars.Vertical
.TextAlign = HorizontalAlignment.Center
.Text = vbCrLf & _
lblMessageLine01.Text.Trim & _
vbCrLf & vbCrLf & _
lblMessageLine02.Text.Trim & _
vbCrLf & vbCrLf & _
lblMessageLine03.Text.Trim
.Visible = True
End With
With cmdCancel
.Focus()
End With
End Sub
Private Sub cmdYes_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdYes.Click
Me.DialogResult = System.Windows.Forms.DialogResult.Yes
End Sub
Private Sub cmdCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdCancel.Click
Try
Me.DialogResult = Windows.Forms.DialogResult.No
End Sub
显示我在上面显示的消息对话框编码。我想知道如何让 DialogResult.OK
或 DialogResult.No
调用 exe。
已编辑
根据Jimi我更改了我的调用程序代码。但它仍然没有 return 任何价值。
Dim p As New Process()
p.StartInfo.UseShellExecute = False
p.StartInfo.ErrorDialog = True
p.StartInfo.RedirectStandardOutput = True
p.StartInfo.UseShellExecute = False
p.StartInfo.Arguments = ("FormName$C$lblMessageLine01$lblMessageLine02$lblMessageLine03")
p.StartInfo.FileName = "DisplayMessage"
p.Start()
Dim output As String = p.StandardOutput.ReadToEnd()
p.WaitForExit()
MessageBox.Show(output)
关于 dlgDisplayMessage
表单处理命令行参数的方式的一些建议:
您应该使用 Environment.GetCommandLineArgs() 来获取传递给命令行的值数组。这些值应由 space.
分隔
第一项始终表示可执行路径。推荐,它是.Net方法,更容易翻译成另一种.Net语言。
也可以用My.Application.CommandLineArgs,区别是第一项是命令行的第一个参数,不是可执行路径。
避免 Interaction.Command()
我认为那些lblMessageLine01
等部分实际上是一些标签的内容。在这种情况下,您当然应该使用 lblMessageLine01.Text
属性。您可以使用内插字符串将这些值添加到命令行。由于这些标签可能包含由 space 分隔的多个单词,您需要将这些值用双引号引起来。例如:
Dim commandLine = $"FormName C ""{lblMessageLine01.Text}${lblMessageLine02.Text}"""
到 return 来自对话框的值,使用过程 class,你有几个选择:
- 使用
Console.WriteLine()
/ Console.Out.WriteLine()
写入标准输出
- 使用Console.Error.WriteLine()
写入标准错误
- 使用 Console.OpenStandardOutput() +
StreamWriter.WriteLine()
初始化的 StreamWriter
- Console.SetOut(),传递现有的 StreamWriter(例如,如上初始化)
- 使用 Environment.Exit()
设置退出代码
当然您也可以使用某种形式的 Interprocess Communications,但这与 OP 中描述的情况不同。
Imports System.IO
Public Class dlgDisplayMessage
' For testing. Replace the Bitmaps with anything else
Private images As New Dictionary(Of String, Bitmap) From {
{"C", SystemIcons.Warning.ToBitmap()},
{"E", SystemIcons.Exclamation.ToBitmap()},
{"Q", SystemIcons.Question.ToBitmap()}
}
Protected Overrides Sub OnLoad(e As EventArgs)
' The first item is always the executable path
Dim arParameter = Environment.GetCommandLineArgs()
If arParameter.Count > 1 AndAlso images.ContainsKey(arParameter(1)) Then
pbPictureBox.Image = images(arParameter(1))
End If
If arParameter.Count > 2 Then
txtMessageBody.Visible = True
txtMessageBody.AppendText(String.Join(Environment.NewLine, arParameter(3).Split("$"c)))
End If
MyBase.OnLoad(e)
End Sub
Private Sub btnOK_Click(sender As Object, e As EventArgs) Handles btnOK.Click
Console.WriteLine("Some data to return")
Console.Out.WriteLine("More data")
Environment.Exit(DialogResult.OK)
End Sub
Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click
Using sw = New StreamWriter(Console.OpenStandardOutput())
sw.AutoFlush = True
sw.WriteLine("Other data to return")
End Using
Environment.Exit(DialogResult.Cancel)
End Sub
Private Sub btnAbort_Click(sender As Object, e As EventArgs) Handles btnAbort.Click
Console.Error.WriteLine("Some errors to report")
Environment.Exit(DialogResult.Abort)
End Sub
End Class
启动此可执行文件的应用程序可以通过不同方式获得对话框的结果:读取它启动的进程的 StandardOutput、StandardError 或 ExitCode。当然,或者全部。
我假设创建此对话框的应用程序在您的控制之下(您做到了)。
在任何情况下,进程 StartInfo must set RedirectStandardOutput to True
, optionally RedirectStandardError to True
and UseShellExecute 到 False
(从系统 Shell 开始不允许在此处重定向)
然后您可以:
开始进程
读取标准输出和标准错误
同步等待进程退出,使用Process.WaitForExit()
阅读过程ExitCode
,例如:
[Process].Start()
Dim sOut = [Process].StandardOutput.ReadToEnd()
Dim sErr = [Process].StandardError.ReadToEnd()
[Process].WaitForExit()
Dim exitCode = [Process]?.ExitCode
[Process]?.Dispose()
这个过程是同步的(阻塞的)。当进程启动并且其结果在 GUI 中等待时,您很有可能不希望这样做,因为它也会阻塞用户界面。
当然,您可以 运行 一个任务或启动一个线程,然后将结果编组回 UI 线程。
您还可以使用异步(事件驱动)版本,订阅 OutputDataReceived, ErrorDataReceived and Exited 事件。
要启用 Exited
事件,您需要将 Process.EnableRaisingEvents 设置为 True
。
还将 Process.SynchronizingObject 设置为将处理事件的控件 class 的实例(通常是表单,但任何 ISynchronizeInvoke
对象都可以)。这是因为进程的事件是在 ThreadPool 线程中引发的。将 UI 元素设置为 SynchronizingObject
,会导致事件在创建指定对象的同一线程中引发(此处为 UI 线程)。
这在现有上下文中可能有些令人讨厌,因为您必须将事件处理程序添加到表单 class,记得删除它们,处理进程 asynchronously
等
所以这里有一个助手 class,它可以将事件驱动的过程转换为可等待的任务,该任务可以从任何异步方法执行。例如,它可以从 Button 的 Click
事件处理程序调用,将 Async
关键字添加到方法中。
它可以被修改并用于测试不同的场景和方法,以启动一个进程并在单独的 environment.
中获取其结果
它使用 TaskCompletionSource + Task.WhenAny().
Exited
事件导致 TaskCompletionSource
设置其结果。
助手 class return 将 Process 的 StandardOutput、StandardError 和 ExitCode 值的内容转换为 DialogResult 值。
它可以设置超时,以停止等待进程 return 结果(如果指定)。
示例调用程序:
Private Async Sub SomeButton_Click(sender As Object, e As EventArgs) Handles SomeButton.Click
Dim exePath = "[The Executable Path]"
Dim cmdLine = $"FormName C ""{lblMessageLine01.Text}${lblMessageLine02.Text}${lblMessageLine03.Text}"""
Dim dlgResponse = New DialogResponseHelper(exePath, cmdLine)
' This call returns when the Dialog is closed or a Timeout occurs
Dim exitCode = Await dlgResponse.Start()
' Read the StandardOutput results
If dlgResponse.DialogData.Count > 0 Then
For Each dataItem In dlgResponse.DialogData
Console.WriteLine(dataItem)
Next
End If
' See whether errors are signaled
Console.WriteLine(dlgResponse.DialogErrors.Count)
If dlgResponse.ExitCode = DialogResult.OK Then
' Do something
End If
End Sub
如果 Interpolated Strings 功能不可用,请改用 String.Format()
:
Dim dataOut = {lblMessageLine01.Text, lblMessageLine02.Text, lblMessageLine03.Text}
Dim cmdLine = String.Format("FormName C ""{0}""", dataOut)
DialogResponseHelper
class:
Imports System.Collections.Concurrent
Imports System.Collections.Generic
Imports System.Diagnostics
Imports System.IO
Imports System.Linq
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Windows.Forms
Public Class DialogResponseHelper
Private exitedTcs As TaskCompletionSource(Of Integer) = Nothing
Private dlgData As New ConcurrentBag(Of String)()
Private dlgErrors As New ConcurrentBag(Of String)()
Private mExitCode As DialogResult = DialogResult.None
Private proc As Process = Nothing
Public Sub New(exePath As String, cmdLine As String, Optional timeout As Integer = Timeout.Infinite)
ExecutablePath = exePath
CommandLine = cmdLine
WaitTimeout = timeout
End Sub
Public ReadOnly Property ExecutablePath As String
Public ReadOnly Property CommandLine As String
Public ReadOnly Property WaitTimeout As Integer
Public ReadOnly Property ExitCode As DialogResult
Get
Return mExitCode
End Get
End Property
Public ReadOnly Property DialogData As List(Of String)
Get
Return dlgData.ToList()
End Get
End Property
Public ReadOnly Property DialogErrors As List(Of String)
Get
Return dlgErrors.ToList()
End Get
End Property
Public Async Function Start() As Task(Of Integer)
exitedTcs = New TaskCompletionSource(Of Integer)()
proc = New Process()
Dim psi = New ProcessStartInfo(ExecutablePath, CommandLine) With {
.RedirectStandardError = True,
.RedirectStandardOutput = True,
.UseShellExecute = False,
.WorkingDirectory = Path.GetDirectoryName(ExecutablePath)
}
proc.StartInfo = psi
AddHandler proc.OutputDataReceived, AddressOf DataReceived
AddHandler proc.ErrorDataReceived, AddressOf ErrorReceived
AddHandler proc.Exited, AddressOf ProcessExited
proc.EnableRaisingEvents = True
proc.Start()
proc.BeginErrorReadLine()
proc.BeginOutputReadLine()
Await Task.WhenAny(exitedTcs.Task, Task.Delay(WaitTimeout))
If proc IsNot Nothing Then
RemoveHandler proc.Exited, AddressOf ProcessExited
RemoveHandler proc.ErrorDataReceived, AddressOf ErrorReceived
RemoveHandler proc.OutputDataReceived, AddressOf DataReceived
proc.Dispose()
proc = Nothing
End If
Return exitedTcs.Task.Result
End Function
Private Sub DataReceived(sender As Object, e As DataReceivedEventArgs)
If String.IsNullOrEmpty(e.Data) Then Return
dlgData.Add(e.Data)
End Sub
Private Sub ErrorReceived(sender As Object, e As DataReceivedEventArgs)
If String.IsNullOrEmpty(e.Data) Then Return
dlgErrors.Add(e.Data)
End Sub
Private Sub ProcessExited(sender As Object, e As EventArgs)
Dim exitId = (proc?.ExitCode).Value
mExitCode = CType(exitId, DialogResult)
exitedTcs.TrySetResult(exitId)
End Sub
End Class
当我想显示消息时,我从我的主程序(调用程序)调用消息显示子程序(EXE)。我无法将调用 exe 对话框结果发送给调用者。
Dim psiProcessInfo As New ProcessStartInfo
With psiProcessInfo
.FileName = "DisplayMessage"
.Arguments = ("FormName$C$lblMessageLine01$lblMessageLine02$lblMessageLine03")
End With
Process.Start(psiProcessInfo)
上面我显示调用部分。
Private Sub dlgDisplayMessage_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' Input Parameter Accepted
strInputMessage = Command()
' Composite Parameter Seperator
Dim strParaSeperator As String = "$"
Dim strCompersitePara As String = Microsoft.VisualBasic.Interaction.Command
' Parameter Split & Assign To Array
Dim arParameter() As String = strCompersitePara.Split(strParaSeperator.ToCharArray)
With pbPictureBox
Select Case lblMessageType.Text
Case Is = "C" ' Critical
.Image = My.Resources.Critical
Case Is = "E" ' Exclamation
.Image = My.Resources.Exclamation
Case Is = "Q" ' Question
.Image = My.Resources.Question
End Select
.Visible = True
End With
With txtMessageBody
.Multiline = True
.Size = New Size(386, 215)
.Location = New Point(24, 53)
.ScrollBars = ScrollBars.Vertical
.TextAlign = HorizontalAlignment.Center
.Text = vbCrLf & _
lblMessageLine01.Text.Trim & _
vbCrLf & vbCrLf & _
lblMessageLine02.Text.Trim & _
vbCrLf & vbCrLf & _
lblMessageLine03.Text.Trim
.Visible = True
End With
With cmdCancel
.Focus()
End With
End Sub
Private Sub cmdYes_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdYes.Click
Me.DialogResult = System.Windows.Forms.DialogResult.Yes
End Sub
Private Sub cmdCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdCancel.Click
Try
Me.DialogResult = Windows.Forms.DialogResult.No
End Sub
显示我在上面显示的消息对话框编码。我想知道如何让 DialogResult.OK
或 DialogResult.No
调用 exe。
已编辑
根据Jimi我更改了我的调用程序代码。但它仍然没有 return 任何价值。
Dim p As New Process()
p.StartInfo.UseShellExecute = False
p.StartInfo.ErrorDialog = True
p.StartInfo.RedirectStandardOutput = True
p.StartInfo.UseShellExecute = False
p.StartInfo.Arguments = ("FormName$C$lblMessageLine01$lblMessageLine02$lblMessageLine03")
p.StartInfo.FileName = "DisplayMessage"
p.Start()
Dim output As String = p.StandardOutput.ReadToEnd()
p.WaitForExit()
MessageBox.Show(output)
关于 dlgDisplayMessage
表单处理命令行参数的方式的一些建议:
您应该使用 Environment.GetCommandLineArgs() 来获取传递给命令行的值数组。这些值应由 space.
分隔 第一项始终表示可执行路径。推荐,它是.Net方法,更容易翻译成另一种.Net语言。
也可以用My.Application.CommandLineArgs,区别是第一项是命令行的第一个参数,不是可执行路径。 避免Interaction.Command()
我认为那些
lblMessageLine01
等部分实际上是一些标签的内容。在这种情况下,您当然应该使用lblMessageLine01.Text
属性。您可以使用内插字符串将这些值添加到命令行。由于这些标签可能包含由 space 分隔的多个单词,您需要将这些值用双引号引起来。例如:Dim commandLine = $"FormName C ""{lblMessageLine01.Text}${lblMessageLine02.Text}"""
到 return 来自对话框的值,使用过程 class,你有几个选择:
- 使用
Console.WriteLine()
/Console.Out.WriteLine()
写入标准输出
- 使用Console.Error.WriteLine() 写入标准错误
- 使用 Console.OpenStandardOutput() +
StreamWriter.WriteLine()
初始化的 StreamWriter
- Console.SetOut(),传递现有的 StreamWriter(例如,如上初始化)
- 使用 Environment.Exit() 设置退出代码
- 使用
当然您也可以使用某种形式的 Interprocess Communications,但这与 OP 中描述的情况不同。
Imports System.IO
Public Class dlgDisplayMessage
' For testing. Replace the Bitmaps with anything else
Private images As New Dictionary(Of String, Bitmap) From {
{"C", SystemIcons.Warning.ToBitmap()},
{"E", SystemIcons.Exclamation.ToBitmap()},
{"Q", SystemIcons.Question.ToBitmap()}
}
Protected Overrides Sub OnLoad(e As EventArgs)
' The first item is always the executable path
Dim arParameter = Environment.GetCommandLineArgs()
If arParameter.Count > 1 AndAlso images.ContainsKey(arParameter(1)) Then
pbPictureBox.Image = images(arParameter(1))
End If
If arParameter.Count > 2 Then
txtMessageBody.Visible = True
txtMessageBody.AppendText(String.Join(Environment.NewLine, arParameter(3).Split("$"c)))
End If
MyBase.OnLoad(e)
End Sub
Private Sub btnOK_Click(sender As Object, e As EventArgs) Handles btnOK.Click
Console.WriteLine("Some data to return")
Console.Out.WriteLine("More data")
Environment.Exit(DialogResult.OK)
End Sub
Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click
Using sw = New StreamWriter(Console.OpenStandardOutput())
sw.AutoFlush = True
sw.WriteLine("Other data to return")
End Using
Environment.Exit(DialogResult.Cancel)
End Sub
Private Sub btnAbort_Click(sender As Object, e As EventArgs) Handles btnAbort.Click
Console.Error.WriteLine("Some errors to report")
Environment.Exit(DialogResult.Abort)
End Sub
End Class
启动此可执行文件的应用程序可以通过不同方式获得对话框的结果:读取它启动的进程的 StandardOutput、StandardError 或 ExitCode。当然,或者全部。
我假设创建此对话框的应用程序在您的控制之下(您做到了)。
在任何情况下,进程 StartInfo must set RedirectStandardOutput to True
, optionally RedirectStandardError to True
and UseShellExecute 到 False
(从系统 Shell 开始不允许在此处重定向)
然后您可以:
开始进程
读取标准输出和标准错误
同步等待进程退出,使用Process.WaitForExit()
阅读过程
ExitCode
,例如:[Process].Start() Dim sOut = [Process].StandardOutput.ReadToEnd() Dim sErr = [Process].StandardError.ReadToEnd() [Process].WaitForExit() Dim exitCode = [Process]?.ExitCode [Process]?.Dispose()
这个过程是同步的(阻塞的)。当进程启动并且其结果在 GUI 中等待时,您很有可能不希望这样做,因为它也会阻塞用户界面。
当然,您可以 运行 一个任务或启动一个线程,然后将结果编组回 UI 线程。
您还可以使用异步(事件驱动)版本,订阅 OutputDataReceived, ErrorDataReceived and Exited 事件。
要启用 Exited
事件,您需要将 Process.EnableRaisingEvents 设置为 True
。
还将 Process.SynchronizingObject 设置为将处理事件的控件 class 的实例(通常是表单,但任何 ISynchronizeInvoke
对象都可以)。这是因为进程的事件是在 ThreadPool 线程中引发的。将 UI 元素设置为 SynchronizingObject
,会导致事件在创建指定对象的同一线程中引发(此处为 UI 线程)。
这在现有上下文中可能有些令人讨厌,因为您必须将事件处理程序添加到表单 class,记得删除它们,处理进程 asynchronously
等
所以这里有一个助手 class,它可以将事件驱动的过程转换为可等待的任务,该任务可以从任何异步方法执行。例如,它可以从 Button 的 Click
事件处理程序调用,将 Async
关键字添加到方法中。
它可以被修改并用于测试不同的场景和方法,以启动一个进程并在单独的 environment.
它使用 TaskCompletionSource + Task.WhenAny().
Exited
事件导致 TaskCompletionSource
设置其结果。
助手 class return 将 Process 的 StandardOutput、StandardError 和 ExitCode 值的内容转换为 DialogResult 值。
它可以设置超时,以停止等待进程 return 结果(如果指定)。
示例调用程序:
Private Async Sub SomeButton_Click(sender As Object, e As EventArgs) Handles SomeButton.Click
Dim exePath = "[The Executable Path]"
Dim cmdLine = $"FormName C ""{lblMessageLine01.Text}${lblMessageLine02.Text}${lblMessageLine03.Text}"""
Dim dlgResponse = New DialogResponseHelper(exePath, cmdLine)
' This call returns when the Dialog is closed or a Timeout occurs
Dim exitCode = Await dlgResponse.Start()
' Read the StandardOutput results
If dlgResponse.DialogData.Count > 0 Then
For Each dataItem In dlgResponse.DialogData
Console.WriteLine(dataItem)
Next
End If
' See whether errors are signaled
Console.WriteLine(dlgResponse.DialogErrors.Count)
If dlgResponse.ExitCode = DialogResult.OK Then
' Do something
End If
End Sub
如果 Interpolated Strings 功能不可用,请改用 String.Format()
:
Dim dataOut = {lblMessageLine01.Text, lblMessageLine02.Text, lblMessageLine03.Text}
Dim cmdLine = String.Format("FormName C ""{0}""", dataOut)
DialogResponseHelper
class:
Imports System.Collections.Concurrent
Imports System.Collections.Generic
Imports System.Diagnostics
Imports System.IO
Imports System.Linq
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Windows.Forms
Public Class DialogResponseHelper
Private exitedTcs As TaskCompletionSource(Of Integer) = Nothing
Private dlgData As New ConcurrentBag(Of String)()
Private dlgErrors As New ConcurrentBag(Of String)()
Private mExitCode As DialogResult = DialogResult.None
Private proc As Process = Nothing
Public Sub New(exePath As String, cmdLine As String, Optional timeout As Integer = Timeout.Infinite)
ExecutablePath = exePath
CommandLine = cmdLine
WaitTimeout = timeout
End Sub
Public ReadOnly Property ExecutablePath As String
Public ReadOnly Property CommandLine As String
Public ReadOnly Property WaitTimeout As Integer
Public ReadOnly Property ExitCode As DialogResult
Get
Return mExitCode
End Get
End Property
Public ReadOnly Property DialogData As List(Of String)
Get
Return dlgData.ToList()
End Get
End Property
Public ReadOnly Property DialogErrors As List(Of String)
Get
Return dlgErrors.ToList()
End Get
End Property
Public Async Function Start() As Task(Of Integer)
exitedTcs = New TaskCompletionSource(Of Integer)()
proc = New Process()
Dim psi = New ProcessStartInfo(ExecutablePath, CommandLine) With {
.RedirectStandardError = True,
.RedirectStandardOutput = True,
.UseShellExecute = False,
.WorkingDirectory = Path.GetDirectoryName(ExecutablePath)
}
proc.StartInfo = psi
AddHandler proc.OutputDataReceived, AddressOf DataReceived
AddHandler proc.ErrorDataReceived, AddressOf ErrorReceived
AddHandler proc.Exited, AddressOf ProcessExited
proc.EnableRaisingEvents = True
proc.Start()
proc.BeginErrorReadLine()
proc.BeginOutputReadLine()
Await Task.WhenAny(exitedTcs.Task, Task.Delay(WaitTimeout))
If proc IsNot Nothing Then
RemoveHandler proc.Exited, AddressOf ProcessExited
RemoveHandler proc.ErrorDataReceived, AddressOf ErrorReceived
RemoveHandler proc.OutputDataReceived, AddressOf DataReceived
proc.Dispose()
proc = Nothing
End If
Return exitedTcs.Task.Result
End Function
Private Sub DataReceived(sender As Object, e As DataReceivedEventArgs)
If String.IsNullOrEmpty(e.Data) Then Return
dlgData.Add(e.Data)
End Sub
Private Sub ErrorReceived(sender As Object, e As DataReceivedEventArgs)
If String.IsNullOrEmpty(e.Data) Then Return
dlgErrors.Add(e.Data)
End Sub
Private Sub ProcessExited(sender As Object, e As EventArgs)
Dim exitId = (proc?.ExitCode).Value
mExitCode = CType(exitId, DialogResult)
exitedTcs.TrySetResult(exitId)
End Sub
End Class