I am using WCF to download a really long file. It is a self hosted service on net-tcp binding. On client i am reading the stream and writing it to a disk in a background thread. On UI, there is a cancel button. I cancel the read-write loop using a CancellationToken
.
Problem is,
if the Stream is premature (not EOF) then it takes too long to dispose it.
at Server (c#):
IO.Stream getFile(string filePath) {
return new IO.FileStream(filePath);
}
at client (vb):
using proxy as new ServiceReference1.TestServer
using wcfStrm = proxy.getFile("c:\100MB.dat")
using fileStrm = new FileStream("d:\destination\100MB.dat")
dim buff(256) as new Byte
while true
cancellationToken.ThrowIfCancellationRequested
Dim len = wcfStrm.Read(buff, 0, buff.Length)
if len > 0 then
fileStrm.write(buff, 0, len)
else
exit while
end if
end while
end using
end using ' <-------------- this hangs for 10Mins
end using
When the CancellationToken
throws OperationCancelledException
, all the three using blocks try to dispose their resources. Now when the second using block tries to dispose the MessageBodyStream
, then it hangs for 10 minutes. but if the stream is completely read, then it exits quickly.
i suspected, it had something to do with the ReceiveTimeout
of 10 mins. so i changed it to 30 seconds and viola! the dispose now takes 30 seconds.
One more thing. The Dispose
operation actually timeouts. It eats my OperationCancelledException
and produces a TimeoutException
saying The sockket transfer timed out after 00:00:00... bla bla bla
following is the stack trace
System.TimeoutException: The socket transfer timed out after 00:00:00. You have exceeded the timeout set on your binding. The time allotted to this operation may have been a portion of a longer timeout.
at System.ServiceModel.Channels.SocketConnection.SetReadTimeout(TimeSpan timeout, Boolean synchronous, Boolean closing)
at System.ServiceModel.Channels.SocketConnection.ReadCore(Byte[] buffer, Int32 offset, Int32 size, TimeSpan timeout, Boolean closing)
at System.ServiceModel.Channels.SocketConnection.Read(Byte[] buffer, Int32 offset, Int32 size, TimeSpan timeout)
at System.ServiceModel.Channels.DelegatingConnection.Read(Byte[] buffer, Int32 offset, Int32 size, TimeSpan timeout)
at System.ServiceModel.Channels.PreReadConnection.Read(Byte[] buffer, Int32 offset, Int32 size, TimeSpan timeout)
at System.ServiceModel.Channels.SingletonConnectionReader.SingletonInputConnectionStream.ReadCore(Byte[] buffer, Int32 offset, Int32 count)
at System.ServiceModel.Channels.SingletonConnectionReader.SingletonInputConnectionStream.Read(Byte[] buffer, Int32 offset, Int32 count)
at System.ServiceModel.Channels.MaxMessageSizeStream.Read(Byte[] buffer, Int32 offset, Int32 count)
at System.ServiceModel.Channels.SingletonConnectionReader.Close(TimeSpan timeout)
at System.ServiceModel.Channels.SingletonConnectionReader.SingletonInputConnectionStream.Close()
at System.ServiceModel.Channels.DelegatingStream.Close()
at System.Xml.XmlBufferReader.Close()
at System.Xml.XmlBaseReader.Close()
at System.Xml.XmlBinaryReader.Close()
at System.ServiceModel.Dispatcher.StreamFormatter.MessageBodyStream.Close()
at System.IO.Stream.Dispose()
at ...somewhere in my code...
I am not convinced that once cannot cancel a stream without fully reading it. On the other hand I cannot just forget the stream and let it go without disposing. It has to be a blocking wait-until-safely-released call.
Can someone please help me out here?
The stack trace shows:
' this is interesting
at System.Xml.XmlBinaryReader.Close() ' VVVVVVVVVVVVV
at System.ServiceModel.Dispatcher.StreamFormatter.MessageBodyStream.Close()
at System.IO.Stream.Dispose()
So i changed the using block to a try-finally
block. there i put wcfStrm.close
followed by wcfStrm.Dispose
. to my astonishment, the close statement passed swiftly and the dispose timed out. Now if inside dispose the actual culprit was Close
then why did explicit close didnt hang? and then again the dispose hanged even when the stream was closed?
To clarify, the implementation of Stream.Dispose()
is to call Stream.Close()
. The base implementation of Stream.Close()
is to call Stream.Dispose(bool)
. It's counter to the guidelines of how you would typically implement IDisposable
, so it's worth noting.
The MessageBodyStream.Close()
method is implemented to first close the Message
being read, then to close the XmlDictionaryReader
associated with the stream.
Looking at your full stack trace, the problem seems to be that this reader eventually calls into SingletonConnectionReader.Close(TimeSpan)
. This takes a TimeSpan
as a timeout, and this is the source of your TimeoutException
replacing the OperationCancelledException
.
This method attempts to read the rest of the stream to complete the close operation. I can't explain the rationale behind that, but it how it is.
To fix your problem, you must stop using your proxy class as you are. Despite being IDisposable
, it's not safe to use any WCF proxy in a using
block, because calling Dispose()
calls Close()
, and in the event of an exception, that's not what you mean.
In this case, calling Abort()
on the proxy will fix the problem completely, because it's what you mean: abort the operation.
using proxy as new ServiceReference1.TestServer
dim wcfStrm = proxy.getFile("c:\100MB.dat")
try
using fileStrm = new FileStream("d:\destination\100MB.dat")
dim buff(256) as new Byte
while true
cancellationToken.ThrowIfCancellationRequested
Dim len = wcfStrm.Read(buff, 0, buff.Length)
if len > 0 then
fileStrm.write(buff, 0, len)
else
exit while
end if
end while
end using
end try
catch
proxy.Abort()
end catch
finally
wcfStrm.Dispose()
end finally
end using
I'm not a VB developer, so I apologise if my syntax is terrible.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With