好的,所以您的代码中存在许多问题,真正的问题不是您将文件分成超过 2 个部分,而是当您将其分成 3 个或更多部分时,它会暴露代码中的竞争条件。
1) 您正试图从多个打开文件进行追加的线程写入同一个文件。每次打开文件,结尾都是一个移动的目标。
2) 即使解决了以文件为中心的问题,GetResponseAsync 仍会死锁,所以我改用 HttpClient,它可以异步工作,没有死锁问题。
3) 应用了 KISS 主体并简化了您的代码。它仍然会下载 .jpg 文件的片段,尽管我认为您做了一个很大的假设,认为这比仅在一个请求中下载整个文件要快。我会测试以确保您的程序在没有文件分块的情况下会简单得多。
4) 我忘了提到的另一件事是,您不能在命令行应用程序中拥有异步入口点,因此我添加了 Task.WaitAll 调用来修复如果不这样做可能会出现的死锁。阅读 [Why is AsyncContext needed when using async/await with a console application? 了解更多详情。
此代码每次都有效,不会崩溃,而且您可以执行任意数量的代码块。你欠我一杯啤酒:-)。
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace OctaneDownloadEngine
{
class Program
{
static void Main()
{
try
{
// have to use this because you can't have async entrypoint
Task.WaitAll(SplitDownload("http://www.hdwallpapers.in/walls/tree_snake_hd-wide.jpg", @"c:\temp\output.jpg"));
}
catch (Exception ex)
{
Console.Error.WriteLine(ex);
throw;
}
Console.ReadLine();
}
public static async Task<string> SplitDownload(string URL, string OUT)
{
var responseLength = WebRequest.Create(URL).GetResponse().ContentLength;
var partSize = (long)Math.Floor(responseLength / 4.00);
Console.WriteLine(responseLength.ToString(CultureInfo.InvariantCulture) + " TOTAL SIZE");
Console.WriteLine(partSize.ToString(CultureInfo.InvariantCulture) + " PART SIZE" + "\n");
var previous = 0;
var fs = new FileStream(OUT, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, (int)partSize);
try
{
fs.SetLength(responseLength);
List<Tuple<Task<byte[]>, int, int>> asyncTasks = new List<Tuple<Task<byte[]>, int, int>>();
for (var i = (int)partSize; i <= responseLength + partSize; i = (i + (int)partSize) + 1)
{
var previous2 = previous;
var i2 = i;
// GetResponseAsync deadlocks for some reason so switched to HttpClient instead
HttpClient client = new HttpClient() { MaxResponseContentBufferSize = 1000000 };
client.DefaultRequestHeaders.Range = new RangeHeaderValue(previous2, i2);
byte[] urlContents = await client.GetByteArrayAsync(URL);
// start each download task and keep track of them for later
Console.WriteLine("start {0},{1}", previous2, i2);
var downloadTask = client.GetByteArrayAsync(URL);
asyncTasks.Add(new Tuple<Task<byte[]>, int, int>(downloadTask, previous2, i2));
previous = i2;
}
// now that all the downloads are started, we can await the results
// loop through looking for a completed task in case they complete out of order
while (asyncTasks.Count > 0)
{
Tuple<Task<byte[]>, int, int> completedTask = null;
foreach (var task in asyncTasks)
{
// as each task completes write the data to the file
if (task.Item1.IsCompleted)
{
Console.WriteLine("await {0},{1}", task.Item2, task.Item3);
var array = await task.Item1;
Console.WriteLine("write to file {0},{1}", task.Item2, task.Item3);
fs.Position = task.Item2;
foreach (byte x in array)
{
if (fs.Position != task.Item3)
{
fs.WriteByte(x);
}
}
completedTask = task;
break;
}
}
asyncTasks.Remove(completedTask);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
finally
{
Console.WriteLine("close file");
fs.Close();
}
return OUT;
}
}
}