如何实现同步的返回任务方法而不出现CS1998警告?

13
例如,考虑以下界面:
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是有道理的。通常的建议是 删除 async 关键字并使用 Task.FromResult, 但它忽略了一个微妙的问题。如果代码抛出异常会怎么样?那么该代码转换将更改方法的行为: async 版本将在 Task 中包装任何异常;而非 async 版本则不会,除非使用显式的 try-catch
保留 async 关键字可以完全按照我的意愿工作,但它会产生一个编译器警告,我认为抑制这些警告是不明智的。
如何重构我的方法实现,以避免产生编译器警告,并像任何其他 async 方法一样在 Task 中包装所有异常?
2个回答

12

我用机械翻译的方式将产生编译警告CS1998的async版本转换为与其完全相同的非async版本,具体步骤如下:

  • 去除async关键字。
  • 在现有方法体周围加上try-catch块。
  • try-catch块之前定义一个名为tcsTaskCompletionSource<T>
  • 将所有现有的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;
    }
}

虽然我不知道是否适合在代码库中引入这样的帮助方法,但以下内容可以更加普遍地表达。

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;
}

查看这个这个,了解另一种选择:Task.RunSynchronously。我个人更喜欢它,而不是TaskCompletionSource方法。 - noseratio - open to work

8

如果您正在使用.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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接