Добавление возможности паузы и продолжения в моем загрузчике

Я создаю загрузчик на С#. Я использую класс WebClient. Чтобы приостановить загрузку нажатием кнопки, я мог просто подумать об использовании Thread. Итак, когда я создал поток и прикрепил его к загрузке моего файла, как показано ниже

WebClient web = new WebLCient();
Thread dwnd_thread = new Thread(Program.web.DownloadFileAsync(new Uri(Program.src), Program.dest));

он дает мне следующие ошибки: «Лучший перегруженный метод, соответствующий« System.Threading.Thread.Thread (System.Threading.ThreadStart) », имеет некоторые недопустимые аргументы» и «Аргумент« 1 »: не может преобразовать из« пустоты »в« System.Threading.ThreadStart'".

Затем я подумал, что если я приостановлю свой основной поток системы, он может заблокировать весь мой процесс, который я использовал ниже строки кода.

System.Threading.Thread.Sleep(100);

но вообще ничего не делает. Может ли кто-нибудь сказать мне, что может быть лучшим подходом для приостановки/загрузки и как использовать поток для приостановки процесса загрузки.


person Community    schedule 14.04.2013    source источник


Ответы (4)


Поскольку не существует стандартного способа приостановить/возобновить запрос на загрузку, вам придется реализовать свой собственный механизм. Ниже приведен блок кода, содержащий пример того, как может выглядеть такой механизм. Класс FileDownload принимает 3 параметра:

  • source - ссылка на файл, который нужно скачать.

  • destination - куда сохранить файл.

  • chunkSize - сколько байт читать, перед проверкой приостановить или продолжить загрузку.


public class FileDownload
{
    private volatile bool _allowedToRun;
    private string _source;
    private string _destination;
    private int _chunkSize;

    private Lazy<int> _contentLength;

    public int BytesWritten { get; private set; }
    public int ContentLength { get { return _contentLength.Value; } }

    public bool Done { get { return ContentLength == BytesWritten; } }

    public FileDownload(string source, string destination, int chunkSize)
    {
        _allowedToRun = true;

        _source = source;
        _destination = destination;
        _chunkSize = chunkSize;
        _contentLength = new Lazy<int>(() => Convert.ToInt32(GetContentLength()));

        BytesWritten = 0;
    }

    private long GetContentLength()
    {
        var request = (HttpWebRequest)WebRequest.Create(_source);
        request.Method = "HEAD";

        using (var response = request.GetResponse())
            return response.ContentLength;
    }

    private async Task Start(int range)
    {
        if (!_allowedToRun)
            throw new InvalidOperationException();

        var request = (HttpWebRequest)WebRequest.Create(_source);
        request.Method = "GET";
        request.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)";
        request.AddRange(range);

        using (var response = await request.GetResponseAsync())
        {
            using (var responseStream = response.GetResponseStream())
            {
                using (var fs = new FileStream(_destination, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
                {
                    while (_allowedToRun)
                    {
                        var buffer = new byte[_chunkSize];
                        var bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length);

                        if (bytesRead == 0) break;

                        await fs.WriteAsync(buffer, 0, bytesRead);
                        BytesWritten += bytesRead;
                    }

                    await fs.FlushAsync();
                }
            }
        }
    }

    public Task Start()
    {
        _allowedToRun = true;
        return Start(BytesWritten);
    }

    public void Pause()
    {
        _allowedToRun = false;
    }
}

Использование:

