Logo

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

Calling an async method from a synchronous method can be tricky in C# because the async/await model is fundamentally designed to propagate asynchrony up the call stack. While the most recommended approach is to make the higher-level method async (so you can await), sometimes you’re constrained by an existing synchronous API or an interface that you can’t change. Below are some ways to handle it, along with important caveats.

1. Call .GetAwaiter().GetResult()

This pattern avoids a potential AggregateException and throws the original exception if the task fails:

public void SyncMethod() { // Synchronously wait for an async call to finish var result = AsyncMethod().GetAwaiter().GetResult(); Console.WriteLine(result); } public async Task<string> AsyncMethod() { await Task.Delay(1000); return "Async result"; }
  • Pros: Preserves the original exception (rather than wrapping it in AggregateException).
  • Cons: This is effectively a blocking wait; the current thread is tied up until AsyncMethod() completes. If you call this on a UI thread (e.g., in a Windows Forms or WPF application) or in an ASP.NET context with a SynchronizationContext, you risk a deadlock if AsyncMethod() tries to resume on the same context.

2. Use .Result

A common—but not ideal—approach is to directly access the .Result property on the Task:

public void SyncMethod() { // Potentially riskier way var result = AsyncMethod().Result; Console.WriteLine(result); }
  • Pros: Simple, one-line.
  • Cons: Calling .Result can lead to AggregateException (if AsyncMethod throws). Also susceptible to the same deadlock pitfalls if you’re on a context that doesn’t allow re-entry (e.g., a UI thread).

3. Use Task.Run in Some Contexts

If you absolutely must do async work from a sync method, one way to mitigate deadlock risk is to offload the call to another thread using Task.Run. However, this may or may not fit your scenario:

public void SyncMethod() { var result = Task.Run(async () => await AsyncMethod()).Result; Console.WriteLine(result); }
  • Pros: This can avoid deadlocks because the async method will resume on a thread pool thread instead of the original calling thread’s context.
  • Cons: You’re still blocking the caller thread. Also, if you rely on the original SynchronizationContext for UI updates, the code inside AsyncMethod() won’t have that context while it’s running.

4. Restructure to Use async All the Way

Whenever possible, the best practice is to propagate async up the call chain rather than forcing a synchronous call. For example, if you have:

public void MainEntryPoint() { // Instead of calling a sync method that calls async, // just make MainEntryPoint async too, if possible. var result = DoAsyncWork().GetAwaiter().GetResult(); } public async Task<string> DoAsyncWork() { await Task.Delay(1000); return "Done"; }

You can often change MainEntryPoint into:

public async Task MainEntryPointAsync() { var result = await DoAsyncWork(); Console.WriteLine(result); }
  • Pros: Avoids blocking, avoids deadlocks, and makes the code simpler in the long run.
  • Cons: This may require a breaking change if the synchronous signature is locked by an interface or used by external callers.

Key Concerns

  1. Deadlocks

    • In a UI application (WPF/WinForms) or ASP.NET classic (pre-Core), calling .Wait() or .Result in a thread that has a SynchronizationContext can lead to deadlock.
    • If the async method attempts to resume on the same context while the thread is blocked, it’ll wait forever.
  2. Exception Wrapping

    • Using .Result or .Wait() often throws AggregateException if the async method fails.
    • .GetAwaiter().GetResult() unwraps this into the original exception, which may be more user-friendly.
  3. Performance

    • Forcing an async operation to run synchronously can hurt performance and responsiveness because you block the current thread.
    • If it’s a UI thread, the UI becomes unresponsive until the async work completes.
  4. Design

    • Microsoft’s recommended approach is to follow the “async all the way” principle. If you control both the caller and callee, prefer making everything async.

Strengthen Your C# and Async/Await Skills

Asynchronous programming can be challenging, especially in larger applications. If you’re aiming to level up your knowledge of C# and learn best coding practices, consider exploring these resources on DesignGurus.io:

Additionally, check out the DesignGurus.io YouTube channel for free tutorials and in-depth discussions around coding interviews, system design fundamentals, and advanced programming tips.

Bottom Line

  • Preferred: Let the async method remain async and call it with await from an async method.
  • Fallback: If forced to call it from a synchronous context (and you cannot change the API), use .GetAwaiter().GetResult() or .Result cautiously, aware of potential deadlocks and performance hits.
CONTRIBUTOR
TechGrind