Tutorial: Batch Processing Multiple CAD Files

Learning Objectives

In this tutorial, you’ll learn:

  • How to efficiently process multiple CAD files in batch operations
  • How to implement parallel processing for better performance
  • How to track progress and handle errors in batch operations
  • How to create a reusable batch processing framework
  • How to optimize batch operations for different environments

Prerequisites

Before starting this tutorial, you should have:

  • Completed the SDK Implementation in C# tutorial
  • An Aspose Cloud account with an active subscription or trial
  • Your Client ID and Client Secret from the Aspose Cloud dashboard
  • Multiple CAD drawings (DWG, DXF, etc.) for testing
  • Basic understanding of asynchronous programming concepts
  • Basic knowledge of parallel processing techniques

Introduction

In real-world scenarios, you often need to process multiple CAD files at once. Batch processing allows you to scale, convert, and transform many drawings automatically, saving time and effort. This tutorial will teach you how to implement efficient batch processing using Aspose.CAD Cloud API.

Understanding Batch Processing Approaches

There are several approaches to batch processing CAD files:

  1. Sequential Processing: Processing one file after another
  2. Parallel Processing: Processing multiple files simultaneously
  3. Chunked Processing: Processing files in smaller batches or chunks
  4. Queue-Based Processing: Adding files to a queue for asynchronous processing

Each approach has its advantages and trade-offs, which we’ll explore in this tutorial.

Step 1: Create a Batch Processing Framework

Let’s start by creating a batch processing framework that supports different processing strategies:

