How to stop reading from NetworkStream?
Asked Answered
G

2

3

I'm trying to read data from NetworkStream. I write next code:

Imports System.Net
Imports System.Net.Sockets

Public Class Form1
    Dim tcpclnt1 As New TcpClient
    Dim streamTcp1 As NetworkStream
    Dim DataBuffer(1024) As Byte    'Buffer for reading
    Dim numberOfBytes As Integer
    ' event for reading data from stream
    Dim evtDataArrival As New AsyncCallback(AddressOf DataProcessing) 

    Private Sub Btn_Connect5000_Click(sender As Object, e As EventArgs)_
            Handles Btn_Connect5000.Click
        ' Connecting to server
        tcpclnt1 = New TcpClient   
        tcpclnt1.Connect("192.168.1.177", 5000)
        streamTcp1 = tcpclnt1.GetStream()   'Create stream for current tcpClient
        ' HERE WE START TO READ
        streamTcp1.BeginRead(DataBuffer, 0, DataBuffer.Length, evtDataArrival, Nothing) 

    End Sub

    Public Sub DataProcessing(ByVal dr As IAsyncResult)
        numberOfBytes = streamTcp1.EndRead(dr) 'END READ
        ' ...HERE SOME ROUTINE FOR PRINT DATA TO TEXTBOXES...
        'START NEW READING 
        streamTcp1.BeginRead(DataBuffer, 0, DataBuffer.Length, evtDataArrival, Nothing)
    End Sub

    Private Sub Btn_Disconnect5000_Click(sender As Object, e As EventArgs)_
            Handles Btn_Disconnect5000.Click
        ' Disconnect from port 5000. Close TcpClient
        streamTcp1.Dispose()
        streamTcp1.EndRead(Nothing) 'And here mistake appears !!!
        streamTcp1.Close()
        tcpclnt1.Close()
    End Sub
End Class

Problem: I create new client and new stream. By using BeginRead as I understand it starts to read data in new thread. So to do it for real time data, I start new BeginRead at the end of DataProcessing function. But I face the problem when try to disconnect (look at Btn_Disconnect5000_Click function): I try to close stream and client but it sill try to read in DataProcessing method and says me:

Cannot access a disposed object

(Thanks to djv for correct translation!).

So I suppose I need to stop thread first but can't figure out how to do this: I tried Dispose() method, tried close stream first but still can't. Also I tried to call EndRead method manually, but can't understand what I have to assign to it as argument (parameter).

Giovanna answered 29/3, 2017 at 14:49 Comment(6)
Maybe this could help: #11786235Nabal
The English translation of your exception message is Cannot access a disposed objectIncompliant
As the answer in lotus's link suggest, try just closing the NetworkStream instead of calling EndRead (my answer somewhat explains why you don't need to call EndRead there either).Luba
I'm glad you ask TCP-related questions! It is one of my specialities but there are rarely any TCP questions in VB.NET, and the ones in C# are either questions I can't answer or questions where the OP has copy-pasted the entire application(s). ;)Luba
@VisualVincent I'm so glad too! Because usually the questions like this is a highly specialized questions and find the answerers is really tricky task :) I'm really grateful to you for your help! And I'm happy you find them interesting!Giovanna
@Incompliant Thank you for the correct translation! I edited the questionGiovanna
L
3

EndRead does not actually stop the asynchronous reading. It gets the amount of bytes read, ends the current read operation and throws any exceptions that might have occurred during the operation. Thus calling it will not stop further async operations from happening.

I suggest you insert a null check in the callback instead which will exit the method and stop BeginRead from being called even more.

Public Sub DataProcessing(ByVal dr As IAsyncResult)
    If streamTcp1 Is Nothing Then _
        Return 'Stream is disposed, stop any further reading.

    numberOfBytes = streamTcp1.EndRead(dr)

As the_lotus says you might also need to add exception handling, because if you are unlucky enough streamTcp1 might get disposed after passing the null check.

