过早地在 WCF 中释放 MessageBodyStream



我正在使用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,并发出TimeoutExceptionThe 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. 停止当前操作并释放资源

我想所以处置函数实现"1"和关闭函数实现"2">

您可以在反射或文档中检查这一点。

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

相关内容

  • 没有找到相关文章

最新更新