// Tutorial Code Example - Batch Processing Framework in C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AsposeCadBatchProcessing
{
    // Base class for batch processing operations
    public abstract class BatchProcessor<T>
    {
        protected readonly List<T> _items;
        protected readonly int _maxParallelTasks;
        protected readonly CancellationToken _cancellationToken;

        // Event for progress tracking
        public event EventHandler<BatchProgressEventArgs> ProgressChanged;
        
        // Event for completion notification
        public event EventHandler<BatchCompletedEventArgs> ProcessingCompleted;

        protected BatchProcessor(IEnumerable<T> items, int maxParallelTasks = 4, CancellationToken cancellationToken = default)
        {
            _items = items?.ToList() ?? throw new ArgumentNullException(nameof(items));
            _maxParallelTasks = maxParallelTasks > 0 ? maxParallelTasks : throw new ArgumentException("Max parallel tasks must be greater than zero", nameof(maxParallelTasks));
            _cancellationToken = cancellationToken;
        }

        // Process all items sequentially
        public async Task ProcessSequentiallyAsync()
        {
            int total = _items.Count;
            int completed = 0;
            int failed = 0;
            var errors = new Dictionary<T, Exception>();

            OnProgressChanged(new BatchProgressEventArgs(0, total, completed, failed));

            foreach (var item in _items)
            {
                if (_cancellationToken.IsCancellationRequested)
                {
                    break;
                }

                try
                {
                    await ProcessItemAsync(item);
                    completed++;
                }
                catch (Exception ex)
                {
                    errors[item] = ex;
                    failed++;
                }

                OnProgressChanged(new BatchProgressEventArgs((completed + failed) * 100 / total, total, completed, failed));
            }

            OnProcessingCompleted(new BatchCompletedEventArgs(total, completed, failed, errors));
        }

        // Process items in parallel
        public async Task ProcessInParallelAsync()
        {
            int total = _items.Count;
            int completed = 0;
            int failed = 0;
            var errors = new Dictionary<T, Exception>();
            var lockObject = new object();

            OnProgressChanged(new BatchProgressEventArgs(0, total, completed, failed));

            // Create batches for parallel processing
            var tasks = new List<Task>();
            var semaphore = new SemaphoreSlim(_maxParallelTasks);

            foreach (var item in _items)
            {
                if (_cancellationToken.IsCancellationRequested)
                {
                    break;
                }

                await semaphore.WaitAsync();

                tasks.Add(Task.Run(async () =>
                {
                    try
                    {
                        await ProcessItemAsync(item);

                        lock (lockObject)
                        {
                            completed++;
                            OnProgressChanged(new BatchProgressEventArgs((completed + failed) * 100 / total, total, completed, failed));
                        }
                    }
                    catch (Exception ex)
                    {
                        lock (lockObject)
                        {
                            errors[item] = ex;
                            failed++;
                            OnProgressChanged(new BatchProgressEventArgs((completed + failed) * 100 / total, total, completed, failed));
                        }
                    }
                    finally
                    {
                        semaphore.Release();
                    }
                }));
            }

            await Task.WhenAll(tasks);

            OnProcessingCompleted(new BatchCompletedEventArgs(total, completed, failed, errors));
        }

        // Process items in chunks
        public async Task ProcessInChunksAsync(int chunkSize = 5)
        {
            int total = _items.Count;
            int completed = 0;
            int failed = 0;
            var errors = new Dictionary<T, Exception>();

            OnProgressChanged(new BatchProgressEventArgs(0, total, completed, failed));

            // Process in chunks
            for (int i = 0; i < total; i += chunkSize)
            {
                if (_cancellationToken.IsCancellationRequested)
                {
                    break;
                }

                var chunk = _items.Skip(i).Take(Math.Min(chunkSize, total - i)).ToList();
                var tasks = new List<Task>();
                var lockObject = new object();

                foreach (var item in chunk)
                {
                    tasks.Add(Task.Run(async () =>
                    {
                        try
                        {
                            await ProcessItemAsync(item);

                            lock (lockObject)
                            {
                                completed++;
                            }
                        }
                        catch (Exception ex)
                        {
                            lock (lockObject)
                            {
                                errors[item] = ex;
                                failed++;
                            }
                        }
                    }));
                }

                await Task.WhenAll(tasks);
                OnProgressChanged(new BatchProgressEventArgs((completed + failed) * 100 / total, total, completed, failed));
            }

            OnProcessingCompleted(new BatchCompletedEventArgs(total, completed, failed, errors));
        }

        // Abstract method to process a single item
        protected abstract Task ProcessItemAsync(T item);

        // Helper methods for events
        protected virtual void OnProgressChanged(BatchProgressEventArgs e)
        {
            ProgressChanged?.Invoke(this, e);
        }

        protected virtual void OnProcessingCompleted(BatchCompletedEventArgs e)
        {
            ProcessingCompleted?.Invoke(this, e);
        }
    }

    // Event args for progress tracking
    public class BatchProgressEventArgs : EventArgs
    {
        public int PercentComplete { get; }
        public int TotalItems { get; }
        public int CompletedItems { get; }
        public int FailedItems { get; }

        public BatchProgressEventArgs(int percentComplete, int totalItems, int completedItems, int failedItems)
        {
            PercentComplete = percentComplete;
            TotalItems = totalItems;
            CompletedItems = completedItems;
            FailedItems = failedItems;
        }
    }

    // Event args for completion notification
    // Event args for completion notification
    public class BatchCompletedEventArgs : EventArgs
    {
        public int TotalItems { get; }
        public int CompletedItems { get; }
        public int FailedItems { get; }
        public IReadOnlyDictionary<object, Exception> Errors { get; }

        public BatchCompletedEventArgs(int totalItems, int completedItems, int failedItems, Dictionary<object, Exception> errors)
        {
            TotalItems = totalItems;
            CompletedItems = completedItems;
            FailedItems = failedItems;
            Errors = errors;
        }
    }
}

Step 2: Implement a CAD File Processor

Now, let’s create a concrete implementation of our batch processor specifically for CAD files:

// Tutorial Code Example - CAD Batch Processor in C#
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Aspose.CAD.Cloud.Sdk.Api;
using Aspose.CAD.Cloud.Sdk.Model.Requests;

