【问题标题】:Why can't I split download more than 2 pieces?为什么我不能拆分下载超过 2 件?
【发布时间】:2016-01-09 00:54:20
【问题描述】:

我正在开发一个能够将文件拆分并下载为多个部分的下载器,但目前存在一个问题,即如果我尝试超过 2 个部分,则输出文件已损坏。我不知道发生了什么,但我认为它可能发生在它正在寻找单个尺寸的部分。

有相当多的代码,所以我决定将其发布到网站之外。代码可以在这里找到:Github

非常感谢您在此问题上提供的任何帮助。谢谢!

【问题讨论】:

  • 我运行程序并在执行 fs.Position = Start; 的行(第 69 行)中收到错误。这是你遇到的吗?
  • +Ian No. 当我运行程序时,我没有收到任何错误。但每次我尝试下载超过 2 个部分的图像时,它们最终都会损坏。
  • “尝试下载两个以上的部分”您如何以编程方式执行此操作?

标签: c# .net networking download


【解决方案1】:

好的,所以您的代码中存在许多问题,真正的问题不是您将文件分成超过 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;
        }
    }
}

【讨论】:

  • "如果你取消睡眠,文件将按块的随机顺序写入" fs.Position = Start 不会设置文件中写入卡盘的位置吗?那么如果没有睡眠,这怎么会乱写呢?
  • 我修改了我的答案并发布了一个新的解决方案。我同意 fs.Position 应该设置位置,但在我的测试中似乎它不是那样工作的。还有更大的问题需要解决,例如 GetResponseAsync 会在没有睡眠的情况下使进程死锁,所以我之前的回答不会让你 100% 顺利完成。我认为您可以接受我的新答案并使用它。
  • 感谢您的帮助,汤姆成功了!你有什么资源可以让我了解更多关于 async/await 的信息吗?
  • 如果你喜欢书,也许可以看看这本书......它似乎是权威。 shop.oreilly.com/product/…
猜你喜欢
  • 1970-01-01
  • 2014-12-01
  • 2011-12-29
  • 2022-01-10
  • 1970-01-01
  • 1970-01-01
  • 2016-05-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多