【问题标题】:Should I worry about "This async method lacks 'await' operators and will run synchronously" warning我是否应该担心“此异步方法缺少 'await' 运算符并将同步运行”警告
【发布时间】:2015-07-07 12:40:04
【问题描述】:

我有一个接口,它公开了一些异步方法。更具体地说,它定义了返回TaskTask<T> 的方法。我正在使用 async/await 关键字。

我正在实现这个接口。然而,在其中一些方法中,这个实现没有任何等待。出于这个原因,我收到编译器警告“CS1998:此异步方法缺少 'await' 运算符并将同步运行......”

我明白为什么我会收到这些警告,但我想知道在这种情况下我是否应该对它们做任何事情。忽略编译器警告感觉不对。

我知道我可以通过等待Task.Run 来修复它,但是对于只执行一些廉价操作的方法来说,这感觉不对。听起来它也会给执行增加不必要的开销,但我也不确定这是否已经存在,因为存在 async 关键字。

我应该忽略这些警告,还是有办法解决我没有看到的问题?

【问题讨论】:

  • 这将取决于具体情况。您确定要同步执行这些操作吗?如果您确实希望它们同步执行,为什么将方法标记为async
  • 只需删除 async 关键字。您仍然可以使用Task.FromResult 返回Task
  • @BenVoigt Google 有很多关于它的信息,如果 OP 还不知道的话。
  • @BenVoigt Michael Liu 不是已经提供了那个提示吗?使用Task.FromResult
  • @hvd:后来被编辑到他的评论中。

标签: c# .net asynchronous async-await task


【解决方案1】:

async 关键字只是方法的实现细节;它不是方法签名的一部分。如果一个特定的方法实现或覆盖没有等待,那么只需省略 async 关键字并使用Task.FromResult<TResult>返回一个已完成的任务:

public Task<string> Foo()               //    public async Task<string> Foo()
{                                       //    {
    Baz();                              //        Baz();
    return Task.FromResult("Hello");    //        return "Hello";
}                                       //    }

如果您的方法返回Task 而不是Task<TResult>,那么您可以返回任何类型和值的已完成任务。 Task.FromResult(0) 似乎是一个受欢迎的选择:

public Task Bar()                       //    public async Task Bar()
{                                       //    {
    Baz();                              //        Baz();
    return Task.FromResult(0);          //
}                                       //    }

或者,从 .NET Framework 4.6 开始,您可以返回 Task.CompletedTask

public Task Bar()                       //    public async Task Bar()
{                                       //    {
    Baz();                              //        Baz();
    return Task.CompletedTask;          //
}                                       //    }

【讨论】:

  • 谢谢,我认为我缺少的是创建一个已完成任务的概念,而不是返回一个实际任务,就像你说的那样,它与使用 async 关键字相同。现在看起来很明显,但我只是没有看到它!
  • Task 可以按照 Task.Empty 的方式使用静态成员来实现此目的。意图会更清楚一点,想到所有这些尽职尽责的任务返回一个从不需要的零,我就感到痛苦。
  • await Task.FromResult(0)? await Task.Yield() 怎么样?
  • @Sushi271:不,在非async 方法中,您返回 Task.FromResult(0) 而不是等待它。
  • 其实不,异步不仅仅是一个实现细节,还有很多细节需要注意:)。必须知道,哪些部分同步运行,哪些部分异步运行,当前同步上下文是什么,仅作记录,任务总是快一点,因为幕后没有状态机:)。
【解决方案2】:

一些“异步”操作同步完成,但为了多态性仍然符合异步调用模型,这是完全合理的。

OS I/O API 就是一个真实的例子。某些设备上的异步和重叠调用总是内联完成(例如,写入使用共享内存实现的管道)。但是它们实现了与在后台继续执行的多部分操作相同的接口。

