<%@ Page Title="Parallel chunk concurrency" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" %> 
<asp:Content ID="TitleContent" ContentPlaceHolderID="TitleContent" runat="server">Parallel chunk tuning - AjaxUploader v5.3 Demos</asp:Content> 
<asp:Content ID="HeaderContent" ContentPlaceHolderID="HeaderTitle" runat="server">Parallel chunk concurrency</asp:Content> 
<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server"> 
<div class="demo-card"> <div class="demo-card-header">Tune parallel chunk concurrency live</div> <div class="demo-card-body"> <p class="demo-description"> For chunked uploads (and direct-to-S3 / Azure), <code>chunkConcurrency</code> controls how many parts stream at once. Higher values fill the pipe; lower values are kinder to shared infrastructure. This demo lets you change the value between uploads and watch the effect on throughput.
 </p> <div class="demo-expect"><strong>What you should see:</strong> 512&nbsp;KB chunks with a concurrency slider - watch the network panel change from serial to parallel as you move it.</div> <div class="card p-3 mt-3" style="background:#fff;border:1px solid #e2e8f0;"> <div class="d-flex flex-wrap align-items-center gap-3"> <label class="form-label fw-semibold small mb-0" for="concurrencySlider">Chunk concurrency:</label> <input id="concurrencySlider" type="range" min="1" max="8" value="4" class="form-range" style="flex:1" /> <span id="concurrencyValue" class="badge bg-info-subtle text-info-emphasis" style="min-width:3rem;padding:0.45rem 0.8rem">4</span> </div> <p class="mt-2 text-muted small mb-0">Applies on the next upload. Range 1-8; tune for your bucket's 5xx rate in production.</p> </div> <div class="d-flex gap-2 mb-2 mt-4">
 <button type="button" class="btn btn-primary" id="startBtn_concurrencyUploader">Start upload</button>
 <button type="button" class="btn btn-outline-secondary" id="clearBtn_concurrencyUploader">Clear queue</button>
 </div>
 <div id="concurrencyUploader"></div> <script> (function () {
 function init() {
 if (!window.AjaxUploader || typeof AjaxUploader.create !== 'function') return; // graceful no-op if runtime missing
 var slider = document.getElementById('concurrencySlider');
 var valueEl = document.getElementById('concurrencyValue');
 var uploader = AjaxUploader.create('#concurrencyUploader', {
 uploadUrl: '<%= ResolveUrl("~/ajaxupload.axd/upload") %>',
 chunkUrl: '<%= ResolveUrl("~/ajaxupload.axd/chunk") %>',
 chunkCompleteUrl: '<%= ResolveUrl("~/ajaxupload.axd/chunk/complete") %>',
 chunked: true,
 chunkSize: 512 * 1024,
 chunkConcurrency: 4,
 autoUpload: false,
 multiple: true
 });
 if (slider) {
 slider.addEventListener('input', function () {
 if (valueEl) valueEl.textContent = slider.value;
 if (uploader && typeof uploader.setOptions === 'function') {
 uploader.setOptions({ chunkConcurrency: parseInt(slider.value, 10) });
 }
 });
 }
 var startButton = document.getElementById('startBtn_concurrencyUploader');
 var clearButton = document.getElementById('clearBtn_concurrencyUploader');
 if (startButton) {
 startButton.addEventListener('click', function () {
 if (uploader && typeof uploader.upload === 'function') uploader.upload();
 });
 }
 if (clearButton) {
 clearButton.addEventListener('click', function () {
 if (uploader && typeof uploader.clear === 'function') uploader.clear();
 });
 }
 }
 if (document.readyState !== 'loading') init();
 else document.addEventListener('DOMContentLoaded', init);
 })();
 </script> <div class="alert alert-info mt-3" role="alert"> <strong>What you'll observe:</strong> <code>1</code> = strictly sequential (slowest, kindest to throttled servers). <code>4</code> = sensible default for most connections. <code>8+</code> = best on high-bandwidth links, but watch for rate-limit 429s.
 </div> <h5 class="mt-4">Configuration</h5> <div class="code-panel"> 
<pre><code>AjaxUploader.create('#uploader', {
 chunked: true,
 chunkUrl: '/ajaxupload.axd/chunk',
 chunkCompleteUrl: '/ajaxupload.axd/chunk/complete',
 chunkSize: 5 * 1024 * 1024,
 chunkConcurrency: 4 <span style="color:#94a3b8">// default 1; tune to taste</span> 
});</code></pre> </div> <h5 class="mt-4">How parallel chunks work</h5> <ul class="mb-3"> <li>The file is sliced into N chunks (<code>totalChunks = ceil(size / chunkSize)</code>).</li> <li>A worker pool (size = <code>chunkConcurrency</code>) pulls chunks off the queue and uploads them independently.</li> <li>Per-chunk progress feeds into a shared <code>chunkBytes</code> map; the displayed progress is the sum across all chunks.</li> <li>Each chunk has its own retry budget with exponential backoff; a failed chunk is retried without stalling the others.</li> <li>On completion, the server assembles chunks (for <code>chunked</code>) or the client commits the block list / part list (for <code>s3</code> / <code>azure</code>).</li> </ul> <h5 class="mt-4">Works for every multipart strategy</h5> <p>The same <code>chunkConcurrency</code> option applies to <code>chunked</code>, <code>s3</code>, and <code>azure</code> strategies. <code>tus</code> is protocol-sequential (one PATCH at a time) and ignores the setting.</p> </div> 
</div> 
</asp:Content> 
