Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement synchronous Task-returning method without warning CS1998?

Take for example the following interface:

interface IOracle
{
    Task<string> GetAnswerAsync(string question);
}

Some implementations of this interface might use async/await. Others might not need to. For example, consider this simple toy implementation.

class SimpleOracle
{
    public Dictionary<string, string> Lookup { get; set; }

    // Warning CS1998: This async method lacks 'await' operators
    // and will run synchonously.
    public async Task<string> GetAnswerAsync(string question)
    {
        string answer = Lookup[question];
        return answer;
    }
}

The compiler warning CS1998 of course makes sense. The usual suggestion is to remove the async keyword and use Task.FromResult, but it misses a subtle issue. What if the code throws an exception? Then that code transform changes the behavior of the method: the async version will wrap any exception in a Task; the non-async version will not, without an explict try-catch.

Leaving the async keyword works exactly as I want, but it produces a compiler warning, and I don't think suppressing those is wise.

How should I refactor my method implementation to not produce a compiler warning while also wrapping all exceptions with Task as any other async method would?

like image 944
Timothy Shields Avatar asked Jun 15 '15 18:06

Timothy Shields


People also ask

How to return Task from async method?

Return Task from asynchronous method When a method returns a task, we can use the await keyword to call it. Note: The await keyword should be within a function qualified by an async keyword (in other words asynchronous), otherwise the compiler will treat it as a normal synchronous function.

What return types are allowed for asynchronous methods?

Async methods can have the following return types: Task, for an async method that performs an operation but returns no value. Task<TResult>, for an async method that returns a value. void , for an event handler.

How do you call a synchronous method asynchronously in C#?

The simplest way to execute a method asynchronously is to start executing the method by calling the delegate's BeginInvoke method, do some work on the main thread, and then call the delegate's EndInvoke method. EndInvoke might block the calling thread because it does not return until the asynchronous call completes.

Can we use await with synchronous method?

Async/await helps you write synchronous-looking JavaScript code that works asynchronously. Await is in an async function to ensure that all promises that are returned in the function are synchronized.


1 Answers

The mechanical translation I use to convert from the async version that yields compiler warning CS1998 to a non-async version that behaves identically is as follows.

  • Remove the async keyword.
  • Wrap the entire existing method body with a try-catch.
  • Define a TaskCompletionSource<T> called tcs before the try-catch.
  • Replace all existing instances of return <expr>; with tcs.SetResult(<expr>); followed by return tcs.Task;.
  • Define the catch block to call tcs.SetException(e) followed by return tcs.Task;.

For example:

public Task<string> GetAnswerAsync(string question)
{
    var tcs = new TaskCompletionSource<string>();
    try
    {
        string answer = Lookup[question];
        tcs.SetResult(answer);
        return tcs.Task;
    }
    catch (Exception e)
    {
        tcs.SetException(e);
        return tcs.Task;
    }
}

This can be expressed more generally by the following, although I don't know if it would be appropriate to actually introduce such a helper method into a codebase.

public static Task<T> AsyncPattern(Func<T> func)
{
    var tcs = new TaskCompletionSource<T>();
    try
    {
        tcs.SetResult(func());
    }
    catch (Exception e)
    {
        tcs.SetException(e);
    }
    return tcs.Task;
}
like image 176
Timothy Shields Avatar answered Oct 10 '22 13:10

Timothy Shields