diff --git a/src/Microsoft.VisualBasic.Forms/src/Microsoft/VisualBasic/ApplicationServices/SingleInstanceHelpers.vb b/src/Microsoft.VisualBasic.Forms/src/Microsoft/VisualBasic/ApplicationServices/SingleInstanceHelpers.vb index 5895efb9536..4801df5276b 100644 --- a/src/Microsoft.VisualBasic.Forms/src/Microsoft/VisualBasic/ApplicationServices/SingleInstanceHelpers.vb +++ b/src/Microsoft.VisualBasic.Forms/src/Microsoft/VisualBasic/ApplicationServices/SingleInstanceHelpers.vb @@ -12,20 +12,26 @@ Namespace Microsoft.VisualBasic.ApplicationServices Friend Module SingleInstanceHelpers Private Const NamedPipeOptions As PipeOptions = PipeOptions.Asynchronous Or PipeOptions.CurrentUserOnly - Private Async Function ReadArgsAsync(pipeServer As NamedPipeServerStream, cancellationToken As CancellationToken) As Task(Of String()) + Private Async Function ReadArgsAsync( + pipeServer As NamedPipeServerStream, + cancellationToken As CancellationToken) As Task(Of String()) + Const bufferLength As Integer = 1024 - Dim buffer As Byte() = New Byte(bufferLength - 1) {} + Using stream As New MemoryStream While True + Dim buffer As Byte() = New Byte(bufferLength - 1) {} Dim bytesRead As Integer = Await pipeServer.ReadAsync( - buffer.AsMemory(0, bufferLength), - cancellationToken).ConfigureAwait(False) + buffer:=buffer.AsMemory(start:=0, length:=bufferLength), + cancellationToken) _ + .ConfigureAwait(continueOnCapturedContext:=False) If bytesRead = 0 Then Exit While End If Await stream.WriteAsync( - buffer.AsMemory(0, bytesRead), - cancellationToken).ConfigureAwait(False) + buffer:=buffer.AsMemory(start:=0, length:=bytesRead), + cancellationToken) _ + .ConfigureAwait(continueOnCapturedContext:=False) End While stream.Seek(0, SeekOrigin.Begin) Dim serializer As New DataContractSerializer(GetType(String())) @@ -37,35 +43,51 @@ Namespace Microsoft.VisualBasic.ApplicationServices End Using End Function - Private Async Function WriteArgsAsync(pipeClient As NamedPipeClientStream, args As String(), cancellationToken As CancellationToken) As Task + Private Async Function WriteArgsAsync( + pipeClient As NamedPipeClientStream, + args As String(), + cancellationToken As CancellationToken) As Task + Dim content As Byte() Using stream As New MemoryStream Dim serializer As New DataContractSerializer(GetType(String())) serializer.WriteObject(stream, args) content = stream.ToArray() End Using - Await pipeClient.WriteAsync(content.AsMemory(0, content.Length), cancellationToken).ConfigureAwait(False) + Await pipeClient.WriteAsync( + buffer:=content.AsMemory(start:=0, length:=content.Length), cancellationToken) _ + .ConfigureAwait(continueOnCapturedContext:=False) End Function - Friend Async Function SendSecondInstanceArgsAsync(pipeName As String, args As String(), cancellationToken As CancellationToken) As Task + Friend Async Function SendSecondInstanceArgsAsync( + pipeName As String, + args As String(), + cancellationToken As CancellationToken) As Task + Using pipeClient As New NamedPipeClientStream( - serverName:=".", - pipeName:=pipeName, - direction:=PipeDirection.Out, - options:=NamedPipeOptions) - Await pipeClient.ConnectAsync(cancellationToken).ConfigureAwait(False) - Await WriteArgsAsync(pipeClient, args, cancellationToken).ConfigureAwait(False) + serverName:=".", + pipeName:=pipeName, + direction:=PipeDirection.Out, + options:=NamedPipeOptions) + + Await pipeClient.ConnectAsync(cancellationToken) _ + .ConfigureAwait(continueOnCapturedContext:=False) + Await WriteArgsAsync(pipeClient, args, cancellationToken) _ + .ConfigureAwait(continueOnCapturedContext:=False) End Using End Function - Friend Function TryCreatePipeServer(pipeName As String, ByRef pipeServer As NamedPipeServerStream) As Boolean + Friend Function TryCreatePipeServer( + pipeName As String, + ByRef pipeServer As NamedPipeServerStream) As Boolean + Try pipeServer = New NamedPipeServerStream( - pipeName:=pipeName, - direction:=PipeDirection.In, - maxNumberOfServerInstances:=1, - transmissionMode:=PipeTransmissionMode.Byte, - options:=NamedPipeOptions) + pipeName:=pipeName, + direction:=PipeDirection.In, + maxNumberOfServerInstances:=1, + transmissionMode:=PipeTransmissionMode.Byte, + options:=NamedPipeOptions) Return True Catch ex As Exception pipeServer = Nothing @@ -73,12 +95,18 @@ Namespace Microsoft.VisualBasic.ApplicationServices End Try End Function - Friend Async Function WaitForClientConnectionsAsync(pipeServer As NamedPipeServerStream, callback As Action(Of String()), cancellationToken As CancellationToken) As Task + Friend Async Function WaitForClientConnectionsAsync( + pipeServer As NamedPipeServerStream, + callback As Action(Of String()), + cancellationToken As CancellationToken) As Task + While True cancellationToken.ThrowIfCancellationRequested() - Await pipeServer.WaitForConnectionAsync(cancellationToken).ConfigureAwait(False) + Await pipeServer.WaitForConnectionAsync(cancellationToken) _ + .ConfigureAwait(continueOnCapturedContext:=False) Try - Dim args() As String = Await ReadArgsAsync(pipeServer, cancellationToken).ConfigureAwait(False) + Dim args() As String = Await ReadArgsAsync(pipeServer, cancellationToken) _ + .ConfigureAwait(continueOnCapturedContext:=False) If args IsNot Nothing Then callback(args) End If diff --git a/src/Microsoft.VisualBasic.Forms/tests/UnitTests/System/Windows/Forms/SingleInstanceHelpersTests.vb b/src/Microsoft.VisualBasic.Forms/tests/UnitTests/System/Windows/Forms/SingleInstanceHelpersTests.vb new file mode 100644 index 00000000000..ede0967cedb --- /dev/null +++ b/src/Microsoft.VisualBasic.Forms/tests/UnitTests/System/Windows/Forms/SingleInstanceHelpersTests.vb @@ -0,0 +1,84 @@ +' Licensed to the .NET Foundation under one or more agreements. +' The .NET Foundation licenses this file to you under the MIT license. + +Imports System.IO.Pipes +Imports System.Runtime.CompilerServices +Imports System.Threading +Imports FluentAssertions +Imports Microsoft.VisualBasic.ApplicationServices + +Imports Xunit + +Namespace Microsoft.VisualBasic.Forms.Tests + + Public Class SingleInstanceHelpersTests + Private _resultArgs As String() + + + Public Sub TryCreatePipeServerTests() + Dim pipeName As String = GetUniqueText() + Dim pipeServer As NamedPipeServerStream = Nothing + TryCreatePipeServer(pipeName, pipeServer).Should.BeTrue() + Using pipeServer + pipeServer.CanRead.Should.BeTrue() + pipeServer.CanSeek.Should.BeFalse() + pipeServer.CanWrite.Should.BeFalse() + pipeServer.TransmissionMode.Should.Be(PipeTransmissionMode.Byte) + End Using + End Sub + + + Public Sub TryCreatePipeServerTwiceTests_Fail() + Dim pipeName As String = GetUniqueText() + Dim pipeServer As NamedPipeServerStream = Nothing + TryCreatePipeServer(pipeName, pipeServer).Should.BeTrue() + Using pipeServer + Dim pipeServer1 As NamedPipeServerStream = Nothing + TryCreatePipeServer(pipeName, pipeServer1).Should.BeFalse() + pipeServer1.Should.BeNull() + End Using + End Sub + + + Public Async Function WaitForClientConnectionsAsyncTests() As Task + Dim pipeName As String = GetUniqueText() + Dim pipeServer As NamedPipeServerStream = Nothing + + If TryCreatePipeServer(pipeName, pipeServer) Then + + Using pipeServer + Dim tokenSource As New CancellationTokenSource() + Dim commandLine As String() = {"Hello"} + Dim clientConnection As Task = WaitForClientConnectionsAsync( + pipeServer, + callback:=Sub(args As String()) + If args.Length = 1 Then + _resultArgs = commandLine + End If + End Sub, + cancellationToken:=tokenSource.Token) + + Dim awaitable As ConfiguredTaskAwaitable = SendSecondInstanceArgsAsync( + pipeName, + args:=commandLine, + cancellationToken:=tokenSource.Token) _ + .ConfigureAwait(continueOnCapturedContext:=False) + + awaitable.GetAwaiter().GetResult() + Dim CancelToken As New CancellationToken + Dim buffer As Byte() = New Byte(commandLine.Length) {} + Dim count As Integer = Await pipeServer.ReadAsync( + buffer:=buffer.AsMemory(start:=0, length:=commandLine.Length)) + + ' Ensure the result is set + Do + Await Task.Delay(5) + Loop Until _resultArgs IsNot Nothing + _resultArgs(0).Should.Be("Hello") + Await tokenSource.CancelAsync() + End Using + End If + End Function + + End Class +End Namespace