private async Task StartImpl()
        {
            _cancellationTokenSource = new CancellationTokenSource();
            var cancellationToken = _cancellationTokenSource.Token;

            Exception exRoot = null;

            //download all remaiing parts
            var ranges = DownloadedRanges.Where(x => !x.IsDone);

            await ForEachAsync(ranges, async range =>
            {
                Exception ex2 = null;
                for (var i = 0; i < MaxRetyCount + 1 && !_cancellationTokenSource.IsCancellationRequested; i++)  // retry
                {
                    try
                    {
                        await DownloadPart(range, cancellationToken).ConfigureAwait(true);

                        exRoot = null;
                        return;  // finished
                    }
                    catch (Exception ex)
                    {
                        ex2 = ex;
                    }
                }

                // just first exception treated as the exception; next exceptions such as CancelOperationException should be ignored
                lock (_monitor)
                {
                    if (exRoot == null)
                    {
                        exRoot = ex2;
                    }

                    // cancel other jobs
                    _cancellationTokenSource.Cancel();  // cancel all other parts
                }
            }, MaxPartCount, _cancellationTokenSource.Token).ConfigureAwait(false);

            // release _cancellationTokenSource
            _cancellationTokenSource.Dispose();
            _cancellationTokenSource = null;

            if (exRoot != null)
            {
                throw exRoot;
            }
        }
        private async Task Init3()
        {
            try
            {
                //make sure stream is created
                GetStream();

                // download the header
                using var httpClient     = new HttpClient();
                using var requestMessage = new HttpRequestMessage(HttpMethod.Head, Uri);
                var response = await httpClient.SendAsync(requestMessage, _cancellationTokenSource.Token).ConfigureAwait(false);

                if (response.StatusCode != HttpStatusCode.OK)
                {
                    throw new Exception($"StatusCode is {response.StatusCode}");
                }

                IsResumingSupported = AllowResuming && response.Headers.AcceptRanges.Contains("bytes");
                TotalSize           = response.Content.Headers.ContentLength ?? 0;
                if (response.Content.Headers.ContentLength == null)
                {
                    throw new Exception($"Could not retrieve the stream size: {Uri}");
                }

                // create new download range if previous size is different
                if (DownloadedRanges.Length == 0 || DownloadedRanges.Sum(x => x.To - x.From + 1) != TotalSize)
                {
                    DownloadedRanges = BuildDownloadRanges(TotalSize, IsResumingSupported && MaxPartCount >= 2 ? PartSize : TotalSize);
                }

                // finish initializing
                State = DownloadState.Initialized;
            }
            catch (Exception ex)
            {
                SetLastError(ex);
                throw;
            }
            finally
            {
                _cancellationTokenSource.Dispose();
                _cancellationTokenSource = null;
                _initTask.SetResult(null);
                lock (_monitorState)
                {
                    _initTask = null;
                }
            }
        }
        public Downloader(DownloaderOptions options)
        {
            if (options is null)
            {
                throw new ArgumentNullException(nameof(options));
            }
            if (options.PartSize < 10000)
            {
                throw new ArgumentException("PartSize parameter must be equals or greater than 10000", nameof(options.PartSize));
            }

            Uri               = options.Uri;
            _stream           = options.Stream;
            _writeBufferSize  = options.WriteBufferSize;
            DownloadedRanges  = options.DownloadedRanges ?? Array.Empty <DownloadRange>();
            MaxPartCount      = options.MaxPartCount;
            MaxRetyCount      = options.MaxRetryCount;
            PartSize          = options.PartSize;
            State             = options.IsStopped ? DownloadState.Stopped : DownloadState.None;
            AutoDisposeStream = options.AutoDisposeStream;
            AllowResuming     = options.AllowResuming;
            TotalSize         = options.DownloadedRanges != null && options.DownloadedRanges.Length > 0 ? DownloadedRanges.Sum(x => x.To - x.From + 1) : 0;
        }