What is the difference between `Task.FromResult()` and `Task.Run()`?

5 minintermediate.NETTaskasync-awaitperformance

Quick Answer

`Task.FromResult()` synchronously wraps an already-available value in a completed task — no thread is used, ideal for returning cached results or implementing async interfaces synchronously. `Task.Run()` queues a delegate to run on a thread-pool thread, so it's for offloading CPU-bound work. Using `Task.Run` to return a known value (or to make I/O 'async') is wasteful — prefer `FromResult`/true async APIs.

Detailed Answer

Task.FromResult() creates a completed task with a result value, while Task.Run() queues work to run on the ThreadPool. The key difference is that Task.FromResult() is synchronous and immediate, while Task.Run() is asynchronous and offloads work to a background thread.

Key Differences:

AspectTask.FromResult()Task.Run()
ExecutionSynchronous, immediateAsynchronous, queued
ThreadRuns on current threadRuns on ThreadPool thread
Use CaseAlready computed valuesCPU-bound work
PerformanceNo overheadThread switching overhead
When to UseConverting sync to async APIOffloading CPU work

Example:

public class TaskCreationComparison
{
    // Task.FromResult() - for already computed values
    public async Task<string> GetCachedDataAsync(string key)
    {
        // Simulate cache lookup (synchronous operation)
        string cachedValue = GetFromCache(key);
        
        if (cachedValue != null)
        {
            // Already have the value - use Task.FromResult()
            return await Task.FromResult(cachedValue);
        }
        
        // Need to fetch from database (async operation)
        return await FetchFromDatabaseAsync(key);
    }
    
    // Task.Run() - for CPU-bound work
    public async Task<int> CalculatePrimeCountAsync(int maxNumber)
    {
        // CPU-intensive work - offload to ThreadPool
        return await Task.Run(() =>
        {
            int count = 0;
            for (int i = 2; i <= maxNumber; i++)
            {
                if (IsPrime(i))
                    count++;
            }
            return count;
        });
    }
    
    // WRONG: Using Task.Run() for already computed values
    public async Task<string> GetCachedDataWrongAsync(string key)
    {
        string cachedValue = GetFromCache(key);
        
        if (cachedValue != null)
        {
            // BAD: Unnecessary thread switching overhead
            return await Task.Run(() => cachedValue);
        }
        
        return await FetchFromDatabaseAsync(key);
    }
    
    // WRONG: Using Task.FromResult() for CPU-bound work
    public async Task<int> CalculatePrimeCountWrongAsync(int maxNumber)
    {
        // BAD: Blocks the current thread
        int count = 0;
        for (int i = 2; i <= maxNumber; i++)
        {
            if (IsPrime(i))
                count++;
        }
        
        return await Task.FromResult(count);
    }
    
    private string GetFromCache(string key)
    {
        // Simulate cache lookup
        return key == "cached" ? "cached_value" : null;
    }
    
    private async Task<string> FetchFromDatabaseAsync(string key)
    {
        await Task.Delay(1000); // Simulate database call
        return $"database_value_for_{key}";
    }
    
    private bool IsPrime(int number)
    {
        if (number < 2) return false;
        for (int i = 2; i * i <= number; i++)
        {
            if (number % i == 0) return false;
        }
        return true;
    }
}

Advanced Examples:

public class AdvancedTaskCreation
{
    // Task.FromResult() for configuration values
    public async Task<AppSettings> GetAppSettingsAsync()
    {
        // Configuration is already loaded - no need for async
        var settings = LoadConfiguration();
        return await Task.FromResult(settings);
    }
    
    // Task.FromResult() for constants
    public async Task<string> GetApiVersionAsync()
    {
        return await Task.FromResult("v1.0");
    }
    
    // Task.FromResult() for simple calculations
    public async Task<decimal> CalculateTaxAsync(decimal amount, decimal rate)
    {
        decimal tax = amount * rate;
        return await Task.FromResult(tax);
    }
    
    // Task.Run() for file processing
    public async Task<string> ProcessLargeFileAsync(string filePath)
    {
        return await Task.Run(() =>
        {
            // CPU-intensive file processing
            var lines = File.ReadAllLines(filePath);
            var processedLines = lines
                .Where(line => !string.IsNullOrWhiteSpace(line))
                .Select(line => line.ToUpper())
                .OrderBy(line => line)
                .ToArray();
            
            return string.Join("\n", processedLines);
        });
    }
    
    // Task.Run() for image processing
    public async Task<byte[]> ResizeImageAsync(byte[] imageData, int width, int height)
    {
        return await Task.Run(() =>
        {
            // CPU-intensive image processing
            using (var originalImage = Image.FromStream(new MemoryStream(imageData)))
            using (var resizedImage = new Bitmap(originalImage, width, height))
            using (var stream = new MemoryStream())
            {
                resizedImage.Save(stream, ImageFormat.Jpeg);
                return stream.ToArray();
            }
        });
    }
    
    private AppSettings LoadConfiguration()
    {
        // Simulate configuration loading
        return new AppSettings { DatabaseConnection = "Server=localhost", ApiKey = "secret" };
    }
}

public class AppSettings
{
    public string DatabaseConnection { get; set; }
    public string ApiKey { get; set; }
}

Performance Comparison:

public class PerformanceComparison
{
    public async Task ComparePerformance()
    {
        const int iterations = 10000;
        
        // Task.FromResult() - very fast
        var stopwatch = Stopwatch.StartNew();
        for (int i = 0; i < iterations; i++)
        {
            await Task.FromResult(i);
        }
        stopwatch.Stop();
        Console.WriteLine($"Task.FromResult(): {stopwatch.ElapsedMilliseconds}ms");
        
        // Task.Run() - slower due to thread switching
        stopwatch.Restart();
        for (int i = 0; i < iterations; i++)
        {
            await Task.Run(() => i);
        }
        stopwatch.Stop();
        Console.WriteLine($"Task.Run(): {stopwatch.ElapsedMilliseconds}ms");
    }
}

Best Practices:

Use Task.FromResult() when:

  • You already have the computed value
  • Converting synchronous APIs to async
  • Returning constants or configuration values
  • Simple calculations that don't block

Use Task.Run() when:

  • Performing CPU-intensive work
  • Processing large files or data
  • Image/video processing
  • Mathematical calculations
  • Any work that could block the UI thread

Avoid Task.Run() when:

  • You already have the result
  • The work is already asynchronous
  • You're just wrapping synchronous I/O operations
  • The operation is very fast

Common Anti-patterns:

// BAD: Unnecessary Task.Run()
public async Task<string> GetUserNameAsync(int userId)
{
    var user = await GetUserFromDatabaseAsync(userId);
    return await Task.Run(() => user.Name); // Unnecessary!
}

// GOOD: Use Task.FromResult() or just return directly
public async Task<string> GetUserNameAsync(int userId)
{
    var user = await GetUserFromDatabaseAsync(userId);
    return user.Name; // Simple return
}

// BAD: Blocking with Task.FromResult()
public async Task<string> ProcessDataAsync(string data)
{
    var result = ExpensiveProcessing(data); // Blocks current thread
    return await Task.FromResult(result);
}

// GOOD: Use Task.Run() for CPU work
public async Task<string> ProcessDataAsync(string data)
{
    return await Task.Run(() => ExpensiveProcessing(data));
}

Summary:

  • Task.FromResult(): For already computed values, no thread switching
  • Task.Run(): For CPU-bound work that needs to run on background thread
  • Choose based on whether you need to offload work or just return a value
  • Performance matters: avoid unnecessary thread switching

Related Resources