/// <summary> /// Upload a <see cref="Stream"/> in partitions. /// </summary> /// <typeparam name="T"> /// Response type when uploading the entire stream or commiting a /// sequence of partitions. /// </typeparam> /// <typeparam name="P"> /// Response type when uploading a single partition. /// </typeparam> /// <param name="uploadStreamAsync"> /// Returns a Task that will upload the entire stream (given the stream, /// whether to execute it async, and a cancellation token). /// </param> /// <param name="uploadPartitionAsync"> /// Returns a Task that will upload a single partition of a stream (given the /// partition's stream, sequence number, whether to execute it /// async, and a cancellation token). /// </param> /// <param name="commitAsync"> /// Returns a task that will commit a series of partition uploads (given /// whether to execute it async and a cancellation token). /// </param> /// <param name="uploadAsSinglePartition"> /// Returns a bool indicating whether to upload the content as a single partition, instead of multiple. /// </param> /// <param name="getStreamPartitioner"> /// Returns a StreamPartitioner for the content. /// </param> /// <param name="singleUploadThreshold"> /// The maximum size of the stream to allow using /// <paramref name="uploadStreamAsync"/>. /// </param> /// <param name="parallelTransferOptions"> /// Optional <see cref="ParallelTransferOptions"/> to configure /// parallel transfer behavior. /// </param> /// <param name="async"> /// Whether to perform the upload asynchronously. /// </param> /// <param name="cancellationToken"> /// Optional <see cref="CancellationToken"/> to propagate /// notifications that the operation should be cancelled. /// </param> /// <returns></returns> /// <remarks> /// This method assumes that individual uploads are automatically retried. /// </remarks> public static async Task <Response <T> > UploadAsync <T, P>( Func <Task <Response <T> > > uploadStreamAsync, Func <Stream, long, bool, CancellationToken, Task <Response <P> > > uploadPartitionAsync, Func <bool, CancellationToken, Task <Response <T> > > commitAsync, Func <long, bool> uploadAsSinglePartition, Func <MemoryPool <byte>, StreamPartitioner> getStreamPartitioner, long singleUploadThreshold, ParallelTransferOptions?parallelTransferOptions = default, bool async = true, CancellationToken cancellationToken = default) { singleUploadThreshold = Math.Min(singleUploadThreshold, Constants.Blob.Block.MaxUploadBytes); if (uploadAsSinglePartition(singleUploadThreshold)) { // When possible, upload as a single partition Task <Response <T> > uploadTask = uploadStreamAsync(); return(async ? await uploadTask.ConfigureAwait(false) : uploadTask.EnsureCompleted()); } else { // Split the stream into partitions and upload in parallel parallelTransferOptions ??= new ParallelTransferOptions(); var maximumThreadCount = parallelTransferOptions.Value.MaximumThreadCount ?? Constants.Blob.Block.DefaultConcurrentTransfersCount; var maximumBlockLength = Math.Min( Constants.Blob.Block.MaxStageBytes, parallelTransferOptions.Value.MaximumTransferLength ?? Constants.DefaultBufferSize ); var maximumActivePartitionCount = maximumThreadCount; var maximumLoadedPartitionCount = 2 * maximumThreadCount; var memoryPool = default(MemoryPool <byte>); try { // Use the shared memory pool if our maximum block length will fit inside it memoryPool = (maximumBlockLength < MemoryPool <byte> .Shared.MaxBufferSize) ? MemoryPool <byte> .Shared : new StorageMemoryPool(maximumBlockLength, maximumLoadedPartitionCount); using (StreamPartitioner partitioner = getStreamPartitioner(memoryPool)) { await foreach ( StreamPartition partition in partitioner.GetPartitionsAsync( maximumActivePartitionCount, maximumLoadedPartitionCount, maximumBlockLength, async, cancellationToken ) ) { // execute on background task #pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed Task.Run( async() => { //Console.WriteLine($"Partition {partition.ParentPosition} first bytes = {partition.ReadByte()} {partition.ReadByte()} {partition.ReadByte()} {partition.ReadByte()} {partition.ReadByte()} {partition.ReadByte()} {partition.ReadByte()} {partition.ReadByte()}"); partition.Seek(0, SeekOrigin.Begin); await uploadPartitionAsync(partition, partition.ParentPosition, async, cancellationToken).ConfigureAwait(false); partition.Dispose(); } ); #pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed } } // Complete the upload Task <Response <T> > commitTask = commitAsync(async, cancellationToken); return(async ? await commitTask.ConfigureAwait(false) : commitTask.EnsureCompleted()); } finally { if (memoryPool is StorageMemoryPool) { memoryPool.Dispose(); } } } }