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?
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.
async
keyword. try
- catch
. TaskCompletionSource<T>
called tcs
before the try
- catch
. return <expr>;
with tcs.SetResult(<expr>);
followed by return tcs.Task;
. 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;
}
If you're using .NET 4.6, you can use Task.FromException
to handle the exception case, just as you use FromResult
to handle the successful case:
public Task<string> GetAnswerAsync(string question)
{
try
{
return Task.FromResult(Lookup[question]);
}
catch(Exception e)
{
return Task.FromException<string>(e);
}
}
If you're using .NET 4.5 then you'll need to write your own FromException
method, but it's pretty trivial:
public static Task<T> FromException<T>(Exception e)
{
var tcs = new TaskCompletionSource<T>();
tcs.SetException(e);
return tcs.Task;
}
public static Task FromException(Exception e)
{
var tcs = new TaskCompletionSource<bool>();
tcs.SetException(e);
return tcs.Task;
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.