我正在使用WCF下载一个很长的文件。它是 net-tcp 绑定上的自托管服务。在客户端上,我正在读取流并将其写入后台线程中的磁盘。在 UI 上,有一个取消按钮。我使用CancellationToken
取消读写循环。
问题是,
如果流过早(不是 EOF(,则处理它需要很长时间。
在服务器 (C#( 上:
IO.Stream getFile(string filePath) {
return new IO.FileStream(filePath);
}
在客户端 (VB(:
using proxy as new ServiceReference1.TestServer
using wcfStrm = proxy.getFile("c:100MB.dat")
using fileStrm = new FileStream("d:destination100MB.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
当CancellationToken
抛出OperationCancelledException
时,所有三个使用块都试图处置他们的资源。现在,当第二个使用块尝试处理MessageBodyStream
时,它会挂起10分钟。但是,如果流被完全读取,那么它会很快退出。
我怀疑,这与10分钟的ReceiveTimeout
有关。 所以我把它改成了30秒和中提琴! 处置现在需要 30 秒。
还有一件事。Dispose
操作实际上超时。它吃掉了我的OperationCancelledException
,并发出TimeoutException
说The sockket transfer timed out after 00:00:00... bla bla bla
以下是堆栈跟踪
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...
我不相信 Once 不能在没有完全阅读的情况下取消流。另一方面,我不能只是忘记溪流,让它不处置。它必须是一个阻塞等待,直到安全释放的呼叫。
有人可以在这里帮我吗?
编辑
堆栈跟踪显示:
' this is interesting
at System.Xml.XmlBinaryReader.Close() ' VVVVVVVVVVVVV
at System.ServiceModel.Dispatcher.StreamFormatter.MessageBodyStream.Close()
at System.IO.Stream.Dispose()
所以我把使用块改为try-finally
块。 在那里我把wcfStrm.close
放在后面,然后是wcfStrm.Dispose
. 令我惊讶的是,关闭语句很快就过去了,处置超时了。现在,如果内部处置真正的罪魁祸首是Close
那么为什么显式关闭没有挂起?然后,即使溪流关闭,垃圾也再次挂起?
澄清一下,Stream.Dispose()
的实现是调用Stream.Close()
。Stream.Close()
的基本实现是调用Stream.Dispose(bool)
。这与通常如何实现IDisposable
的准则背道而驰,因此值得注意。
实现 MessageBodyStream.Close()
方法是为了首先关闭正在读取的Message
,然后关闭与流关联的XmlDictionaryReader
。
查看您的完整堆栈跟踪,问题似乎是该读取器最终调用SingletonConnectionReader.Close(TimeSpan)
。这需要TimeSpan
作为超时,这是您TimeoutException
替换OperationCancelledException
的来源。
此方法尝试读取流的其余部分以完成关闭操作。我无法解释这背后的理由,但它是如何的。
若要解决此问题,必须停止按原样使用代理类。尽管IDisposable
,但在using
块中使用任何WCF代理是不安全的,因为调用Dispose()
调用Close()
,并且在发生异常时,这不是您的意思。
在这种情况下,在代理上调用Abort()
将完全解决问题,因为这就是您的意思:中止操作。
using proxy as new ServiceReference1.TestServer
dim wcfStrm = proxy.getFile("c:100MB.dat")
try
using fileStrm = new FileStream("d:destination100MB.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
我不是 VB 开发人员,所以如果我的语法很糟糕,我深表歉意。
您可以考虑将大文件分块为较小的块(流(。你仍然会有延迟,但比你的方法短得多。
这实际上取决于"处置"函数的计划
逻辑处置功能可能意味着(看这里(
- 完成当前操作并释放资源
- 停止当前操作并释放资源
我想所以处置函数实现"1"和关闭函数实现"2">
您可以在反射或文档中检查这一点。
我没有检查它,因为我不知道"wcfStrm"是什么类型