警告 CS1998 这种异步方法缺少“等待”运算符,正确的静音方法是什么?

ose*_*ert 0 c# async-await

这是我的测试:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace asynktest
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var t = Test1(true);
            await t; //throws InvalidOperationException here. correct
            t = Test1(false);
            await t; //throws NotImplementedException here. correct

            t = Test2(true); //throws InvalidOperationException here. wrong
            await t;
            t = Test2(false); //throws NotImplementedException here. wrong
            await t;

            t = Test3(true);
            await t; //throws InvalidOperationException here. correct
            t = Test3(false); //throws NotImplementedException here. wrong
            await t;

            t = Test4(true);
            await t; //throws InvalidOperationException here. correct
            t = Test4(false);
            await t; //throws NotImplementedException here. correct

            t = Test5(true);
            await t; //throws InvalidOperationException here. correct
            t = Test5(false);
            await t; //throws NotImplementedException here. correct
        }

        public static async Task<int> Test1(bool err) //CS1998: This async method lacks 'await'
        {
            if (err)
                throw new InvalidOperationException();
            return GetNum(42);
        }

        public static Task<int> Test2(bool err)
        {
            if (err)
                throw new InvalidOperationException();
            return Task.FromResult(GetNum(42));
        }

        public static Task<int> Test3(bool err)
        {
            if (err)
                return Task.FromException<int>(new InvalidOperationException());
            return Task.FromResult(GetNum(42));
        }

        public static async Task<int> Test4(bool err)
        {
            await Task.CompletedTask; // remove CS1998
            if (err)
                throw new InvalidOperationException();
            return GetNum(42);
        }

        public static Task<int> Test5(bool err)
        {
            try
            {
                if (err)
                    return Task.FromException<int>(new InvalidOperationException());
                return Task.FromResult(GetNum(42));
            }
            catch (Exception e)
            {
                return Task.FromException<int>(e);
            }
        }
        public static int GetNum(int num)
        {
            throw new NotImplementedException();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Test1 生成我想要修复的警告。只有 Test4 和 Test5 不会改变程序流程。但是 Test5 需要大量的样板代码。真的可以替代方案是在我的程序中散布“await Task.CompletedTask”或添加疯狂数量的愚蠢Task.FromX代码吗?在这一点上我真的觉得CS1998是完全错误的,必须被静音。或者我错过了什么?

编辑:作为结果删除任务不是一个选项,它通常是一个接口(我无法控制)实现。

Edit2:程序流程是这里的关键。更改程序以在不同的地方抛出异常是不好的。想象一个程序创建 3 个任务,关闭核反应堆,然后 Task.WaitAll(t1, t2, t3)。保留代码原样或 Test4\Test5,否则反应器会爆炸:-)

Edit3:我经常读到 async 创建一个不必要的状态机(性能),但从我的例子中你可以看到这并不完全正确。没有这个状态机,它会改变程序流程。当然,这必须与强制异步接口实现有关,否则你只会让它同步,我想这里 CS1998 可能会有所帮助:-) 但是 CS1998 不够聪明,无法理解这是你可以控制的代码还是不是。棘手...

Edit4:我最终忽略了警告,但是包装器的答案是一个非常好的选择。但我希望微软可以制作一个补充的“同步”关键字,除了在“异步”时生成状态机外,还可以自动生成这些包装器。

Joh*_*lay 5

删除任务作为结果不是一个选项,它通常是一个接口(我无法控制)实现

使用async关键字创建状态机,如果实现是同步的,则不需要。

如果方法必须返回Task<TResult>以满足接口约定,请使用Task.FromResult().

对于返回非泛型的方法Task,返回Task.CompletedTask


在异常处理的情况下,使用Task.FromException

public static Task<int> Test3(bool err)
{
    if (err) return Task.FromException<int>(new InvalidOperationException());
    
    try { return Task.FromResult(GetNum(42)); }
    catch (Exception e) { return Task.FromException<int>(e); }
}
Run Code Online (Sandbox Code Playgroud)

如果样板的数量是一个问题,那么如何使用这些包装器:

Task RunAsTask(Action action)
{
    try { action(); }
    catch (Exception e) { return Task.FromException(e); }
    return Task.CompletedTask;
}

Task<TResult> RunAsTask<TResult>(Func<TResult> func)
{
    try { return Task.FromResult(func()); }
    catch (Exception e) { return Task.FromException<TResult>(e); }
}
Run Code Online (Sandbox Code Playgroud)

然后你可以同步编写你的方法:

public static Task<int> Test1(bool err) => RunAsTask(() =>
{
    if (err)
        throw new InvalidOperationException();
    return GetNum(42);
});
Run Code Online (Sandbox Code Playgroud)