以下面的界面为例:
interface IOracle
{
Task<string> GetAnswerAsync(string question);
}
此界面的某些实现可能会使用async
/ await
。其他人可能不需要。例如,考虑这个简单的玩具实现。
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;
}
}
编译器警告CS1998当然有道理。通常的建议是remove the async
keyword and use Task.FromResult
,但它错过了一个微妙的问题。如果代码抛出异常怎么办?然后,该代码转换会更改方法的行为:async
版本将在Task
中包装任何异常;非async
版本不会,没有明确try
- catch
。
让async
关键字完全按照我的意愿运行,但它会产生编译器警告,我不认为压制它们是明智的。
我应该如何重构我的方法实现以不产生编译器警告,同时用Task
包装所有异常作为任何其他async
方法?
答案 0 :(得分:6)
我用来将产生编译器警告CS1998的async
版本转换为行为相同的非async
版本的机械翻译如下所示。
async
关键字。try
- catch
。TaskCompletionSource<T>
- tcs
之前定义名为try
的{{1}}。catch
的所有现有实例替换为return <expr>;
,后跟tcs.SetResult(<expr>);
。return tcs.Task;
块以呼叫catch
,然后tcs.SetException(e)
。例如:
return tcs.Task;
这可以通过以下方式更一般地表达,虽然我不知道将这样的辅助方法实际引入代码库是否合适。
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;
}
}
答案 1 :(得分:5)
如果您使用的是.NET 4.6,则可以使用Task.FromException
来处理异常情况,就像使用FromResult
来处理成功案例一样:
public Task<string> GetAnswerAsync(string question)
{
try
{
return Task.FromResult(Lookup[question]);
}
catch(Exception e)
{
return Task.FromException<string>(e);
}
}
如果您使用的是.NET 4.5,那么您需要编写自己的FromException
方法,但这非常简单:
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;
}