6

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

问题是,

如果 Stream 为时过早(不是 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:\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

CancellationTokenthrows时OperationCancelledException,所有三个 using 块都会尝试处理它们的资源。现在,当第二个 using 块尝试处理 时MessageBodyStream,它会挂起 10 分钟。但是如果流被完全读取,那么它会很快退出。

我怀疑,这与ReceiveTimeout10 分钟有关。所以我把它改成了 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...

我不相信一旦没有完全阅读它就不能取消流。另一方面,我不能只是忘记流并且不处理就让它离开。它必须是一个阻塞等待直到安全释放的调用。

有人可以帮我吗?

编辑

堆栈跟踪显示:

'                                                         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那么为什么显式关闭没有挂起?然后即使在流关闭时也再次挂起处置?

4

3 回答 3

2

澄清一下,实现Stream.Dispose()是调用Stream.Close(). 的基本实现Stream.Close()是调用Stream.Dispose(bool). 这与您通常如何实施的指导方针背道而驰IDisposable,因此值得注意。

MessageBodyStream.Close()方法被实现为首先关闭Message正在读取的,然后关闭XmlDictionaryReader与流关​​联的。

查看您的完整堆栈跟踪,问题似乎是该读者最终调用了SingletonConnectionReader.Close(TimeSpan). 这需要 aTimeSpan作为超时,这是您TimeoutException替换OperationCancelledException.

此方法尝试读取流的其余部分以完成关闭操作。我无法解释其背后的基本原理,但它是如何发生的。


要解决您的问题,您必须停止使用您的代理类。尽管如此IDisposable,在一个块中使用任何 WCF 代理都是不安全的using,因为调用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 开发人员,所以如果我的语法很糟糕,我深表歉意。

于 2013-10-16T17:03:24.030 回答
0

您可以考虑将大文件分块为较小的块(流)。您仍然会有延迟,但比您的方法要短得多。

于 2013-10-07T18:58:52.363 回答
0

这真的取决于“处置”功能计划的逻辑

dispose 函数可能意味着(看这里

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

我想所以 dispose 函数实现了 '1' 而 Close 函数实现了 '2'

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

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

于 2013-10-16T08:45:04.833 回答