namespace AsposeCadBatchProcessing
{
    // Model class for CAD processing parameters
    public class CadProcessingItem
    {
        public string FileName { get; set; }
        public string OutputPath { get; set; }
        public string OutputFormat { get; set; }
        public int Width { get; set; }
        public int Height { get; set; }
        public bool MaintainAspectRatio { get; set; }
        
        public override string ToString()
        {
            return FileName;
        }
    }

    // Batch processor for CAD files
    public class CadBatchProcessor : BatchProcessor<CadProcessingItem>
    {
        private readonly CadApi _cadApi;
        private readonly bool _useStorage;

        public CadBatchProcessor(
            CadApi cadApi, 
            IEnumerable<CadProcessingItem> items, 
            bool useStorage = true,
            int maxParallelTasks = 4, 
            CancellationToken cancellationToken = default)
            : base(items, maxParallelTasks, cancellationToken)
        {
            _cadApi = cadApi ?? throw new ArgumentNullException(nameof(cadApi));
            _useStorage = useStorage;
        }

        protected override async Task ProcessItemAsync(CadProcessingItem item)
        {
            Console.WriteLine($"Processing {item.FileName}...");
            
            try
            {
                // If aspect ratio should be maintained, calculate the correct dimensions
                if (item.MaintainAspectRatio)
                {
                    // Get drawing properties
                    var propertiesRequest = new GetDrawingPropertiesRequest(item.FileName);
                    var properties = _cadApi.GetDrawingProperties(propertiesRequest);
                    
                    if (!string.IsNullOrEmpty(properties.Width) && !string.IsNullOrEmpty(properties.Height))
                    {
                        float originalWidth = float.Parse(properties.Width);
                        float originalHeight = float.Parse(properties.Height);
                        
                        // Calculate aspect ratio
                        float aspectRatio = originalWidth / originalHeight;
                        
                        // Use specified width to calculate height
                        item.Height = (int)(item.Width / aspectRatio);
                        
                        Console.WriteLine($"Original dimensions: {originalWidth}x{originalHeight}");
                        Console.WriteLine($"New dimensions: {item.Width}x{item.Height}");
                    }
                }
                
                if (_useStorage)
                {
                    // Process files from cloud storage
                    var request = new GetImageResizeRequest(item.FileName, item.OutputFormat, item.Width, item.Height);
                    
                    using (var response = await Task.FromResult(_cadApi.GetImageResize(request)))
                    {
                        // Save the result to a file
                        using (var output = File.Create(item.OutputPath))
                        {
                            await response.CopyToAsync(output);
                        }
                    }
                }
                else
                {
                    // Process local files by uploading them first
                    if (!File.Exists(item.FileName))
                    {
                        throw new FileNotFoundException($"File not found: {item.FileName}");
                    }
                    
                    // Read the file data
                    byte[] drawingData = File.ReadAllBytes(item.FileName);
                    
                    // Create request
                    var request = new PostImageResizeRequest(item.OutputFormat, item.Width, item.Height, drawingData);
                    
                    // Make API call
                    using (var response = await Task.FromResult(_cadApi.PostImageResize(request)))
                    {
                        // Save the result to a file
                        using (var output = File.Create(item.OutputPath))
                        {
                            await response.CopyToAsync(output);
                        }
                    }
                }
                
                Console.WriteLine($"Successfully processed {item.FileName}. Output saved to {item.OutputPath}");
            }
            catch (Exception ex)
            {
                Console.WriteLine($"Error processing {item.FileName}: {ex.Message}");
                throw; // Rethrow to be captured by the batch processor
            }
        }
    }
}

Step 3: Create a Batch Processing Application

Now, let’s create an application to demonstrate our batch processing framework:

// Tutorial Code Example - Batch Processing Application in C#
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Aspose.CAD.Cloud.Sdk.Api;

namespace AsposeCadBatchProcessing
{
    class Program
    {
        // Replace these with your own credentials
        private const string ClientId = "YOUR_CLIENT_ID";
        private const string ClientSecret = "YOUR_CLIENT_SECRET";

