Async Exempel - Filsökning 🔍

💡 Vad gör detta exempel?

Filsökning som är snabb som blixten! ⚡

Tänk dig att du ska söka igenom tusentals filer efter ett specifikt ord. Vanligt sätt = kolla en fil i taget (tråkigt och långsamt). Async-sätt = kolla många filer samtidigt (coolt och snabbt)!

Det är som skillnaden mellan att äta popcorn ett korn i taget vs att ta en näve - mycket effektivare!

🎯 TL;DR

Parallell filsökning med Task.WhenAll. Istället för 100 sekunder tar det bara 5 sekunder!

Exempel - Sökning av filer

Vi skapar ett async-drivet sökverktyg som letar genom alla .cs-filer parallellt! 🚀

🟢 Grundläggande async-filsökning


static class Program
{
    // Async Main - modern C-Sharp!
    static async Task Main()
    {
        var searcher = new FileSearcher();
        var sourceFolder = @"C:\source";

        Console.WriteLine("🔍 Söker efter 'async' i alla .cs filer...");
        var stopwatch = Stopwatch.StartNew();

        // ASYNC call - blockerar inte!
        var results = await searcher.SearchFilesAsync(
            sourceFolder,
            "*.cs",
            "async"
        );

        stopwatch.Stop();

        Console.WriteLine($"\n✅ Hittade {results.Count} matchningar på {stopwatch.ElapsedMilliseconds}ms:\n");
        foreach (var result in results)
        {
            Console.WriteLine($"📄 {result}");
        }
    }
}

🟡 FileSearcher-klassen med error handling


public class FileSearcher
{
    public async Task<List<SearchResult>> SearchFilesAsync(
        string searchPath,
        string pattern,
        string searchText)
    {
        try
        {
            // Hämta alla filer som matchar pattern
            var files = Directory.GetFiles(searchPath, pattern, SearchOption.AllDirectories);
            Console.WriteLine($"📁 Hittade {files.Length} filer att söka igenom");

            // Skapa en task för varje fil (PARALLELLT!)
            var searchTasks = files.Select(file => SearchSingleFileAsync(file, searchText));

            // Vänta på att ALLA tasks blir klara samtidigt
            var results = await Task.WhenAll(searchTasks);

            // Filtrera bort null-resultat
            return results.Where(result => result != null).ToList();
        }
        catch (DirectoryNotFoundException)
        {
            Console.WriteLine($"❌ Mappen {searchPath} finns inte!");
            return new List<SearchResult>();
        }
        catch (UnauthorizedAccessException)
        {
            Console.WriteLine($"🔒 Ingen åtkomst till {searchPath}");
            return new List<SearchResult>();
        }
    }

    private async Task<SearchResult?> SearchSingleFileAsync(string filePath, string searchText)
    {
        try
        {
            // Läs filen asynkront - blockerar inte!
            var lines = await File.ReadAllLinesAsync(filePath);

            // Sök efter text och hitta radnummer
            for (int i = 0; i < lines.Length; i++)
            {
                if (lines[i].Contains(searchText, StringComparison.OrdinalIgnoreCase))
                {
                    return new SearchResult
                    {
                        FilePath = filePath,
                        LineNumber = i + 1,
                        LineContent = lines[i].Trim(),
                        SearchTerm = searchText
                    };
                }
            }

            return null; // Inget hittat
        }
        catch (IOException ex)
        {
            Console.WriteLine($"⚠️ Kunde inte läsa {filePath}: {ex.Message}");
            return null;
        }
    }
}

public class SearchResult
{
    public string FilePath { get; set; }
    public int LineNumber { get; set; }
    public string LineContent { get; set; }
    public string SearchTerm { get; set; }

    public override string ToString()
    {
        var fileName = Path.GetFileName(FilePath);
        return $"{fileName}:{LineNumber}{LineContent}";
    }
}


### 🔴 Avancerad version med progress tracking

