CS1998如何在没有警告的情况下实现同步任务返回方法?
How to implement synchronous Task-returning method without warning CS1998?
以如下界面为例:
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
关键字完全符合我的要求,但它会产生编译器警告,我认为禁止这些关键字是不明智的。
我应该如何重构我的方法实现以不产生编译器警告,同时像任何其他 async
方法一样用 Task
包装所有异常?
我用来从产生编译器警告 CS1998 的 async
版本转换为行为相同的非 async
版本的机械翻译如下。
- 删除
async
关键字。
- 用
try
-catch
包裹整个现有的方法体。
- 在
try
-catch
之前定义一个名为tcs
的TaskCompletionSource<T>
。
- 将
return <expr>;
的所有现有实例替换为 tcs.SetResult(<expr>);
,然后是 return tcs.Task;
。
- 定义要调用
tcs.SetException(e)
的 catch
块,然后调用 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;
}
}
这可以通过以下更一般地表达,尽管我不知道将这样的辅助方法实际引入代码库是否合适。
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;
}
如果您使用的是 .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;
}
以如下界面为例:
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
关键字完全符合我的要求,但它会产生编译器警告,我认为禁止这些关键字是不明智的。
我应该如何重构我的方法实现以不产生编译器警告,同时像任何其他 async
方法一样用 Task
包装所有异常?
我用来从产生编译器警告 CS1998 的 async
版本转换为行为相同的非 async
版本的机械翻译如下。
- 删除
async
关键字。 - 用
try
-catch
包裹整个现有的方法体。 - 在
try
-catch
之前定义一个名为tcs
的TaskCompletionSource<T>
。 - 将
return <expr>;
的所有现有实例替换为tcs.SetResult(<expr>);
,然后是return tcs.Task;
。 - 定义要调用
tcs.SetException(e)
的catch
块,然后调用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;
}
}
这可以通过以下更一般地表达,尽管我不知道将这样的辅助方法实际引入代码库是否合适。
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;
}
如果您使用的是 .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;
}