Codectionary / Developer documentation / C#

Task & Task<T>

Task represents an asynchronous operation that may still be running, and Task<T> represents one that will eventually produce a value of type T. Tasks are the building block that async/await is built on top of - an async method actually returns a Task (or Task<T>) that the caller can await, check the status of, or attach continuations to.

Read the syntax

Task represents an operation that may complete later; Task<T> also carries a result. await observes completion and propagates failures.

Task.Run schedules work on the thread pool and is useful for CPU-bound work in appropriate contexts. Task.CompletedTask and Task.FromResult wrap already-completed work without starting a new thread.

Syntax

Task<T> task = SomeAsyncMethod();

Examples

Creating and Running a Task

Starting work on a separate task and awaiting its completion.

Task<int> CalculateAsync()
{
    return Task.Run(() =>
    {
        Thread.Sleep(500);  // simulate CPU-bound work
        return 6 * 7;
    });
}

int result = await CalculateAsync();
Console.WriteLine(result);  // 42
  1. Starting work on a separate task and awaiting its completion.
  2. Task represents an operation that may complete later; Task<T> also carries a result. await observes completion and propagates failures.
  3. Task.Run performs the calculation in a thread-pool task after a simulated blocking wait. Await retrieves 42 and the final line prints it.

Task Status and Properties

Inspecting a task's state without necessarily awaiting it immediately.

Task<int> task = Task.Run(() =>
{
    Thread.Sleep(300);
    return 100;
});

Console.WriteLine(task.IsCompleted);  // timing-dependent

int result = await task;
Console.WriteLine(task.IsCompleted);   // true
Console.WriteLine(result);              // 100
  1. Inspecting a task's state without necessarily awaiting it immediately.
  2. Task represents an operation that may complete later; Task<T> also carries a result. await observes completion and propagates failures.
  3. The first IsCompleted is timing-dependent and may be either bool value. After await it is True, and the result printed is 100.

Task.CompletedTask and Task.FromResult

Returning an already-completed task, useful for satisfying an async interface without real async work.

Task LogAsync(string message)
{
    Console.WriteLine(message);
    return Task.CompletedTask;  // no real async work needed, but interface requires Task
}

Task<int> GetCachedValueAsync(int cached)
{
    return Task.FromResult(cached);  // wraps an already-known value in a Task
}

await LogAsync("Started");
int value = await GetCachedValueAsync(99);
Console.WriteLine(value);
  1. Returning an already-completed task, useful for satisfying an async interface without real async work.
  2. Task represents an operation that may complete later; Task<T> also carries a result. await observes completion and propagates failures.
  3. LogAsync writes Started and returns a completed task. GetCachedValueAsync wraps 99 in an already-completed Task<int>; awaiting it prints 99.

Best practices

  • Use Task.Run() specifically for offloading CPU-bound work to a background thread - it is not needed for naturally async I/O operations like HttpClient calls, which are already non-blocking
  • Prefer awaiting a Task directly over checking .IsCompleted in a polling loop - awaiting is more efficient and far simpler to reason about
  • Use Task.FromResult() or Task.CompletedTask when implementing an async interface method that has no real asynchronous work to do
  • Avoid using .Result or .Wait() to synchronously block on a Task - always prefer await, which does not risk deadlocks or block the calling thread

Runtime & versions

.NET runtime. Feature availability depends on the language and runtime version used by your project; consult the official reference below.

C# language reference

Specifications & further reading