写入具有多个流的文件C#

写入具有多个流的文件C#,c#,multithreading,file,io,parallel.for,C#,Multithreading,File,Io,Parallel.for,我正在尝试使用HTTP从一台服务器下载一个大文件(>1GB)。为此,我并行地发出HTTP范围请求。这让我可以并行下载该文件 当保存到磁盘时,我会获取每个响应流,打开与文件流相同的文件,寻找我想要的范围,然后写入 然而,我发现除了一个响应流外,所有响应流都超时了。看起来磁盘I/O跟不上网络I/O。但是,如果我做同样的事情,但让每个线程写入一个单独的文件,它就可以正常工作 以下是我在同一文件中编写的代码,以供参考: int numberOfStreams = 4; List<Tuple<

我正在尝试使用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;
列表范围=新列表();
字符串文件名=@“C:\mycolfile.txt”;
//此处填充的列表
并行。对于(0,numberOfStreams,(索引,状态)=>
{
尝试
{
HttpWebRequest webRequest=(HttpWebRequest)webRequest.Create(“某些URL”);
使用(Stream responseStream=webRequest.GetResponse().GetResponseStream())
{
使用(FileStream FileStream=File.Open(fileName,FileMode.OpenOrCreate,FileAccess.Write,FileShare.Write))
{
Seek(范围[index].Item1,SeekOrigin.Begin);
字节[]缓冲区=新字节[64*1024];
int字节读取;
而((bytesRead=responseStream.Read(buffer,0,buffer.Length))>0)
{
如果(state.iss)
{
返回;
}
写入(缓冲区,0,字节读取);
}
}
};
}
捕获(例外e)
{
例外=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();
    }
});
int numberOfStreams=4;
列表范围=新列表();
字符串文件名=@“C:\mycolfile.txt”;
//此处填充的列表
并行。对于(0,numberOfStreams,(索引,状态)=>
{
尝试
{
HttpWebRequest webRequest=(HttpWebRequest)webRequest.Create(“某些URL”);
使用(Stream responseStream=webRequest.GetResponse().GetResponseStream())
{
使用(FileStream FileStream=File.Open(fileName+“+index+”.tmp”、FileMode.OpenOrCreate、FileAccess.Write、FileShare.Write))
{
Seek(范围[index].Item1,SeekOrigin.Begin);
字节[]缓冲区=新字节[64*1024];
int字节读取;
而((bytesRead=responseStream.Read(buffer,0,buffer.Length))>0)
{
如果(state.iss)
{
返回;
}
写入(缓冲区,0,字节读取);
}
}
};
}
捕获(例外e)
{
例外=e;
state.Stop();
}
});
我的问题是,C#/Windows在从多个线程写入单个文件时是否会执行一些额外的检查/操作,从而导致文件I/O速度比写入多个文件时慢?所有磁盘操作都应该由磁盘速度绑定,对吗?有人能解释这种行为吗

提前谢谢

更新:以下是源服务器引发的错误:

无法将数据写入传输连接:连接尝试失败,因为连接方在一段时间后没有正确响应,或者建立的连接失败,因为连接的主机没有响应 [System.IO.IOException]:“无法将数据写入传输连接:连接尝试失败,因为连接方在一段时间后没有正确响应,或者建立的连接失败,因为连接的主机没有响应。” InnerException:“连接尝试失败,因为连接方在一段时间后没有正确响应,或者建立的连接失败,因为连接的主机没有响应” 消息:“无法将数据写入传输连接:连接尝试失败,因为连接方在一段时间后没有正确响应,或者建立的连接失败,因为连接的主机没有响应。”
StackTrace:“在System.Net.Sockets.NetworkStream.Write(字节[]缓冲区,Int32偏移量,Int32大小)\r\n在System.Net.Security.\u ssStream.StartWriting(字节[]缓冲区,Int32偏移量,Int32计数,AsyncProtocolRequest异步请求)\r\n在System.Net.Security.\u ssStream.ProcessWrite(字节[])缓冲区,Int32偏移量,Int32计数,AsyncProtocolRequest异步请求)\r\n位于System.Net.Security.SslStream.Write(字节[]缓冲区,Int32偏移量,Int32计数)\r\n

除非您正在写入分条RAID,否则您不太可能通过同时从多个线程写入文件来获得性能优势。事实上,更可能的情况是相反的–并发写入将被交错并导致随机访问,从而导致磁盘寻道延迟,从而使它们产生数量级的slo比大的顺序写入更有效

要获得透视感,请看一些。从磁盘连续读取1 MB需要20毫秒;写入大约需要相同的时间。另一方面,每个磁盘寻道大约需要10毫秒。如果您的写入以4 KB块进行交错,那么您的1 MB写入将需要额外的2560毫秒寻道时间,使其比sequenti慢100倍艾尔


我建议在任何时候只允许一个线程写入该文件,并且仅在网络传输时使用并行性。您可以使用生产者-消费者模式,其中下载的块被写入一个有界的并发集合(例如),然后通过专用线程将其提取并写入磁盘。

根据目前提供的信息,我猜:

在Windows上,当您写入扩展文件大小的位置时,Windows需要将之前的所有内容初始化为零
    fileStream.Seek(ranges[index].Item1, SeekOrigin.Begin);
using System;
using System.IO;
using System.Diagnostics;

class Program {
    static void Main(string[] args) {
        string path = @"c:\temp\test.bin";
        var fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Write);
        fs.Seek(1024L * 1024 * 1024, SeekOrigin.Begin);
        var buf = new byte[4096];
        var sw = Stopwatch.StartNew();
        fs.Write(buf, 0, buf.Length);
        sw.Stop();
        Console.WriteLine("Writing 4096 bytes took {0} milliseconds", sw.ElapsedMilliseconds);
        Console.ReadKey();
        fs.Close();
        File.Delete(path);
    }
}
Writing 4096 bytes took 1491 milliseconds
int numberOfStreams = 4;
List<Tuple<int, int>> ranges = new List<Tuple<int, int>>();
string fileName = @"C:\MyCoolFile.txt";
//Ranges list populated here
using (MemoryMappedFile mmf = MemoryMappedFile.CreateFromFile(fileName, FileMode.OpenOrCreate, null, fileSize.Value, MemoryMappedFileAccess.ReadWrite))
{
    Parallel.For(0, numberOfStreams, index =>
    {
        try
        {
            HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create("Some URL");
            using(Stream responseStream = webRequest.GetResponse().GetResponseStream())
            {
                using (MemoryMappedViewStream fileStream = mmf.CreateViewStream(ranges[index].Item1, ranges[index].Item2 - ranges[index].Item1 + 1, MemoryMappedFileAccess.Write))
                {
                    responseStream.CopyTo(fileStream);
                }
            };
        }
        catch (Exception e)
        {
            exception = e;
        }
    });
}