        static async Task Main(string[] args)
        {
            try
            {
                Console.WriteLine("Aspose.CAD Cloud Batch Processing Demo");
                Console.WriteLine("======================================");

                // Initialize the API
                var cadApi = new CadApi(ClientId, ClientSecret);

                // Create processing items
                var items = CreateSampleItems();

                // Create cancellation token source (for demonstration)
                var cts = new CancellationTokenSource();
                
                // Create batch processor
                var processor = new CadBatchProcessor(cadApi, items, true, 3, cts.Token);
                
                // Subscribe to events
                processor.ProgressChanged += (sender, e) => 
                {
                    Console.WriteLine($"Progress: {e.PercentComplete}%, Completed: {e.CompletedItems}/{e.TotalItems}, Failed: {e.FailedItems}");
                };
                
                processor.ProcessingCompleted += (sender, e) => 
                {
                    Console.WriteLine($"\nProcessing completed!");
                    Console.WriteLine($"Total: {e.TotalItems}, Completed: {e.CompletedItems}, Failed: {e.FailedItems}");
                    
                    if (e.FailedItems > 0)
                    {
                        Console.WriteLine("\nErrors:");
                        foreach (var error in e.Errors)
                        {
                            Console.WriteLine($"- {error.Key}: {error.Value.Message}");
                        }
                    }
                };

                // Show menu and get user choice
                while (true)
                {
                    Console.WriteLine("\nSelect processing method:");
                    Console.WriteLine("1. Sequential Processing");
                    Console.WriteLine("2. Parallel Processing");
                    Console.WriteLine("3. Chunk Processing");
                    Console.WriteLine("4. Exit");
                    Console.Write("\nEnter your choice (1-4): ");

                    var choice = Console.ReadLine();

                    switch (choice)
                    {
                        case "1":
                            Console.WriteLine("\n=== Starting Sequential Processing ===\n");
                            await processor.ProcessSequentiallyAsync();
                            break;

                        case "2":
                            Console.WriteLine("\n=== Starting Parallel Processing ===\n");
                            await processor.ProcessInParallelAsync();
                            break;

                        case "3":
                            Console.WriteLine("\nEnter chunk size: ");
                            if (int.TryParse(Console.ReadLine(), out int chunkSize) && chunkSize > 0)
                            {
                                Console.WriteLine($"\n=== Starting Chunk Processing (Chunk Size: {chunkSize}) ===\n");
                                await processor.ProcessInChunksAsync(chunkSize);
                            }
                            else
                            {
                                Console.WriteLine("Invalid chunk size. Please enter a positive number.");
                            }
                            break;

                        case "4":
                            return;

                        default:
                            Console.WriteLine("Invalid choice. Please try again.");
                            break;
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"An error occurred: {ex.Message}");
                Console.WriteLine(ex.StackTrace);
            }

            Console.WriteLine("\nPress any key to exit...");
            Console.ReadKey();
        }

        private static List<CadProcessingItem> CreateSampleItems()
        {
            // Replace these with your actual CAD files in cloud storage
            var fileNames = new[] 
            { 
                "sample1.dxf", 
                "sample2.dwg", 
                "sample3.dxf", 
                "sample4.dxf", 
                "sample5.dwg" 
            };
            
            var outputFormats = new[] { "pdf", "png", "jpg" };
            var items = new List<CadProcessingItem>();
            
            // Create a processing item for each file and format
            foreach (var fileName in fileNames)
            {
                var baseName = Path.GetFileNameWithoutExtension(fileName);
                
                foreach (var format in outputFormats)
                {
                    items.Add(new CadProcessingItem 
                    {
                        FileName = fileName,
                        OutputPath = $"{baseName}.{format}",
                        OutputFormat = format,
                        Width = 800,
                        Height = 600,
                        MaintainAspectRatio = true
                    });
                }
            }
            
            return items;
        }
    }
}

Step 4: Advanced Batch Processing Techniques

Let’s explore some advanced techniques for batch processing:

1. File Discovery and Filtering

Often, you’ll want to automatically discover and filter CAD files before processing them:

// Tutorial Code Example - File Discovery and Filtering in C#
public class CadFileDiscovery
{
    private readonly CadApi _cadApi;
    private readonly string[] _supportedExtensions = { ".dwg", ".dxf", ".dgn", ".dwf", ".dwfx", ".ifc", ".stl", ".igs", ".plt", ".obj" };

