【发布时间】:2015-07-31 17:37:00
【问题描述】:
我正在尝试使用 HTTP 将一个大文件 (>1GB) 从一台服务器下载到另一台服务器。为此,我正在并行发出 HTTP 范围请求。这让我可以并行下载文件。
保存到磁盘时,我会获取每个响应流,将同一个文件作为文件流打开,寻找我想要的范围然后写入。
但是,我发现除了一个响应流之外的所有响应流都会超时。 看起来磁盘 I/O 跟不上网络 I/O。但是,如果我做同样的事情,但让每个线程写入一个单独的文件,它就可以正常工作。
作为参考,这是我写入同一文件的代码:
int numberOfStreams = 4;
List<Tuple<int, int>> ranges = new List<Tuple<int, int>>();
string fileName = @"C:\MyCoolFile.txt";
//List populated here
Parallel.For(0, numberOfStreams, (index, state) =>
{
try
{
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create("Some URL");
using(Stream responseStream = webRequest.GetResponse().GetResponseStream())
{
using (FileStream fileStream = File.Open(fileName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write))
{
fileStream.Seek(ranges[index].Item1, SeekOrigin.Begin);
byte[] buffer = new byte[64 * 1024];
int bytesRead;
while ((bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0)
{
if (state.IsStopped)
{
return;
}
fileStream.Write(buffer, 0, bytesRead);
}
}
};
}
catch (Exception e)
{
exception = e;
state.Stop();
}
});
这是写入多个文件的代码:
int numberOfStreams = 4;
List<Tuple<int, int>> ranges = new List<Tuple<int, int>>();
string fileName = @"C:\MyCoolFile.txt";
//List populated here
Parallel.For(0, numberOfStreams, (index, state) =>
{
try
{
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create("Some URL");
using(Stream responseStream = webRequest.GetResponse().GetResponseStream())
{
using (FileStream fileStream = File.Open(fileName + "." + index + ".tmp", FileMode.OpenOrCreate, FileAccess.Write, FileShare.Write))
{
fileStream.Seek(ranges[index].Item1, SeekOrigin.Begin);
byte[] buffer = new byte[64 * 1024];
int bytesRead;
while ((bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0)
{
if (state.IsStopped)
{
return;
}
fileStream.Write(buffer, 0, bytesRead);
}
}
};
}
catch (Exception e)
{
exception = e;
state.Stop();
}
});
我的问题是,C#/Windows 在从多个线程写入单个文件时是否会采取一些额外的检查/操作,这会导致文件 I/O 比写入多个文件时慢?所有磁盘操作都应该受磁盘速度的约束吗?谁能解释这种行为?
提前致谢!
更新:这是源服务器抛出的错误:
“无法将数据写入传输连接:连接尝试失败,因为连接方在一段时间后没有正确响应,或者建立连接失败,因为连接的主机没有响应。” [System.IO.IOException]:“无法将数据写入传输连接:连接尝试失败,因为连接方在一段时间后没有正确响应,或者连接失败,因为连接的主机没有响应。” InnerException:“连接尝试失败,因为连接方在一段时间后没有正确响应,或者建立连接失败,因为连接主机没有响应” 消息:“无法将数据写入传输连接:连接尝试失败,因为连接方在一段时间后没有正确响应,或者建立连接失败,因为连接的主机没有响应。” StackTrace: " 在 System.Net.Sockets.NetworkStream.Write(Byte[] 缓冲区,Int32 偏移量,Int32 大小)\r\n 在 System.Net.Security._SslStream.StartWriting(Byte[] 缓冲区,Int32 偏移量,Int32 计数, AsyncProtocolRequest asyncRequest)\r\n 在 System.Net.Security._SslStream.ProcessWrite(Byte[] 缓冲区, Int32 偏移量, Int32 计数, AsyncProtocolRequest asyncRequest)\r\n 在 System.Net.Security.SslStream.Write(Byte[ ] 缓冲区,Int32 偏移量,Int32 计数)\r\n
【问题讨论】:
-
我能看到的唯一可能导致对单个文件的写入变得和/或出现缓慢的情况是,您没有在每次调用
fileStream.Write(buffer, 0, bytesRead);后刷新文件 -
不要在每个线程中打开同一个文件。打开一次并使用单个实例(确保多个线程不会同时写入 - 您可以使用 lock )
-
这应该可以。发布异常 ToString。网络有多快,你的超时有多大? (注意,Parallel.For 不适合,因为它使用了无法控制的并行度。您只能指定最大值。)
-
@usr 我从中获取文件的服务器是引发异常的原因。它指出“SocketException:连接尝试失败,因为连接方在一段时间后没有正确响应,或者由于连接的主机未能响应而建立的连接失败”。我认为这意味着接收器没有足够快地从流中读取字节。
-
@shortspider 这通常意味着您尝试连接到不存在的机器。 (如果它存在,你会在很短的时间内得到一个成功的连接或一种 connection denied 消息)
标签: c# multithreading file io parallel.for