```csharp

public class AdvancedFileSearcher
{
    public async Task<List<SearchResult>> SearchWithProgressAsync(
        string searchPath,
        string pattern,
        string searchText,
        IProgress<SearchProgress> progress = null)
    {
        var files = Directory.GetFiles(searchPath, pattern, SearchOption.AllDirectories);
        var results = new List<SearchResult>();
        var processed = 0;

        // Begränsa antal samtidiga tasks för att inte överbelasta systemet
        var semaphore = new SemaphoreSlim(Environment.ProcessorCount * 2); // 2x CPU kärnor

        var tasks = files.Select(async file =>
        {
            await semaphore.WaitAsync(); // Vänta på ledig "slot"
            try
            {
                var result = await SearchSingleFileAsync(file, searchText);

                // Uppdatera progress
                var currentProcessed = Interlocked.Increment(ref processed);
                progress?.Report(new SearchProgress
                {
                    ProcessedFiles = currentProcessed,
                    TotalFiles = files.Length,
                    CurrentFile = Path.GetFileName(file)
                });

                return result;
            }
            finally
            {
                semaphore.Release(); // Frigör "slot"
            }
        });

        var allResults = await Task.WhenAll(tasks);
        return allResults.Where(r => r != null).ToList();
    }
}

public class SearchProgress
{
    public int ProcessedFiles { get; set; }
    public int TotalFiles { get; set; }
    public string CurrentFile { get; set; }

    public double PercentComplete => (double)ProcessedFiles / TotalFiles * 100;

    public override string ToString()
    {
        return $"[{PercentComplete:F1}%] {ProcessedFiles}/{TotalFiles} - {CurrentFile}";
    }
}

// Användning med progress:
static async Task SearchWithProgressExample()
{
    var searcher = new AdvancedFileSearcher();

    var progress = new Progress<SearchProgress>(p =>
    {
        Console.SetCursorPosition(0, Console.CursorTop);
        Console.Write($"🔄 {p}");
    });

    var results = await searcher.SearchWithProgressAsync(
        @"C:\LargeCodebase",
        "*.cs",
        "async",
        progress
    );

    Console.WriteLine($"\n✅ Klart! Hittade {results.Count} matchningar");
}

🚀 Så fungerar magin

  1. Task.WhenAll - Kör alla filsökningar parallellt
  2. SemaphoreSlim - Begränsar antal samtidiga tasks (undvik överbelastning)
  3. Progress<T> - Visar framsteg i realtid
  4. File.ReadAllLinesAsync - Läser filer utan att blockera
  5. Error handling - Hanterar filer som inte kan läsas

📊 Prestanda-jämförelse


SYNC (Sekventiell):
📄 Fil 1: 100ms
📄 Fil 2: 100ms
📄 Fil 3: 100ms
📄 Fil 4: 100ms
TOTALT: 400ms ⏰

ASYNC (Parallell):
📄📄📄📄 Alla filer: ~100ms
TOTALT: 100ms ⚡

SPEED UP: 4x snabbare! 🚀

📚 Sammanfattning

Async filsökning = superkraft för stora kodbasers:

  • Parallell exekvering med Task.WhenAll
  • Resource management med SemaphoreSlim
  • Progress tracking för användarupplevelse
  • Robust error handling för produktionskod
  • 4x+ snabbare än sekventiell sökning

🎯 Övningar

  1. 🟢 Grundläggande: Modifiera koden för att söka efter flera termer samtidigt
  2. 🟡 Utmanande: Lägg till filtrering efter filstorlek och datum
  3. 🔴 Expert: Implementera fuzzy search och regex-stöd

😄 Obligatoriska dad joke

Varför är async-filsökning som en sökmotor?

För att båda är parallella och hittar saker asynkront! 🔍


Upp

Upp


Licens: Apache 2.0 | © 2023 Marcus Medina, Campus Mölndal. Alla rättigheter förbehållna.
Du får använda och modifiera detta verk enligt villkoren i Apache License, Version 2.0. Du får inte använda detta verk för kommersiella ändamål utan tillstånd från upphovsmannen.