Files
PSMinIO/docs/POWERSHELL-THREADING-RULES.md
PSMinIO Developer 3e4a7e0810 Create comprehensive PowerShell threading documentation and patterns
� PERMANENT THREADING KNOWLEDGE BASE:

 CREATED COMPREHENSIVE DOCUMENTATION:
  • docs/POWERSHELL-THREADING-RULES.md - Complete threading guide
  • Enhanced ThreadSafeProgressCollector with usage examples
  • Added to project structure documentation
  • Saved to AI memory for future reference

� CRITICAL RULES DOCUMENTED:
  • PowerShell cmdlets can ONLY call Write-* methods from main thread
  • Background threads must QUEUE updates, never call Write-* directly
  • ProcessQueuedUpdates() must ONLY be called from main thread
  • Common mistakes and error symptoms clearly identified

� DESIGN PATTERNS PROVIDED:
  • Periodic processing pattern (every 1 second)
  • Completion-based processing pattern
  • Manual processing points pattern
  • Correct Task.WaitAll usage with timeouts

� IMPLEMENTATION CHECKLIST:
  • Pre-implementation checklist for new cmdlets
  • Debugging tips and error identification
  • Code examples for correct and incorrect patterns
  • Testing guidelines for threading compliance

� PREVENTS FUTURE REGRESSIONS:
  • Clear documentation of the 'golden rule'
  • Examples of common threading violations
  • Patterns for all background operation types
  • Reference for all future PowerShell module development

This ensures we never repeat the same threading mistakes!
2025-07-14 22:26:29 -04:00

5.4 KiB

PowerShell Cmdlet Threading Rules and Design Patterns

🚨 CRITICAL THREADING RULE

PowerShell cmdlets can ONLY call Write-Progress, Write-Verbose, Write-Object, Write-Error from the main cmdlet thread.

NEVER call these methods from:

  • Background threads (Task.Run, Task.Factory.StartNew)
  • Async callbacks (async/await continuations)
  • Timer callbacks
  • Event handlers from other threads
  • Any thread other than the main cmdlet thread

ONLY call these methods from:

  • BeginProcessing() override
  • ProcessRecord() override
  • EndProcessing() override
  • Main cmdlet thread execution

🔧 DESIGN PATTERN: ThreadSafeProgressCollector

Problem

When performing background operations (file uploads, downloads, processing), you need to report progress from background threads, but PowerShell doesn't allow direct calls to Write-Progress from those threads.

Solution

Use the ThreadSafeProgressCollector pattern:

// Background thread - QUEUE updates (thread-safe)
_progressCollector.QueueProgressUpdate(activityId, "Processing", "Status", percentage);
_progressCollector.QueueVerboseMessage("Processing file: {0}", fileName);

// Main thread - PROCESS queued updates
_progressCollector.ProcessQueuedUpdates(); // Only call from main thread!

Implementation Pattern

public class MyLongRunningCmdlet : PSCmdlet
{
    private ThreadSafeProgressCollector _progressCollector;

    protected override void BeginProcessing()
    {
        _progressCollector = new ThreadSafeProgressCollector(this);
    }

    protected override void ProcessRecord()
    {
        // Start background work
        var tasks = new List<Task>();
        
        for (int i = 0; i < workItems.Count; i++)
        {
            var task = Task.Run(() =>
            {
                // ✅ CORRECT: Queue from background thread
                _progressCollector.QueueProgressUpdate(1, "Processing", $"Item {i}", progress);
                _progressCollector.QueueVerboseMessage("Processing item {0}", i);
                
                // ❌ WRONG: Never call directly from background thread
                // WriteProgress(...); // This will cause threading errors!
                // WriteVerbose(...);  // This will cause threading errors!
            });
            tasks.Add(task);
        }

        // ✅ CORRECT: Process updates from main thread with periodic updates
        while (!Task.WaitAll(tasks.ToArray(), 1000)) // 1 second intervals
        {
            _progressCollector.ProcessQueuedUpdates(); // Safe on main thread
        }
        
        // Final processing
        _progressCollector.ProcessQueuedUpdates();
    }
}

🚨 COMMON MISTAKES TO AVOID

Mistake 1: Direct calls from background threads

Task.Run(() =>
{
    WriteProgress(...); // THREADING ERROR!
    WriteVerbose(...);  // THREADING ERROR!
});

Mistake 2: ProcessQueuedUpdates from background threads

Task.Run(() =>
{
    _progressCollector.QueueProgressUpdate(...);
    _progressCollector.ProcessQueuedUpdates(); // THREADING ERROR!
});

Mistake 3: Async/await callbacks

await SomeAsyncOperation().ContinueWith(task =>
{
    WriteProgress(...); // THREADING ERROR!
});

CORRECT PATTERNS

Pattern 1: Periodic Processing

var tasks = StartBackgroundTasks();
while (!Task.WaitAll(tasks, 1000))
{
    _progressCollector.ProcessQueuedUpdates(); // Every 1 second
}
_progressCollector.ProcessQueuedUpdates(); // Final update

Pattern 2: Completion-based Processing

var tasks = StartBackgroundTasks();
Task.WaitAll(tasks);
_progressCollector.ProcessQueuedUpdates(); // After completion

Pattern 3: Manual Processing Points

foreach (var item in items)
{
    ProcessItemInBackground(item); // Queues updates
    _progressCollector.ProcessQueuedUpdates(); // Process after each item
}

🎯 ERROR SYMPTOMS

If you violate these rules, you'll see errors like:

  • "The WriteObject and WriteError methods cannot be called from outside the overrides..."
  • "WriteProgress can only be called from within the same thread"
  • Cmdlet failures with threading exceptions
  • Progress bars not updating or appearing

📋 CHECKLIST FOR NEW CMDLETS

Before implementing background operations:

  • Are you using ThreadSafeProgressCollector?
  • Are all Write* calls from main thread only?
  • Are you calling ProcessQueuedUpdates() only from main thread?
  • Do you have periodic progress processing for long operations?
  • Have you tested with verbose output enabled?
  • Have you tested with progress bars enabled?

🔧 DEBUGGING TIPS

  1. Enable verbose logging to see threading violations
  2. Test with progress bars - they're most sensitive to threading issues
  3. Use Task.WaitAll with timeouts for periodic processing
  4. Never call ProcessQueuedUpdates from callbacks
  5. Queue everything from background threads, process from main thread
  • File Upload/Download: Use periodic processing during Task.WaitAll
  • Multipart Operations: Queue from parallel tasks, process periodically
  • Long-running Operations: Process updates every 1-2 seconds
  • Collection Processing: Process after each item or batch

🎯 REMEMBER

The golden rule: Background threads QUEUE, main thread PROCESSES.

This pattern ensures PowerShell cmdlet compliance while providing real-time progress updates to users.