    public CadFileDiscovery(CadApi cadApi)
    {
        _cadApi = cadApi ?? throw new ArgumentNullException(nameof(cadApi));
    }

    // Discover files in cloud storage
    public List<string> DiscoverFilesInStorage(string folder = null, bool recursive = false)
    {
        var files = new List<string>();
        
        // Get files list from root or specified folder
        var request = new GetFilesListRequest(folder);
        var response = _cadApi.GetFilesList(request);
        
        foreach (var file in response.Value)
        {
            // Check if it's a CAD file
            string extension = Path.GetExtension(file.Name).ToLowerInvariant();
            if (Array.IndexOf(_supportedExtensions, extension) >= 0)
            {
                string filePath = folder != null ? $"{folder}/{file.Name}" : file.Name;
                files.Add(filePath);
            }
            
            // Recursively process subfolders if requested
            if (recursive && file.IsFolder)
            {
                string subFolder = folder != null ? $"{folder}/{file.Name}" : file.Name;
                files.AddRange(DiscoverFilesInStorage(subFolder, true));
            }
        }
        
        return files;
    }

    // Discover files in local folder
    public List<string> DiscoverFilesLocally(string rootFolder, bool recursive = false)
    {
        var files = new List<string>();
        var searchOption = recursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly;
        
        foreach (var extension in _supportedExtensions)
        {
            files.AddRange(Directory.GetFiles(rootFolder, $"*{extension}", searchOption));
        }
        
        return files;
    }

    // Filter files by criteria
    public List<string> FilterFiles(IEnumerable<string> files, Func<string, bool> predicate)
    {
        return files.Where(predicate).ToList();
    }
}

2. Reporting and Analysis

For large batch operations, generating reports is essential:

// Tutorial Code Example - Batch Processing Report Generator in C#
public class BatchReportGenerator
{
    // Create detailed HTML report
    public string GenerateHtmlReport(BatchCompletedEventArgs args, TimeSpan duration)
    {
        var report = new StringBuilder();
        
        report.AppendLine("<!DOCTYPE html>");
        report.AppendLine("<html>");
        report.AppendLine("<head>");
        report.AppendLine("  <title>CAD Batch Processing Report</title>");
        report.AppendLine("  <style>");
        report.AppendLine("    body { font-family: Arial, sans-serif; margin: 20px; }");
        report.AppendLine("    .summary { background-color: #f5f5f5; padding: 10px; border-radius: 5px; }");
        report.AppendLine("    .success { color: green; }");
        report.AppendLine("    .failure { color: red; }");
        report.AppendLine("    table { border-collapse: collapse; width: 100%; }");
        report.AppendLine("    th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }");
        report.AppendLine("    th { background-color: #f2f2f2; }");
        report.AppendLine("  </style>");
        report.AppendLine("</head>");
        report.AppendLine("<body>");
        
        // Summary
        report.AppendLine("  <h1>CAD Batch Processing Report</h1>");
        report.AppendLine("  <div class='summary'>");
        report.AppendLine($"    <p><strong>Date:</strong> {DateTime.Now}</p>");
        report.AppendLine($"    <p><strong>Duration:</strong> {duration.TotalMinutes:F2} minutes</p>");
        report.AppendLine($"    <p><strong>Total Files:</strong> {args.TotalItems}</p>");
        report.AppendLine($"    <p><strong>Successfully Processed:</strong> <span class='success'>{args.CompletedItems}</span></p>");
        report.AppendLine($"    <p><strong>Failed:</strong> <span class='failure'>{args.FailedItems}</span></p>");
        report.AppendLine($"    <p><strong>Success Rate:</strong> {(args.CompletedItems / (float)args.TotalItems) * 100:F2}%</p>");
        report.AppendLine("  </div>");
        
        // Error details
        if (args.FailedItems > 0)
        {
            report.AppendLine("  <h2>Failed Items</h2>");
            report.AppendLine("  <table>");
            report.AppendLine("    <tr><th>File</th><th>Error</th></tr>");
            
            foreach (var error in args.Errors)
            {
                report.AppendLine($"    <tr><td>{error.Key}</td><td>{error.Value.Message}</td></tr>");
            }
            
            report.AppendLine("  </table>");
        }
        
        report.AppendLine("</body>");
        report.AppendLine("</html>");
        
        return report.ToString();
    }
    
