[英]How can I return a stream in a WCF response asynchronously from a task?
我有一个类,该类对大量数据进行长时间运行的处理,并将输出写入我提供的流中。 我正在尝试将WCF放在此前面(使用命名管道),但是在弄清楚如何返回流时遇到了麻烦。 到目前为止,我有这样的事情:
interface IProcessor { Stream GetStream(); }
class Host {
static void Main(string[] args) {
using (ServiceHost sh = new ServiceHost(typeof(Processor), new Uri[]{new Uri("net.pipe://localhost")})) {
var binding = new NetNamedPipeBinding();
binding.TransferMode = TransferMode.StreamedResponse;
sh.AddServiceEndpoint(typeof(IProcessor), binding, "foo");
sh.Open();
Console.WriteLine("Waiting...");
Console.ReadLine();
sh.Close();
}
}
}
class Processor : IProcessor {
Stream GetStream() {
var SourceProcessor = new SourceProcessor(...);
var s = new MemoryStream();
new Task(() => { SourceProcessor.Run(s); }).Start();
return s;
}
}
class Client {
static void Main(string[] args) {
Console.WriteLine("Starting...");
var binding = new NetNamedPipeBinding();
binding.TransferMode = TransferMode.StreamedResponse;
ChannelFactory<IProcessor> f = new ChannelFactory<IProcessor>(binding, new EndpointAddress("net.pipe://localhost/foo"));
Console.WriteLine("Creating channel...");
IProcessor eps = f.CreateChannel();
Console.WriteLine("Getting stream.");
Stream s = eps.GetStream();
StreamReader sr = new StreamReader(s);
while (!sr.EndOfStream) Console.WriteLine(sr.ReadLine());
Console.ReadLine();
}
}
一切都会进行,但当然没有源数据可以传递给客户端。 我对如何做到这一点感到困惑(也许我做不到),因为我既需要返回流并运行任务,又可能等待任务完成。 如果我不调用任务就直接调用SourceProcessor.Run,它会阻塞和缓冲,但是,我不确定如何等到任务完成后再返回流以供客户端读取...
问题是,如果WCF调用Read(
且调用返回0字节,WCF会认为流“完成” MemoryStream
会很乐意这样做,如果没有可用数据,它将不阻止读取。
问题的根源是WCF正在读取MemoryStream
速度比您编写它的速度快,并认为它已“完成”,解决该问题的方法是,您需要返回阻塞的另一种类型的Stream
,而不是在出现这种情况时返回0没有可用数据。 .NET没有内置可实现此目的的东西,您将需要找到一个第三方类或创建自己的类(它可能很简单,就像从MemoryStream派生并覆盖Read
以阻止读取,直到出现“ Done”标志为止设置(有关类似行为,请参见BlockingCollection<T>
及其CompleteAdding()
方法)。
出于乐趣,我将其组合在一起,虽然未经测试,但可以满足您的需要。
using System;
using System.Collections.Concurrent;
using System.IO;
namespace Example
{
public class BufferStream : Stream
{
public BufferStream()
{
_data = new BlockingCollection<byte[]>();
}
/// <param name="boundedCapacity">The maximum number of calls to <see cref="Write"/> that can be made without
/// the buffer being drained.</param>
public BufferStream(int boundedCapacity)
{
_data = new BlockingCollection<byte[]>(boundedCapacity);
}
private readonly BlockingCollection<byte[]> _data;
private byte[] _currentBlock = null;
private int _currentBlockIndex = 0;
public int BoundedCapacity { get { return _data.BoundedCapacity; } }
public int BufferedWrites { get { return _data.Count; } }
public bool IsAddingCompleted
{
get { return _data.IsAddingCompleted; }
}
public bool IsCompleted
{
get { return _data.IsCompleted; }
}
public void CompleteAdding()
{
_data.CompleteAdding();
}
public override void Write(byte[] buffer, int offset, int count)
{
var localArray = new byte[count];
//Copy the data in to a new buffer of exactly the count size.
Array.Copy(buffer, offset, localArray, 0, count);
_data.Add(localArray);
}
public override int Read(byte[] buffer, int offset, int count)
{
if (_currentBlock == null || _currentBlockIndex == _currentBlock.Length)
{
if (!GetNextBlock())
return 0;
}
int minCount = Math.Min(count, _currentBlock.Length - _currentBlockIndex);
Array.Copy(_currentBlock, _currentBlockIndex, buffer, offset, minCount);
_currentBlockIndex += minCount;
return minCount;
}
/// <summary>
/// Loads the next block in to <see cref="_currentBlock"/>.
/// </summary>
/// <returns>True if the next block was retrieved.</returns>
private bool GetNextBlock()
{
if (!_data.TryTake(out _currentBlock))
{
//The TryTake failed, the collection is empty.
//See if we are in the completed state.
if (_data.IsCompleted)
{
return false;
}
//Wait for more data to show up.
try
{
_currentBlock = _data.Take();
}
catch (InvalidOperationException)
{
//If the blocking collection was marked complete while we where waiting Take throws a InvalidOperationException
return false;
}
}
_currentBlockIndex = 0;
return true;
}
#region Constant functions
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return true; }
}
public override void Flush()
{
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override long Length
{
get { throw new NotSupportedException(); }
}
public override long Position
{
get { throw new NotSupportedException(); }
set { throw new NotSupportedException(); }
}
#endregion
}
}
这种从MemoryStream
派生的优点是,一旦WCF读取了一个值,它就不再需要保留在内存中(返回Stream
而不是byte[]
的整个过程)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.