The reason why you should check the stream in the callback is because, as we discussed, after "closing" a TCP connection it enters CLOSE_WAIT, TIME_WAIT or one of the FIN_* states so that the OS can still map late/retransmitted packets to it.

NetworkStream.BeginRead() ultimately calls WSARecv which registers an asynchronous operation on the native side, thus it is not aware if your socket is disposed or not because all it cares about is if the connection is active (CLOSE_WAIT and TIME_WAIT connections are still considered active to the OS). This may cause the callback to be called even if the socket/stream is disposed.

Luba answered 29/3, 2017 at 15:18 Comment(7)
Is it possible that the stream could end between the If and the EndRead in rare cases? Seems like the only option is to catch the exception.Nabal
@Nabal : I don't think so, or it's at least not very likely... Isn't the callback raised in the current thread? Exception handling in there is good either way though. :)Luba
@Nabal : After testing it seems the callback it isn't raised in the same thread, so there could possibly be concurrency issues like you say, yes.Luba
Vincent, @Nabal - I tried this code: the problem is streamTcp1.Close() and tcpclnt1.Close() don't assign Nothing to my streamTcp1 variable! It still handle to NetworkStream, but some fields of this objects now changed. So it never Returns from this function. BUT: I tried just use streamTcp1 = Nothing in Btn_Disconnect5000_Click function and then it obviously works :) can I go this way? Or is using an exception is a better way?Giovanna
I suppose using streamTcp1 = Nothing is not really good way because I avoid streamTcp1.Close() then. So really stream is still opened. I think I will go for the exceptionGiovanna
@Giovanna : TcpClient.Close() will dispose the stream as well. -- Setting the stream to Nothing is actually a good idea since it helps indicating that it shouldn't be used any more. -- Exception handling should be done either way, getting unhandled exceptions in the released application isn't very popular. :)Luba
@Giovanna : Like I said EndRead also throws exceptions caused during reading, so you have every reason to handle exceptions in the callback.Luba
C
1

Just close the socket without call the EndRead.

At the moment that you are calling EndRead you are blocking the socket reading operation, as Microsoft's documentation says:

This method blocks until the I/O operation has completed.

Also you are calling the method Dispose, before the EndRead, which free all the resourses used by Stream and that is the reason that you have that exception.

Considere just close the socket, that will release all the resource that the socket is using, just like this:

Private Sub Btn_Disconnect5000_Click(sender As Object, e As EventArgs)_
    tcpclnt1.Close()
End Sub

UPGRADE: Considere catch the exception of EndRead that is into the method DataProcessing. At the moment at you are closing the socket a System.ObjectDisposedException will be raise and the exception message is Cannot access a disposed object.. It happens because you are blocking the socket in EndRead and at the moment that tcpclnt1.Close() is invoked it unblocked and release all the resourses of the socket (Disposed) and raise the exception.

So your DataProcessing should looks like:

Public Sub DataProcessing(ByVal dr As IAsyncResult)
    Try
        numberOfBytes = streamTcp1.EndRead(dr) 'END READ
        ' ...HERE SOME ROUTINE FOR PRINT DATA TO TEXTBOXES...
        'START NEW READING 
        streamTcp1.BeginRead(DataBuffer, 0, DataBuffer.Length, evtDataArrival, Nothing)
    Catch ex As Exception
        System.Console.WriteLine(ex.Message)
    End Try

End Sub
Countermine answered 29/3, 2017 at 18:58 Comment(3)
I deleted Dispose() and EndRead so now I'm using tcpclnt1.Close only but still got the same error: TcpClient already closed but it is still trying to call numberOfBytes = streamTcp1.EndRead(dr) inside of my DataProcessing function.Giovanna
@Giovanna I ran your code to repeat your problem and i made the upgrade of my answer. Also i only call the tcpclnt1.Close() to close the socket and release all the resource.Countermine
Yep, I solved this problem adding the exception, you are right! Thank you!Giovanna

© 2022 - 2024 — McMap. All rights reserved.