在WCF中过早释放MessageBodyStream

6

我正在使用WCF下载一个非常长的文件。它是在net-tcp绑定上自托管的服务。在客户端,我在后台线程中读取流并将其写入磁盘。在UI上,有一个取消按钮。我使用CancellationToken来取消读写循环。

问题是,如果流还没有到达结尾,也就是EOF(End of File),那么释放它需要太长时间。

在服务器端(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:\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

CancellationToken 抛出 OperationCancelledException 异常时,三个 using 代码块都会尝试释放它们的资源。当第二个 using 代码块尝试释放 MessageBodyStream 时,会挂起 10 分钟。但是如果流已经完全读取,则退出速度很快。
我怀疑这与 10 分钟的 ReceiveTimeout 有关。因此,我将其更改为 30 秒,然后神奇地,现在的处理只需要 30 秒。
还有一件事。清理操作实际上超时了。它吞掉了我的 OperationCancelledException 并产生一个 TimeoutException, 其中说“套接字传输在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...

我不相信一定要读完流才能取消它。另一方面,我也不能忘记这个流并让它自由释放,而不需要处理。必须进行阻塞的等待直到安全释放调用。

有人能帮我解决这个问题吗?

编辑

堆栈跟踪显示:

'                                                         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。令我惊讶的是,关闭语句迅速通过,而处理超时。如果在dispose内部实际的罪魁祸首是Close,那么为什么显式关闭没有挂起?然后再次关闭流,为什么处理会挂起?

3个回答

2
为了澄清,Stream.Dispose() 的实现是调用 Stream.Close()。而 Stream.Close() 的基本实现是调用 Stream.Dispose(bool)。这与您通常实现 IDisposable 的指南相违背,因此值得注意。 MessageBodyStream.Close() 方法的实现首先是关闭被读取的 Message,然后是关闭与流相关联的 XmlDictionaryReader
查看完整的堆栈跟踪,问题似乎是该读取器最终调用 SingletonConnectionReader.Close(TimeSpan)。这需要一个 TimeSpan 作为超时时间,这就是替换 OperationCancelledExceptionTimeoutException 的来源。
该方法尝试读取剩余部分的流以完成关闭操作。我无法解释其中的原理,但这就是它的工作方式。
要解决问题,您必须停止像您一样使用代理类。尽管是 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:\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

我不是VB开发人员,所以如果我的语法很糟糕,请谅解。


0

这真的取决于“Dispose”函数计划了什么逻辑

Dispose函数可以意味着(在这里查看)

  1. 完成当前操作并释放资源
  2. 停止当前操作并释放资源

我猜测Dispose函数实现了“1”,而Close函数实现了“2”

您可以在反射或文档中检查此内容。

我没有检查它,因为我不知道“wcfStrm”是什么类型


"wcfStrm" 是 "MessageBodyStream" 的一个实例。 - inquisitive

0
你可以考虑将大文件分成较小的块(流)进行处理。这样仍然会有延迟,但比你目前的方法要短得多。

是的,我确实考虑过这个问题,但我更感兴趣的是为什么我仍然会有延迟?我知道有一些解决方法,但想要了解确切的问题所在。 - inquisitive

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接