How to call asynchronous method from synchronous method in C#?

To call an asynchronous method from a synchronous method in C#, you need to bridge the gap between synchronous and asynchronous code. However, this can lead to deadlocks or blocked threads if not handled properly. Below are multiple approaches with examples, along with their trade-offs:

1. Blocking with .GetAwaiter().GetResult()

This method blocks the current thread until the async task completes.
Use Case: Simple scenarios where deadlocks are unlikely (e.g., console apps).

public void SyncMethod()
{
    // Block and wait for the async method to complete
    var result = AsyncMethod().GetAwaiter().GetResult();
    Console.WriteLine(result);
}

public async Task<string> AsyncMethod()
{
    await Task.Delay(1000);
    return "Async result";
}

Pros: Simple syntax.
Cons:

  • Risk of deadlocks in UI/ASP.NET contexts (due to synchronization contexts).
  • Blocks the current thread (poor scalability).

2. Using .Result (Similar to .GetAwaiter().GetResult())

Access the Result property of the Task to block and retrieve the value.
Use Case: Similar to the first method but with slightly different exception behavior.

public void SyncMethod()
{
    var result = AsyncMethod().Result; // Blocks until task completes
    Console.WriteLine(result);
}

Cons:

  • Wraps exceptions in AggregateException.
  • Same deadlock risks as above.

3. Using Task.Run() to Offload Work

Run the async method on a thread pool thread to avoid deadlocks.
Use Case: UI apps (e.g., WPF, WinForms) where blocking the main thread is acceptable.

public void SyncMethod()
{
    var result = Task.Run(() => AsyncMethod()).GetAwaiter().GetResult();
    Console.WriteLine(result);
}

Pros:

  • Avoids deadlocks in UI contexts (async code runs on a thread pool thread).
    Cons:
  • Adds overhead by queuing work to the thread pool.
  • Still blocks the calling thread.

4. Using ConfigureAwait(false)

Configure the async method to avoid marshaling back to the original context.
Use Case: Libraries or code where the synchronization context is irrelevant.

public void SyncMethod()
{
    var result = AsyncMethod().ConfigureAwait(false).GetAwaiter().GetResult();
    Console.WriteLine(result);
}

public async Task<string> AsyncMethod()
{
    await Task.Delay(1000).ConfigureAwait(false); // No context capture
    return "Async result";
}

Pros:

  • Reduces deadlock risk by avoiding synchronization context.
    Cons:
  • Not always safe (e.g., UI controls cannot be updated after ConfigureAwait(false)).

5. Async Over Sync (Refactoring)

If possible, refactor the code to make the entire call chain asynchronous.
Best Practice: Avoid mixing sync and async code where possible.

// Refactor the sync method to be async
public async Task SyncMethodAsync()
{
    var result = await AsyncMethod();
    Console.WriteLine(result);
}

// Call it from an async context (e.g., event handlers, controllers)

6. Deadlock Explanation

A deadlock occurs in UI/ASP.NET Classic apps when:

  1. A synchronous method blocks the main thread.
  2. The async method tries to resume on the same blocked thread (via the synchronization context).

Example of Deadlock:

// UI/WinForms/WPF Example (DO NOT USE)
public void SyncMethod()
{
    // Deadlock: AsyncMethod() waits for the UI thread to be free
    var result = AsyncMethod().Result; 
}

public async Task<string> AsyncMethod()
{
    await Task.Delay(1000); // Tries to resume on the blocked UI thread
    return "Done";
}

Best Practices

  1. Avoid Blocking: Prefer async/await throughout the call chain.
  2. Use Task.Run with Caution: Only use it if you can’t refactor to async.
  3. ConfigureAwait(false): Use in library code to avoid context capture.
  4. Never Block in UI Threads: Use async void event handlers instead.

Summary Table

MethodUse CaseDeadlock RiskThread Blocking
.GetAwaiter().GetResult()Simple apps (console)HighYes
.ResultLegacy codeHighYes
Task.Run()UI appsLowYes
ConfigureAwait(false)Library codeReducedYes
Full Async RefactoringIdeal solutionNoneNo

Final Recommendation

If you must call async code from a sync method:

  • Use Task.Run(() => AsyncMethod()).GetAwaiter().GetResult() with ConfigureAwait(false) in the async method.
  • Refactor to async code whenever possible.

By understanding these trade-offs, you can safely bridge sync and async code in C#!

Leave a Reply

Your email address will not be published. Required fields are marked *