C# 同时从不同客户端流式传输大数据

C# 同时从不同客户端流式传输大数据,c#,multithreading,asynchronous,streaming,task-parallel-library,C#,Multithreading,Asynchronous,Streaming,Task Parallel Library,这是一个bit架构和代码问题。我有很多源url,其中包含来自许多不同客户端的巨大文件,我必须下载这些文件并保存在文件系统中 我对RAM有硬件限制。因此,我想以字节块的形式缓冲每个流,并且我认为为流的每次下载启动一个线程是个好主意 我添加了使用任务并行库启动线程/任务的代码,如下所示: public Task RunTask(Action action) { Task task = Task.Run(action); return task; } 我通过以下方法传递动作参数:

这是一个bit架构和代码问题。我有很多源url,其中包含来自许多不同客户端的巨大文件,我必须下载这些文件并保存在文件系统中

我对RAM有硬件限制。因此,我想以字节块的形式缓冲每个流,并且我认为为流的每次下载启动一个线程是个好主意

我添加了使用任务并行库启动线程/任务的代码,如下所示:

public Task RunTask(Action action)
{
    Task task = Task.Run(action);

    return task;
}
我通过以下方法传递动作参数:

public void DownloadFileThroughWebStream(WebClient webClient, Uri src, string dest, long buffersize)
{
    Stream stream = webClient.OpenRead(src);

    byte[] buffer = new byte[buffersize];
    int len;
    using (BufferedStream bufferedStream = new BufferedStream(stream))
    {
        using (FileStream fileStream = new FileStream(Path.GetFullPath(dest), FileMode.Create, FileAccess.Write))
        {
            while ((len = stream.Read(buffer, 0, buffer.Length)) > 0)
            {
                fileStream.Write(buffer, 0, len);
                fileStream.Flush();
            }
        }

    }
}
出于测试目的,我尝试通过为每个特定下载启动线程/任务,从http uri下载一些资源:

[Test]
public async Task DownloadSomeStream()
{
    Uri uri = new Uri("http://mirrors.standaloneinstaller.com/video-sample/metaxas-keller-Bell.mpeg");

    List<Uri> streams = new List<Uri> { uri, uri, uri};

    List<Task> tasks = new List<Task>();

    var path = "C:\\TMP\\";
    //Create task for each of the streams from uri
    int c = 1;
    foreach (var uri in streams)
    {
        WebClient webClient = new WebClient();
        Task task = taskInitiator.RunTask(() => DownloadFileThroughWebStream(webClient, uri, Path.Combine(path,"File"+c), 8192));
        tasks.Add(task);
        c++;
    }
    Task allTasksHaveCompleted = Task.WhenAll(tasks);
    await allTasksHaveCompleted;
}
在线:

using (FileStream fileStream = new FileStream(Path.GetFullPath(dest), FileMode.Create, FileAccess.Write))
除了这个例外,有两件事我不明白:

System.IO.IOException: 'The process cannot access the file 'D:\TMP\File4' because it is being used by another process'
为什么不允许写?另一个进程是如何分配文件的

我只添加了3个url,所以我只应该有文件:file1、file2和file3,为什么它要保存file4

另外,还有其他一些问题,可以让我们思考一下:

就我想要实现的目标而言,我所做的是正确的吗?我使用任务并行库启动任务是否正确

有什么提示和技巧、最佳实践等吗


我们可以创建可以执行下载的下载方法:

async Task DownloadFile(string url, string location, string fileName)
{
    using (var client = new WebClient())
    {
        await client.DownloadFileTaskAsync(url, $"{location}{fileName}");
    }
}
并且上面的方法可以被Task调用。Run执行文件的同步下载:

IList<string> urls = new List<string>()
{
    @"http://mirrors.standaloneinstaller.com/video-sample/metaxas-keller-Bell.mpeg",
    @"https://...",
    @"https://..."
};

string location = "D:";
Directory.CreateDirectory(location);

Task.Run(async () =>
{
    var tasks = urls.Select(url => 
    {
        var fileName = url.Substring(url.LastIndexOf('/'));
        return DownloadFile(url, location, fileName);
    }).ToArray();
    await Task.WhenAll(tasks);
}).GetAwaiter().GetResult();
为什么不允许写?另一个过程是如何分配的 档案

文件未被其他进程锁定,而是被同一进程锁定。如果你打开一个文件进行写操作,你基本上会得到一个独占锁。当您尝试再次打开该文件以从另一个任务写入时,它将被锁定,这就是您出现错误的原因

要处理这种情况,您应该在将数据写入磁盘时设置一个锁。对于要写入的每个唯一文件名,都应该有一个单独的锁对象,并且要小心使用正确的锁

当我只添加了3个url时,为什么它要保存文件4,所以我只 应该有文件:file1、file2和file3

这是因为您在传递给Task.Run的委托中捕获了变量c。由于这些任务通常在循环结束后启动,因此c的值现在为4。有关闭包的更多信息,请参见