【讨论】:

    【解决方案3】:

    可能为时已晚,但它可能是有用的调查:

    关于编译代码的内部结构(IL):

     public static async Task<int> GetTestData()
        {
            return 12;
        }
    

    在 IL 中变成了:

    .method private hidebysig static class [mscorlib]System.Threading.Tasks.Task`1<int32> 
            GetTestData() cil managed
    {
      .custom instance void [mscorlib]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [mscorlib]System.Type) = ( 01 00 28 55 73 61 67 65 4C 69 62 72 61 72 79 2E   // ..(UsageLibrary.
                                                                                                                                         53 74 61 72 74 54 79 70 65 2B 3C 47 65 74 54 65   // StartType+<GetTe
                                                                                                                                         73 74 44 61 74 61 3E 64 5F 5F 31 00 00 )          // stData>d__1..
      .custom instance void [mscorlib]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = ( 01 00 00 00 ) 
      // Code size       52 (0x34)
      .maxstack  2
      .locals init ([0] class UsageLibrary.StartType/'<GetTestData>d__1' V_0,
               [1] valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> V_1)
      IL_0000:  newobj     instance void UsageLibrary.StartType/'<GetTestData>d__1'::.ctor()
      IL_0005:  stloc.0
      IL_0006:  ldloc.0
      IL_0007:  call       valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<!0> valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::Create()
      IL_000c:  stfld      valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> UsageLibrary.StartType/'<GetTestData>d__1'::'<>t__builder'
      IL_0011:  ldloc.0
      IL_0012:  ldc.i4.m1
      IL_0013:  stfld      int32 UsageLibrary.StartType/'<GetTestData>d__1'::'<>1__state'
      IL_0018:  ldloc.0
      IL_0019:  ldfld      valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> UsageLibrary.StartType/'<GetTestData>d__1'::'<>t__builder'
      IL_001e:  stloc.1
      IL_001f:  ldloca.s   V_1
      IL_0021:  ldloca.s   V_0
      IL_0023:  call       instance void valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::Start<class UsageLibrary.StartType/'<GetTestData>d__1'>(!!0&)
      IL_0028:  ldloc.0
      IL_0029:  ldflda     valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> UsageLibrary.StartType/'<GetTestData>d__1'::'<>t__builder'
      IL_002e:  call       instance class [mscorlib]System.Threading.Tasks.Task`1<!0> valuetype [mscorlib]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::get_Task()
      IL_0033:  ret
    } // end of method StartType::GetTestData
    

    并且没有异步和任务方法:

     public static int GetTestData()
            {
                return 12;
            }
    

    变成:

    .method private hidebysig static int32  GetTestData() cil managed
    {
      // Code size       8 (0x8)
      .maxstack  1
      .locals init ([0] int32 V_0)
      IL_0000:  nop
      IL_0001:  ldc.i4.s   12
      IL_0003:  stloc.0
      IL_0004:  br.s       IL_0006
      IL_0006:  ldloc.0
      IL_0007:  ret
    } // end of method StartType::GetTestData
    

    您可以看到这些方法之间的巨大差异。如果您不在 async 方法中使用 await 并且不关心使用 async 方法(例如 API 调用或事件处理程序),那么最好将其转换为普通同步方法(它可以节省您的应用程序性能)。

    更新:

    还有来自microsoft docs的补充信息:

    async 方法的主体中需要有一个 await 关键字,否则它们会 永不屈服!记住这一点很重要。如果没有使用 await 在异步方法的主体中,C# 编译器将生成一个 警告,但代码将像正常一样编译和运行 方法。请注意,这也会非常低效,因为 C# 编译器为异步方法生成的状态机将 什么都做不了。

    【讨论】:

    • 此外,您对使用async/await 的最终结论被大大简化了,因为您将其基于您对受CPU 限制的单个操作的不切实际示例。 Tasks 如果使用得当,由于并发任务(即并行)和更好的线程管理和使用,可以提高应用程序性能和响应能力
    • 这只是我在这篇文章中所说的测试简化示例。我还提到了对 api 和事件处理程序的请求,其中可能使用两种版本的方法(异步和常规)。 PO 还谈到了使用 async 方法而不在内部等待。我的帖子是关于它,但不是关于正确使用Tasks。您没有阅读全文并快速得出结论,这是一个可悲的故事。
    • 返回 int 的方法(如您的情况)与返回 Task 的方法之间存在差异,如 OP 所讨论的。阅读他的 帖子和接受的答案再次,而不是亲自处理。在这种情况下,您的回答没有帮助。您甚至不必费心去展示一个内部是否有await 的方法之间的区别。现在如果你这样做了,那将是非常好非常值得一票
    • 我猜你真的不明白异步方法和使用 api 或事件处理程序调用的常规方法之间的区别。我的帖子里特别提到了。很抱歉您又再次错过了。
    【解决方案4】:

    仅当您实际调用所涉及的方法时,并且仅当关注性能时。

    这可以通过编写包含以下 4 个方法的程序来演示,然后将它们反编译为 IL(请注意,提供的 IL 可能会在运行时版本之间发生变化;以下来自 .NET Core 3.1):

    int FullySync() => 42;
    
    Task<int> TaskSync() => Task.FromResult(42);
    
    // CS1998
    async Task<int> NotActuallyAsync() => 42;
    
    async Task<int> FullyAsync() => await Task.Run(() => 42);
    

    前两个导致方法体非常短,其中包含您所期望的内容:

    .method private hidebysig 
        instance int32 FullySync () cil managed 
    {
        // Method begins at RVA 0x205e
        // Code size 3 (0x3)
        .maxstack 8
    
        // return 42;
        IL_0000: ldc.i4.s 42
        IL_0002: ret
    } // end of method Program::FullySync
    
    .method private hidebysig 
        instance class [System.Runtime]System.Threading.Tasks.Task`1<int32> TaskSync () cil managed 
    {
        // Method begins at RVA 0x2062
        // Code size 8 (0x8)
        .maxstack 8
    
        // return Task.FromResult(42);
        IL_0000: ldc.i4.s 42
        IL_0002: call class [System.Runtime]System.Threading.Tasks.Task`1<!!0> [System.Runtime]System.Threading.Tasks.Task::FromResult<int32>(!!0)
        IL_0007: ret
    } // end of method Program::TaskSync
    

    但在最后两个上存在async 关键字会导致编译器为这些方法生成asynchronous state machines

    .method private hidebysig 
        instance class [System.Runtime]System.Threading.Tasks.Task`1<int32> NotActuallyAsync () cil managed 
    {
        .custom instance void [System.Runtime]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [System.Runtime]System.Type) = (
            01 00 29 43 53 31 39 39 38 54 65 73 74 2e 50 72
            6f 67 72 61 6d 2b 3c 4e 6f 74 41 63 74 75 61 6c
            6c 79 41 73 79 6e 63 3e 64 5f 5f 33 00 00
        )
        .custom instance void [System.Diagnostics.Debug]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = (
            01 00 00 00
        )
        // Method begins at RVA 0x206c
        // Code size 56 (0x38)
        .maxstack 2
        .locals init (
            [0] class CS1998Test.Program/'<NotActuallyAsync>d__3'
        )
    
        IL_0000: newobj instance void CS1998Test.Program/'<NotActuallyAsync>d__3'::.ctor()
        IL_0005: stloc.0
        IL_0006: ldloc.0
        IL_0007: call valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<!0> valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::Create()
        IL_000c: stfld valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> CS1998Test.Program/'<NotActuallyAsync>d__3'::'<>t__builder'
        IL_0011: ldloc.0
        IL_0012: ldarg.0
        IL_0013: stfld class CS1998Test.Program CS1998Test.Program/'<NotActuallyAsync>d__3'::'<>4__this'
        IL_0018: ldloc.0
        IL_0019: ldc.i4.m1
        IL_001a: stfld int32 CS1998Test.Program/'<NotActuallyAsync>d__3'::'<>1__state'
        IL_001f: ldloc.0
        IL_0020: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> CS1998Test.Program/'<NotActuallyAsync>d__3'::'<>t__builder'
        IL_0025: ldloca.s 0
        IL_0027: call instance void valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::Start<class CS1998Test.Program/'<NotActuallyAsync>d__3'>(!!0&)
        IL_002c: ldloc.0
        IL_002d: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> CS1998Test.Program/'<NotActuallyAsync>d__3'::'<>t__builder'
        IL_0032: call instance class [System.Runtime]System.Threading.Tasks.Task`1<!0> valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::get_Task()
        IL_0037: ret
    } // end of method Program::NotActuallyAsync
    
    .class nested private auto ansi sealed beforefieldinit '<NotActuallyAsync>d__3'
        extends [System.Runtime]System.Object
        implements [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine
    {
        .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
            01 00 00 00
        )
        // Fields
        .field public int32 '<>1__state'
        .field public valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> '<>t__builder'
        .field public class CS1998Test.Program '<>4__this'
    
        // Methods
        .method public hidebysig specialname rtspecialname 
            instance void .ctor () cil managed 
        {
            // Method begins at RVA 0x20fd
            // Code size 8 (0x8)
            .maxstack 8
    
            // {
            IL_0000: ldarg.0
            // (no C# code)
            IL_0001: call instance void [System.Runtime]System.Object::.ctor()
            // }
            IL_0006: nop
            IL_0007: ret
        } // end of method '<NotActuallyAsync>d__3'::.ctor
    
        .method private final hidebysig newslot virtual 
            instance void MoveNext () cil managed 
        {
            .override method instance void [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext()
            // Method begins at RVA 0x2108
            // Code size 58 (0x3a)
            .maxstack 2
            .locals init (
                [0] int32,
                [1] int32,
                [2] class [System.Runtime]System.Exception
            )
    
            // int num = <>1__state;
            IL_0000: ldarg.0
            IL_0001: ldfld int32 CS1998Test.Program/'<NotActuallyAsync>d__3'::'<>1__state'
            IL_0006: stloc.0
            .try
            {
                // result = 42;
                IL_0007: ldc.i4.s 42
                IL_0009: stloc.1
                // }
                IL_000a: leave.s IL_0024
            } // end .try
            catch [System.Runtime]System.Exception
            {
                // catch (Exception exception)
                IL_000c: stloc.2
                // <>1__state = -2;
                IL_000d: ldarg.0
                IL_000e: ldc.i4.s -2
                IL_0010: stfld int32 CS1998Test.Program/'<NotActuallyAsync>d__3'::'<>1__state'
                // <>t__builder.SetException(exception);
                IL_0015: ldarg.0
                IL_0016: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> CS1998Test.Program/'<NotActuallyAsync>d__3'::'<>t__builder'
                IL_001b: ldloc.2
                IL_001c: call instance void valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::SetException(class [System.Runtime]System.Exception)
                // return;
                IL_0021: nop
                IL_0022: leave.s IL_0039
            } // end handler
    
            // <>1__state = -2;
            IL_0024: ldarg.0
            IL_0025: ldc.i4.s -2
            IL_0027: stfld int32 CS1998Test.Program/'<NotActuallyAsync>d__3'::'<>1__state'
            // <>t__builder.SetResult(result);
            IL_002c: ldarg.0
            IL_002d: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> CS1998Test.Program/'<NotActuallyAsync>d__3'::'<>t__builder'
            IL_0032: ldloc.1
            IL_0033: call instance void valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::SetResult(!0)
            // }
            IL_0038: nop
    
            IL_0039: ret
        } // end of method '<NotActuallyAsync>d__3'::MoveNext
    
        .method private final hidebysig newslot virtual 
            instance void SetStateMachine (
                class [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine stateMachine
            ) cil managed 
        {
            .custom instance void [System.Diagnostics.Debug]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = (
                01 00 00 00
            )
            .override method instance void [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine::SetStateMachine(class [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine)
            // Method begins at RVA 0x2160
            // Code size 1 (0x1)
            .maxstack 8
    
            // }
            IL_0000: ret
        } // end of method '<NotActuallyAsync>d__3'::SetStateMachine
    
    } // end of class <NotActuallyAsync>d__3
    
    .method private hidebysig 
        instance class [System.Runtime]System.Threading.Tasks.Task`1<int32> FullyAsync () cil managed 
    {
        .custom instance void [System.Runtime]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [System.Runtime]System.Type) = (
            01 00 23 43 53 31 39 39 38 54 65 73 74 2e 50 72
            6f 67 72 61 6d 2b 3c 46 75 6c 6c 79 41 73 79 6e
            63 3e 64 5f 5f 34 00 00
        )
        .custom instance void [System.Diagnostics.Debug]System.Diagnostics.DebuggerStepThroughAttribute::.ctor() = (
            01 00 00 00
        )
        // Method begins at RVA 0x20b0
        // Code size 56 (0x38)
        .maxstack 2
        .locals init (
            [0] class CS1998Test.Program/'<FullyAsync>d__4'
        )
    
        IL_0000: newobj instance void CS1998Test.Program/'<FullyAsync>d__4'::.ctor()
        IL_0005: stloc.0
        IL_0006: ldloc.0
        IL_0007: call valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<!0> valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::Create()
        IL_000c: stfld valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> CS1998Test.Program/'<FullyAsync>d__4'::'<>t__builder'
        IL_0011: ldloc.0
        IL_0012: ldarg.0
        IL_0013: stfld class CS1998Test.Program CS1998Test.Program/'<FullyAsync>d__4'::'<>4__this'
        IL_0018: ldloc.0
        IL_0019: ldc.i4.m1
        IL_001a: stfld int32 CS1998Test.Program/'<FullyAsync>d__4'::'<>1__state'
        IL_001f: ldloc.0
        IL_0020: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> CS1998Test.Program/'<FullyAsync>d__4'::'<>t__builder'
        IL_0025: ldloca.s 0
        IL_0027: call instance void valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::Start<class CS1998Test.Program/'<FullyAsync>d__4'>(!!0&)
        IL_002c: ldloc.0
        IL_002d: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> CS1998Test.Program/'<FullyAsync>d__4'::'<>t__builder'
        IL_0032: call instance class [System.Runtime]System.Threading.Tasks.Task`1<!0> valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::get_Task()
        IL_0037: ret
    } // end of method Program::FullyAsync
    
    .class nested private auto ansi sealed beforefieldinit '<FullyAsync>d__4'
        extends [System.Runtime]System.Object
        implements [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine
    {
        .custom instance void [System.Runtime]System.Runtime.CompilerServices.CompilerGeneratedAttribute::.ctor() = (
            01 00 00 00
        )
        // Fields
        .field public int32 '<>1__state'
        .field public valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> '<>t__builder'
        .field public class CS1998Test.Program '<>4__this'
        .field private int32 '<>s__1'
        .field private valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<int32> '<>u__1'
    
        // Methods
        .method public hidebysig specialname rtspecialname 
            instance void .ctor () cil managed 
        {
            // Method begins at RVA 0x217b
            // Code size 8 (0x8)
            .maxstack 8
    
            // {
            IL_0000: ldarg.0
            // (no C# code)
            IL_0001: call instance void [System.Runtime]System.Object::.ctor()
            // }
            IL_0006: nop
            IL_0007: ret
        } // end of method '<FullyAsync>d__4'::.ctor
    
        .method private final hidebysig newslot virtual 
            instance void MoveNext () cil managed 
        {
            .override method instance void [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine::MoveNext()
            // Method begins at RVA 0x2184
            // Code size 199 (0xc7)
            .maxstack 3
            .locals init (
                [0] int32,
                [1] int32,
                [2] valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<int32>,
                [3] class CS1998Test.Program/'<FullyAsync>d__4',
                [4] class [System.Runtime]System.Exception
            )
    
            // int num = <>1__state;
            IL_0000: ldarg.0
            IL_0001: ldfld int32 CS1998Test.Program/'<FullyAsync>d__4'::'<>1__state'
            IL_0006: stloc.0
            .try
            {
                // if (num != 0)
                IL_0007: ldloc.0
                IL_0008: brfalse.s IL_000c
    
                // (no C# code)
                IL_000a: br.s IL_000e
    
                // awaiter = Task.Run(() => 42).GetAwaiter();
                IL_000c: br.s IL_0065
    
                IL_000e: ldsfld class [System.Runtime]System.Func`1<int32> CS1998Test.Program/'<>c'::'<>9__4_0'
                IL_0013: dup
                IL_0014: brtrue.s IL_002d
    
                // (no C# code)
                IL_0016: pop
                // if (!awaiter.IsCompleted)
                IL_0017: ldsfld class CS1998Test.Program/'<>c' CS1998Test.Program/'<>c'::'<>9'
                IL_001c: ldftn instance int32 CS1998Test.Program/'<>c'::'<FullyAsync>b__4_0'()
                IL_0022: newobj instance void class [System.Runtime]System.Func`1<int32>::.ctor(object, native int)
                IL_0027: dup
                IL_0028: stsfld class [System.Runtime]System.Func`1<int32> CS1998Test.Program/'<>c'::'<>9__4_0'
    
                IL_002d: call class [System.Runtime]System.Threading.Tasks.Task`1<!!0> [System.Runtime]System.Threading.Tasks.Task::Run<int32>(class [System.Runtime]System.Func`1<!!0>)
                IL_0032: callvirt instance valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<!0> class [System.Runtime]System.Threading.Tasks.Task`1<int32>::GetAwaiter()
                IL_0037: stloc.2
                IL_0038: ldloca.s 2
                IL_003a: call instance bool valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<int32>::get_IsCompleted()
                IL_003f: brtrue.s IL_0081
    
                // num = (<>1__state = 0);
                IL_0041: ldarg.0
                IL_0042: ldc.i4.0
                IL_0043: dup
                IL_0044: stloc.0
                IL_0045: stfld int32 CS1998Test.Program/'<FullyAsync>d__4'::'<>1__state'
                // <>u__1 = awaiter;
                IL_004a: ldarg.0
                IL_004b: ldloc.2
                IL_004c: stfld valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<int32> CS1998Test.Program/'<FullyAsync>d__4'::'<>u__1'
                // <FullyAsync>d__4 stateMachine = this;
                IL_0051: ldarg.0
                IL_0052: stloc.3
                // <>t__builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);
                IL_0053: ldarg.0
                IL_0054: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> CS1998Test.Program/'<FullyAsync>d__4'::'<>t__builder'
                IL_0059: ldloca.s 2
                IL_005b: ldloca.s 3
                IL_005d: call instance void valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::AwaitUnsafeOnCompleted<valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<int32>, class CS1998Test.Program/'<FullyAsync>d__4'>(!!0&, !!1&)
                // return;
                IL_0062: nop
                IL_0063: leave.s IL_00c6
    
                // awaiter = <>u__1;
                IL_0065: ldarg.0
                IL_0066: ldfld valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<int32> CS1998Test.Program/'<FullyAsync>d__4'::'<>u__1'
                IL_006b: stloc.2
                // <>u__1 = default(TaskAwaiter<int>);
                IL_006c: ldarg.0
                IL_006d: ldflda valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<int32> CS1998Test.Program/'<FullyAsync>d__4'::'<>u__1'
                IL_0072: initobj valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<int32>
                // num = (<>1__state = -1);
                IL_0078: ldarg.0
                IL_0079: ldc.i4.m1
                IL_007a: dup
                IL_007b: stloc.0
                IL_007c: stfld int32 CS1998Test.Program/'<FullyAsync>d__4'::'<>1__state'
    
                // <>s__1 = awaiter.GetResult();
                IL_0081: ldarg.0
                IL_0082: ldloca.s 2
                IL_0084: call instance !0 valuetype [System.Runtime]System.Runtime.CompilerServices.TaskAwaiter`1<int32>::GetResult()
                IL_0089: stfld int32 CS1998Test.Program/'<FullyAsync>d__4'::'<>s__1'
                // result = <>s__1;
                IL_008e: ldarg.0
                IL_008f: ldfld int32 CS1998Test.Program/'<FullyAsync>d__4'::'<>s__1'
                IL_0094: stloc.1
                // }
                IL_0095: leave.s IL_00b1
            } // end .try
            catch [System.Runtime]System.Exception
            {
                // catch (Exception exception)
                IL_0097: stloc.s 4
                // <>1__state = -2;
                IL_0099: ldarg.0
                IL_009a: ldc.i4.s -2
                IL_009c: stfld int32 CS1998Test.Program/'<FullyAsync>d__4'::'<>1__state'
                // <>t__builder.SetException(exception);
                IL_00a1: ldarg.0
                IL_00a2: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> CS1998Test.Program/'<FullyAsync>d__4'::'<>t__builder'
                IL_00a7: ldloc.s 4
                IL_00a9: call instance void valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::SetException(class [System.Runtime]System.Exception)
                // return;
                IL_00ae: nop
                IL_00af: leave.s IL_00c6
            } // end handler
    
            // <>1__state = -2;
            IL_00b1: ldarg.0
            IL_00b2: ldc.i4.s -2
            IL_00b4: stfld int32 CS1998Test.Program/'<FullyAsync>d__4'::'<>1__state'
            // <>t__builder.SetResult(result);
            IL_00b9: ldarg.0
            IL_00ba: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32> CS1998Test.Program/'<FullyAsync>d__4'::'<>t__builder'
            IL_00bf: ldloc.1
            IL_00c0: call instance void valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<int32>::SetResult(!0)
            // }
            IL_00c5: nop
    
            IL_00c6: ret
        } // end of method '<FullyAsync>d__4'::MoveNext
    
        .method private final hidebysig newslot virtual 
            instance void SetStateMachine (
                class [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine stateMachine
            ) cil managed 
        {
            .custom instance void [System.Diagnostics.Debug]System.Diagnostics.DebuggerHiddenAttribute::.ctor() = (
                01 00 00 00
            )
            .override method instance void [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine::SetStateMachine(class [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine)
            // Method begins at RVA 0x2268
            // Code size 1 (0x1)
            .maxstack 8
    
            // }
            IL_0000: ret
        } // end of method '<FullyAsync>d__4'::SetStateMachine
    
    } // end of class <FullyAsync>d__4
    
    

    简而言之,执行带有async 修饰符的方法需要为该方法构造和执行异步状态机,无论该方法是否实际执行任何异步工作!我相信您可以猜到,与标准的非异步方法相比,这会带来性能损失,这取决于您的用例 - 可能会或可能不会很重要。

    但这根本不是 CS1998 警告所说的。此警告适用于您已定义 async 方法的情况,因为您需要在其中等待某些内容,但只是忘记在异步调用之前添加 await 关键字。

    您的情况基本上相反:您已将方法定义为async,但您知道并打算不执行任何此类工作。但是编译器无法知道 - 对于编译器来说,它看起来与前面的情况完全相同,所以你会得到相同的警告。

    老实说,在第二种情况下,您自己在实现中不必要地添加了async 关键字,从而引发了警告。您知道该方法没有做任何异步工作,那么为什么还要添加关键字呢?你只是无缘无故地膨胀它。

    当然可以改进警告以指出您基本上是愚蠢的事实,and I've opened an issue in the Roslyn repo to hopefully get that done

    【讨论】:

      【解决方案5】:

      Michael Liu 很好地回答了您关于如何避免警告的问题:通过返回 Task.FromResult。

      我将回答您问题的“我应该担心警告”部分。

      答案是肯定的!

      原因是当您在没有await 运算符的异步方法内部调用返回Task 的方法时,经常会出现警告。我刚刚修复了一个并发错误,因为我在 Entity Framework 中调用了一个操作而没有等待上一个操作。

      如果您可以精心编写代码以避免编译器警告,那么当出现警告时,它会像拇指酸痛一样突出。我本可以避免几个小时的调试。

      【讨论】:

      • 这个答案是错误的。原因如下:在一个地方的方法中至少可以有一个await(不会有CS1998),但这并不意味着不会有其他缺少同步的asnyc方法调用(使用@987654324 @ 或任何其他)。现在,如果有人想知道如何确保您不会意外错过同步,那么请确保您不会忽略另一个警告 - CS4014。我什至会建议以错误为威胁。
      【解决方案6】:

      返回Task.FromResult时的异常行为说明

      这是一个小演示,它显示了标记为async 的方法与未标记的方法在异常处理方面的区别。

      public Task<string> GetToken1WithoutAsync() => throw new Exception("Ex1!");
      
      // Warning: This async method lacks 'await' operators and will run synchronously. Consider ...
      public async Task<string> GetToken2WithAsync() => throw new Exception("Ex2!");  
      
      public string GetToken3Throws() => throw new Exception("Ex3!");
      public async Task<string> GetToken3WithAsync() => await Task.Run(GetToken3Throws);
      
      public async Task<string> GetToken4WithAsync() { throw new Exception("Ex4!"); return await Task.FromResult("X");} 
      
      
      public static async Task Main(string[] args)
      {
          var p = new Program();
      
          try { var task1 = p.GetToken1WithoutAsync(); } 
          catch( Exception ) { Console.WriteLine("Throws before await.");};
      
          var task2 = p.GetToken2WithAsync(); // Does not throw;
          try { var token2 = await task2; } 
          catch( Exception ) { Console.WriteLine("Throws on await.");};
      
          var task3 = p.GetToken3WithAsync(); // Does not throw;
          try { var token3 = await task3; } 
          catch( Exception ) { Console.WriteLine("Throws on await.");};
      
          var task4 = p.GetToken4WithAsync(); // Does not throw;
          try { var token4 = await task4; } 
          catch( Exception ) { Console.WriteLine("Throws on await.");};
      }
      
      // .NETCoreApp,Version=v3.0
      Throws before await.
      Throws on await.
      Throws on await.
      Throws on await.
      

      (我对When async Task<T> required by interface, how to get return variable without compiler warning 的回答的交叉帖子)

      【讨论】:

      • 我刚刚遇到了一个有趣的问题。因为我有一个返回任务的接口,但是我的一些具体实现没有异步调用的内容,其中一些需要返回 null 才能提前退出,所以我一直在使用 return Task.FromResult(null );.我没想到的有趣奇怪的副作用是,当方法引发异常时,唯一的线索是“对象未设置为对象的实例”异常,但是在调用异步方法的行上,这使得零意义当时花了我一段时间来调试。不错的答案!
      猜你喜欢
      • 1970-01-01
      • 2017-04-30
      • 1970-01-01
      • 2019-09-25
      • 1970-01-01
      • 1970-01-01
      • 2020-05-03
      • 2019-08-27
      • 2014-02-13
      相关资源
      最近更新 更多