static void Main(string[] args)
{
    var fw = new FileDownload("http://download.microsoft.com/download/E/E/2/EE2D29A1-2D5C-463C-B7F1-40E4170F5E2C/KinectSDK-v1.0-Setup.exe", @"D:\KinetSDK.exe", 5120);

    // Display progress...
    Task.Factory.StartNew(() =>
    {
        while (!fw.Done)
        {
            Console.SetCursorPosition(0, Console.CursorTop);
            Console.Write(string.Format("ContentLength: {0} | BytesWritten: {1}", fw.ContentLength, fw.BytesWritten));
        }
    });

    // Start the download...
    fw.Start();

    // Simulate pause...
    Thread.Sleep(500);
    fw.Pause();
    Thread.Sleep(2000);

    // Start the download from where we left, and when done print to console.
    fw.Start().ContinueWith(t => Console.WriteLine("Done"));

    Console.ReadKey();
}
person ebb    schedule 14.04.2013
comment
У меня проблема в том, что весь этот код реализован в .NET framework 4.0, а я использую .NET framework 2.0. Есть ли для него альтернатива? - person ; 15.04.2013
comment
Часть «Отображение хода выполнения» не работает. Любая помощь будет оценена по достоинству. - person Arnab Jain; 27.04.2016
comment
Хорошая реализация. Быстрый вопрос: не должна ли быть синхронизация на флаге _allowedToRun, чтобы предотвратить гонки? - person Andy; 11.07.2017
comment
@ebb Поскольку я могу добавить функцию «Отменить загрузку» в класс FileDownload, public void Cancel() { allowedToRun = false; ///??????? } не могли бы вы помочь мне? Спасибо ... - person J. Rodríguez; 31.03.2021

Я взял решение @ebb (прекрасно работает!) и немного адаптировал его. Это сейчас:

  • Может принимать поток в качестве входных данных
  • Можно сообщить о прогрессе через IProgress
  • Некоторые другие мелкие доработки
public class FileDownload
{
    private volatile bool _allowedToRun;
    private Stream _sourceStream;
    private string _sourceUrl;
    private string _destination;
    private bool _disposeOnCompletion;
    private int _chunkSize;
    private IProgress<double> _progress;
    private Lazy<long> _contentLength;

    public long BytesWritten { get; private set; }
    public long ContentLength { get { return _contentLength.Value; } }

    public bool Done { get { return ContentLength == BytesWritten; } }

    public FileDownload(Stream source, string destination, bool disposeOnCompletion = true, int chunkSizeInBytes = 10000 /*Default to 0.01 mb*/, IProgress<double> progress = null)
    {
        _allowedToRun = true;

        _sourceStream = source;
        _destination = destination;
        _disposeOnCompletion = disposeOnCompletion;
        _chunkSize = chunkSizeInBytes;
        _contentLength = new Lazy<int>(() => Convert.ToInt32(GetContentLength()));
        _progress = progress;

        BytesWritten = 0;
    }

    public FileDownload(string source, string destination, int chunkSizeInBytes = 10000 /*Default to 0.01 mb*/, IProgress<double> progress = null)
    {
        _allowedToRun = true;

        _sourceUrl = source;
        _destination = destination;
        _chunkSize = chunkSizeInBytes;
        _contentLength = new Lazy<int>(() => Convert.ToInt32(GetContentLength()));
        _progress = progress;

        BytesWritten = 0;
    }

    private long GetContentLength()
    {
        if (_sourceStream != null)
            return _sourceStream.Length;
        else
        {
            var request = (HttpWebRequest)WebRequest.Create(_sourceUrl);
            request.Method = "HEAD";

            using (var response = request.GetResponse())
                return response.ContentLength;
        }
    }

    private async Task Start(int range)
    {
        if (!_allowedToRun)
            throw new InvalidOperationException();

        if (_sourceStream != null)
        {
            using (var fs = new FileStream(_destination, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
            {
                while (_allowedToRun)
                {
                    var buffer = new byte[_chunkSize];
                    var bytesRead = await _sourceStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);

                    if (bytesRead == 0) break;

                    await fs.WriteAsync(buffer, 0, bytesRead);
                    BytesWritten += bytesRead;
                    _progress?.Report((double)BytesWritten / ContentLength);
                }

                await fs.FlushAsync();
            }

            //Control whether the stream should be disposed here or outside of this class
            if (BytesWritten == ContentLength && _disposeOnCompletion)
                _sourceStream?.Dispose();
        }
        else
        {
            var request = (HttpWebRequest)WebRequest.Create(_sourceUrl);
            request.Method = "GET";
            request.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)";
            request.AddRange(range);

            using (var response = await request.GetResponseAsync())
            {
                using (var responseStream = response.GetResponseStream())
                {
                    using (var fs = new FileStream(_destination, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
                    {
                        while (_allowedToRun)
                        {
                            var buffer = new byte[_chunkSize];
                            var bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);

                            if (bytesRead == 0) break;

                            await fs.WriteAsync(buffer, 0, bytesRead);
                            BytesWritten += bytesRead;
                            _progress?.Report((double)BytesWritten / ContentLength);
                        }

                        await fs.FlushAsync();
                    }
                }
            }
        }
    }

    public Task Start()
    {
        _allowedToRun = true;
        return Start(BytesWritten);
    }

    public void Pause()
    {
        _allowedToRun = false;
    }
}
person derekantrican    schedule 06.12.2019
comment
Хорошее решение, только одно: _contentLength = new Lazy‹int›(() =› Convert.ToInt32(GetContentLength())); произойдет сбой, если размер загружаемого файла превышает максимальное значение 32-битного двоичного целого числа со знаком (2147483647): 2 ГБ. Лучше использовать длинный ContentLength... - person bgcode; 27.05.2020
comment
Хорошо, но поскольку я могу добавить функцию отмены загрузки в FileDownload класс, public void Cancel() { allowedToRun = false; ///??????? } не могли бы вы помочь мне?, спасибо ... - person J. Rodríguez; 31.03.2021

к сожалению, WebClient не имеет возможности приостановить загрузку. Итак, вы должны использовать WebRequest в фоновом потоке и приостановить получение потока ответов с флагом. Вот пример кода. Но убедитесь, что вы не можете делать паузу без ограничений, потому что TCP-соединение будет закрыто, если в течение некоторого времени не будет передачи. Поэтому, если возобновить загрузку не удалось, вы должны снова перезапустить загрузку.

public class DownloadJob {

    public delegate void DownloadCompletedDelegate(Stream stream);
    //completion download  event
    public event DownloadCompletedDelegate OnDownloadCompleted;

    //sync object
    private object _lock = new object();
    //pause flag
    private bool _bPause = false;

    //Pause download
    public void Pause() {

        lock (_lock) {
            _bPause = true;
        }
    }

    //Resume download
    public void Resume() {

        lock (_lock) {
            _bPause = false;
        }
    }

    //Begin download with URI
    public void BeginDowload(Uri uri) {

        //Create Background thread
        Thread downLoadThread = new Thread(
            delegate() {

                WebRequest pWebReq = WebRequest.Create(uri);
                WebResponse pWebRes = pWebReq.GetResponse();

                using (MemoryStream pResultStream = new MemoryStream())
                using (Stream pWebStream = pWebRes.GetResponseStream()) {

                    byte[] buffer = new byte[256];

                    int readCount = 1;
                    while (readCount > 0) {

                        //Read download stream 
                        readCount = pWebStream.Read(buffer, 0, buffer.Length);
                        //Write to result MemoryStream
                        pResultStream.Write(buffer, 0, readCount);

                        //Waiting 100msec while _bPause is true
                        while (true) {

                            lock (_lock) {
                                if (_bPause == true) {
                                    Thread.Sleep(100);
                                }
                                else {
                                    break;
                                }
                            }
                        }

                        pResultStream.Flush();
                    }

                    //Fire Completion event
                    if (OnDownloadCompleted != null) {
                        OnDownloadCompleted(pResultStream);
                    }
                }
            }
        );

        //Start background thread job
        downLoadThread.Start();
    }
}
person Darksanta    schedule 14.04.2013

Я адаптировал его для работы с файлами > 2 ГБ и имеет дело с прерванной загрузкой, так что по каким-то причинам загрузка была остановлена ​​и вам нужно повторить попытку, при следующей попытке загрузки, вместо того, чтобы начинать снова с нуля, она будет продолжаться там, где она был остановлен. Все вышеперечисленные решения от @derekantrican, @ebb и меня будут работать только в том случае, если сервер поддерживает запросы Range. Возможно, вам придется сначала проверить, принимает ли сервер запросы диапазона, прежде чем приступить к этому решению. https://developer.mozilla.org/en-US/docs/Web/HTTP/Range_requests

 public class FileDownload
 {
    private volatile bool _allowedToRun;
    private readonly string _sourceUrl;
    private readonly string _destination;
    private readonly int _chunkSize;
    private readonly IProgress<double> _progress;
    private readonly Lazy<long> _contentLength;

    public long BytesWritten { get; private set; }
    public long ContentLength => _contentLength.Value;

    public bool Done => ContentLength == BytesWritten;

    public FileDownload(string source, string destination, int chunkSizeInBytes = 10000 /*Default to 0.01 mb*/, IProgress<double> progress = null)
    {
        if(string.IsNullOrEmpty(source))
            throw new ArgumentNullException("source is empty");
        if (string.IsNullOrEmpty(destination))
            throw new ArgumentNullException("destination is empty");

        _allowedToRun = true;
        _sourceUrl = source;
        _destination = destination;
        _chunkSize = chunkSizeInBytes;
        _contentLength = new Lazy<long>(GetContentLength);
        _progress = progress;

        if (!File.Exists(destination))
            BytesWritten = 0;
        else
        {
            try
            {
                BytesWritten = new FileInfo(destination).Length;
            }
            catch
            {
                BytesWritten = 0;
            }
        }
    }

    private long GetContentLength()
    {
        var request = (HttpWebRequest)WebRequest.Create(_sourceUrl);
        request.Method = "HEAD";

        using (var response = request.GetResponse())
            return response.ContentLength;
    }

    private async Task Start(long range)
    {
        if (!_allowedToRun)
            throw new InvalidOperationException();

        if(Done)
            //file has been found in folder destination and is already fully downloaded 
            return;

        var request = (HttpWebRequest)WebRequest.Create(_sourceUrl);
        request.Method = "GET";
        request.UserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.2; .NET CLR 1.0.3705;)";
        request.AddRange(range);

        using (var response = await request.GetResponseAsync())
        {
            using (var responseStream = response.GetResponseStream())
            {
                using (var fs = new FileStream(_destination, FileMode.Append, FileAccess.Write, FileShare.ReadWrite))
                {
                    while (_allowedToRun)
                    {
                        var buffer = new byte[_chunkSize];
                        var bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length).ConfigureAwait(false);

                        if (bytesRead == 0) break;

                        await fs.WriteAsync(buffer, 0, bytesRead);
                        BytesWritten += bytesRead;
                        _progress?.Report((double)BytesWritten / ContentLength);
                    }

                    await fs.FlushAsync();
                }
            }
        }
    }

    public Task Start()
    {
        _allowedToRun = true;
        return Start(BytesWritten);
    }

    public void Pause()
    {
        _allowedToRun = false;
    }
}
person bgcode    schedule 27.05.2020