    // Save report to file
    public void SaveReport(string report, string outputPath)
    {
        File.WriteAllText(outputPath, report);
    }
}

3. Dynamic Processing Pipeline

For more complex scenarios, you might want to create a dynamic processing pipeline:

// Tutorial Code Example - Dynamic Processing Pipeline in C#
public class CadProcessingPipeline
{
    private readonly List<Func<CadProcessingItem, Task<CadProcessingItem>>> _steps = 
        new List<Func<CadProcessingItem, Task<CadProcessingItem>>>();
    
    // Add processing step
    public CadProcessingPipeline AddStep(Func<CadProcessingItem, Task<CadProcessingItem>> step)
    {
        _steps.Add(step);
        return this;
    }
    
    // Execute the pipeline
    public async Task<CadProcessingItem> ExecuteAsync(CadProcessingItem item)
    {
        var currentItem = item;
        
        foreach (var step in _steps)
        {
            currentItem = await step(currentItem);
        }
        
        return currentItem;
    }
}

// Example pipeline steps
public static class CadProcessingSteps
{
    // Get properties step
    public static Func<CadProcessingItem, Task<CadProcessingItem>> GetProperties(CadApi cadApi)
    {
        return async (item) =>
        {
            var propertiesRequest = new GetDrawingPropertiesRequest(item.FileName);
            var properties = await Task.FromResult(cadApi.GetDrawingProperties(propertiesRequest));
            
            // Store properties in item for later steps
            item.Properties = properties;
            
            return item;
        };
    }
    
    // Calculate dimensions step
    public static Func<CadProcessingItem, Task<CadProcessingItem>> CalculateDimensions(int maxWidth = 1200, int maxHeight = 800)
    {
        return async (item) =>
        {
            if (item.Properties != null && 
                !string.IsNullOrEmpty(item.Properties.Width) && 
                !string.IsNullOrEmpty(item.Properties.Height))
            {
                float originalWidth = float.Parse(item.Properties.Width);
                float originalHeight = float.Parse(item.Properties.Height);
                
                // Calculate scaling factors
                float widthScale = maxWidth / originalWidth;
                float heightScale = maxHeight / originalHeight;
                
                // Use the smaller scaling factor
                float scale = Math.Min(widthScale, heightScale);
                
                // Calculate new dimensions
                item.Width = (int)(originalWidth * scale);
                item.Height = (int)(originalHeight * scale);
            }
            
            return item;
        };
    }
    
