我有一个 Restful Web 服务,其中包含一个返回流的方法,调用此方法会导致下载与流关联的文件。
现在,出于表演目的,我需要在创建该文件时下载该文件。这是该方法的实际代码:
[WebGet(UriTemplate = "SendFileStream/")]
public Stream SendFileStream()
{
//This thread generates a big file
FileCreatorThread fileCreator = new FileCreatorThread();
Thread fileCreatorThread = new Thread(fileCreator.CreateFile);
fileCreatorThread.Start();
WebOperationContext.Current.OutgoingResponse.Headers.Add(HttpResponseHeader.Expires, DateTime.UtcNow.ToString("ddd, dd MMM yyyy HH:mm:ss 'GMT'"));
WebOperationContext.Current.OutgoingResponse.ContentType = "multipart/related";
FileStream fs = new FileStream(@"c:\test.txt", FileMode.Open, FileAccess.Read, FileShare.Write);
return fs;
}
此方法效果很好,文件在创建时可下载,而不会引发 IOException。但问题是下载速度比创建速度快,并且一旦到达流的末尾就停止,而不下载仍然必须创建的部分。
所以我的问题是,有没有办法让下载保持挂起直到文件创建结束(假设我们事先不知道这个文件的最终长度)而不需要第二种方法来检查下载文件的大小是否与实际文件的长度相同,以及重新开始下载直到完成的方法?
在此先感谢您的帮助。
PS:对于那些想知道如何使用两个不同的线程读写访问文件的人,这里是生成文件的线程的代码。
public class FileCreatorThread
{
public void CreateFile()
{
FileStream fs = new FileStream(@"c:\test.txt", FileMode.Create, FileAccess.Write, FileShare.Read);
StreamWriter writer = new StreamWriter(fs);
for (int i = 0; i < 1000000; i++)
{
writer.WriteLine("New line number " + i);
writer.Flush();
//Thread.Sleep(1);
}
writer.Close();
fs.Close();
}
}
解决方案: 如果最终找到解决我的问题的方法,主要基于这两个页面:
首先,我在网络服务中启用了流媒体。这是来自 web.config 文件的代码片段,有关更多信息,请参阅上面的链接
<bindings>
<webHttpBinding>
<binding name="httpsStream" transferMode="Streamed" maxReceivedMessageSize="67108864">
<security mode="Transport"/>
</binding>
</webHttpBinding>
</bindings>
其次,我创建了一个带有 read() 方法重载的自定义流,该方法检查流的末尾是否已被重新调用,如果是,则等待几毫秒并重试 read() 以确保这确实是结束文件。
这是自定义流的代码:
public class BigFileStream : Stream
{
FileStream inStream;
FileStream testStream;
String filePath;
internal BigFileStream(string filePath)
{
this.filePath = filePath;
inStream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Write);
}
public override bool CanRead
{
get { return inStream.CanRead; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return false; }
}
public override void Flush()
{
throw new Exception("This stream does not support writing.");
}
public override long Length
{
get { throw new Exception("This stream does not support the Length property."); }
}
public override long Position
{
get
{
return inStream.Position;
}
set
{
throw new Exception("This stream does not support setting the Position property.");
}
}
public override int Read(byte[] buffer, int offset, int count)
{
int countRead = inStream.Read(buffer, offset, count);
if (countRead != 0)
{
return countRead;
}
else
{
for (int i = 1; i < 10; i++)
{
Thread.Sleep(i * 15);
countRead = inStream.Read(buffer, offset, count);
if (countRead != 0)
{
return countRead;
}
}
return countRead;
}
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new Exception("This stream does not support seeking.");
}
public override void SetLength(long value)
{
throw new Exception("This stream does not support setting the Length.");
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new Exception("This stream does not support writing.");
}
public override void Close()
{
inStream.Close();
base.Close();
}
protected override void Dispose(bool disposing)
{
inStream.Dispose();
base.Dispose(disposing);
}
}
下载方法现在将返回:
return new BigFileStream(@"c:\test.txt");
这可能不是更清洁的方式或最有效的方式,所以请不要犹豫,评论和建议其他解决方案。
更新 2:
这是 Read() 方法的更高效版本,因为如果作者花费超过 815 毫秒来写入更多字节,我第一次发布的方法仍然可能失败。
public override int Read(byte[] buffer, int offset, int count)
{
int countRead = inStream.Read(buffer, offset, count);
if (countRead != 0)
{
return countRead;
}
else
{
Boolean fileAccessible = false;
while (!fileAccessible)
{
try
{
//try to open the file in Write, if it goes in exception, that means that the file is still opened by the writer
testStream = new FileStream(this.filePath, FileMode.Open, FileAccess.Write, FileShare.Read);
testStream.Close();
break;
}
catch (Exception e)
{
Thread.Sleep(500);
countRead = inStream.Read(buffer, offset, count);
if (countRead != 0)
{
return countRead;
}
}
}
countRead = inStream.Read(buffer, offset, count);
return countRead;
}
}