    // Resize step
    public static Func<CadProcessingItem, Task<CadProcessingItem>> Resize(CadApi cadApi)
    {
        return async (item) =>
        {
            var request = new GetImageResizeRequest(item.FileName, item.OutputFormat, item.Width, item.Height);
            
            using (var response = await Task.FromResult(cadApi.GetImageResize(request)))
            {
                using (var output = File.Create(item.OutputPath))
                {
                    await response.CopyToAsync(output);
                }
            }
            
            return item;
        };
    }
}

Step 5: Optimizing Batch Processing Performance

To get the best performance from your batch processing, consider these optimization techniques:

1. Thread Pool Configuration

Configure thread pool settings for optimal performance:

// Set minimum number of worker threads
ThreadPool.SetMinThreads(workerThreads: 16, completionPortThreads: 16);

// Optional: set maximum threads as well
ThreadPool.SetMaxThreads(workerThreads: 32, completionPortThreads: 32);

2. Rate Limiting

Implement rate limiting to prevent API throttling:

// Tutorial Code Example - Rate Limiting Utility in C#
public class RateLimiter
{
    private readonly SemaphoreSlim _semaphore;
    private readonly int _periodMs;
    
    public RateLimiter(int maxOperations, int periodMs)
    {
        _semaphore = new SemaphoreSlim(maxOperations);
        _periodMs = periodMs;
    }
    
    public async Task<T> LimitAsync<T>(Func<Task<T>> operation)
    {
        await _semaphore.WaitAsync();
        
        try
        {
            var result = await operation();
            
            // Schedule semaphore release after the period
            _ = Task.Delay(_periodMs).ContinueWith(_ => _semaphore.Release());
            
            return result;
        }
        catch
        {
            _semaphore.Release();
            throw;
        }
    }
}

3. Resource Management

Properly manage resources to avoid memory leaks:

// Tutorial Code Example - Resource Management in C#
public class ResourceManager<TResource> : IDisposable where TResource : IDisposable
{
    private readonly ConcurrentBag<TResource> _resources = new ConcurrentBag<TResource>();
    private readonly Func<TResource> _factory;
    
    public ResourceManager(Func<TResource> factory)
    {
        _factory = factory ?? throw new ArgumentNullException(nameof(factory));
    }
    
    public TResource GetResource()
    {
        if (_resources.TryTake(out var resource))
        {
            return resource;
        }
        
        return _factory();
    }
    
    public void ReleaseResource(TResource resource)
    {
        _resources.Add(resource);
    }
    
    public void Dispose()
    {
        while (_resources.TryTake(out var resource))
        {
            resource.Dispose();
        }
    }
}

Practical Batch Processing Scenarios

Let’s look at some real-world batch processing scenarios:

Scenario 1: Converting an Entire Project to PDF

Imagine you have a project with hundreds of CAD drawings, and you need to convert all of them to PDF for documentation purposes:

// Tutorial Code Example - Batch Convert Project to PDF
public async Task ConvertProjectToPdf(string projectFolder, string outputFolder)
{
    // Discover all CAD files in the project folder
    var discovery = new CadFileDiscovery(_cadApi);
    var files = discovery.DiscoverFilesLocally(projectFolder, recursive: true);
    
    Console.WriteLine($"Found {files.Count} CAD files in project folder.");
    
    // Create processing items
    var items = new List<CadProcessingItem>();
    foreach (var file in files)
    {
        var relativePath = file.Substring(projectFolder.Length).TrimStart(Path.DirectorySeparatorChar);
        var outputPath = Path.Combine(outputFolder, Path.ChangeExtension(relativePath, "pdf"));
        
        // Ensure output directory exists
        Directory.CreateDirectory(Path.GetDirectoryName(outputPath));
        
        items.Add(new CadProcessingItem
        {
            FileName = file,
            OutputPath = outputPath,
            OutputFormat = "pdf",
            Width = 1200,
            Height = 900,
            MaintainAspectRatio = true
        });
    }
    
    // Create batch processor for local files
    var processor = new CadBatchProcessor(_cadApi, items, useStorage: false, maxParallelTasks: 5);
    
    // Track start time
    var startTime = DateTime.Now;
    
    // Subscribe to events
    processor.ProgressChanged += (sender, e) => 
    {
        Console.WriteLine($"Progress: {e.PercentComplete}%, Completed: {e.CompletedItems}/{e.TotalItems}");
    };
    
    // Process files in parallel
    await processor.ProcessInParallelAsync();
    
    // Calculate duration
    var duration = DateTime.Now - startTime;
    Console.WriteLine($"Conversion completed in {duration.TotalMinutes:F2} minutes.");
}

Scenario 2: Creating Web Thumbnails

For a web application that displays CAD drawings, you might need to generate thumbnails of different sizes:

// Tutorial Code Example - Generate Web Thumbnails
public async Task GenerateWebThumbnails(List<string> cadFiles, string outputFolder)
{
    // Define thumbnail sizes (small, medium, large)
    var sizes = new[]
    {
        new { Name = "small", Width = 150, Height = 150 },
        new { Name = "medium", Width = 300, Height = 300 },
        new { Name = "large", Width = 600, Height = 600 }
    };
    
    // Create processing items
    var items = new List<CadProcessingItem>();
    
    foreach (var file in cadFiles)
    {
        var baseName = Path.GetFileNameWithoutExtension(file);
        
        foreach (var size in sizes)
        {
            items.Add(new CadProcessingItem
            {
                FileName = file,
                OutputPath = Path.Combine(outputFolder, $"{baseName}_{size.Name}.png"),
                OutputFormat = "png",
                Width = size.Width,
                Height = size.Height,
                MaintainAspectRatio = true
            });
        }
    }
    
    // Create and configure batch processor
    var processor = new CadBatchProcessor(_cadApi, items, useStorage: true);
    
    // Subscribe to events
    processor.ProgressChanged += (sender, e) => 
    {
        Console.WriteLine($"Generating thumbnails: {e.PercentComplete}% complete");
    };
    
    // Process in chunks of 10 items
    await processor.ProcessInChunksAsync(chunkSize: 10);
    
    Console.WriteLine("All thumbnails generated successfully.");
}

Try It Yourself: Building a Complete Batch Processing Solution

Let’s put everything together into a complete solution:

  1. Create a new console application project
  2. Implement the batch processing framework and CAD processor
  3. Create a menu-driven interface for file discovery and processing
  4. Add reporting functionality
  5. Implement error handling and logging

When you run the application, you should be able to:

  • Discover CAD files in a local folder or cloud storage
  • Select which files to process
  • Choose the processing method (sequential, parallel, or chunked)
  • Set output formats and dimensions
  • Monitor progress in real-time
  • Generate a summary report

Troubleshooting Batch Processing Issues

When running batch operations, you might encounter these common issues:

  1. API Rate Limiting: If you hit rate limits, implement rate limiting on your side or reduce parallel tasks.

  2. Memory Issues: For large batches, monitor memory usage and consider processing in smaller chunks.

  3. Network Timeouts: Increase timeout settings and implement retry logic for network operations.

  4. Progress Reporting Accuracy: In parallel processing, ensure thread-safe updates to progress counters.

  5. File Access Conflicts: When writing output files, handle potential conflicts with proper locking.

Learning Checkpoint

Test your understanding of the concepts covered:

  1. What are the advantages and disadvantages of parallel processing compared to sequential processing?
  2. How would you implement rate limiting in a batch processing scenario?
  3. What event mechanism would you use to report progress in a batch operation?
  4. How can you optimize memory usage when processing large numbers of files?

What You’ve Learned

In this tutorial, you’ve learned:

  • How to design and implement a flexible batch processing framework
  • How to process multiple CAD files efficiently using different strategies
  • How to track progress and handle errors in batch operations
  • How to optimize performance with techniques like thread pool configuration and rate limiting
  • How to apply batch processing to real-world scenarios

Further Practice

To reinforce your understanding:

  • Create a Windows service that monitors folders for new CAD files and processes them automatically
  • Implement a web API that provides batch processing capabilities as a service
  • Build a desktop application with a visual interface for batch operations
  • Create a reporting dashboard that shows statistics from batch operations

Helpful Resources

Have questions about this tutorial? Post them on the Aspose.CAD Cloud Forum for quick assistance!