我是否应该担心“此异步方法缺少‘ wait’操作符并将同步运行”警告

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

我正在实现这个接口。然而,在其中的一些方法中,这种实现没有任何可以等待的东西。由于这个原因,我得到了编译器警告“ CS1998: 这个异步方法缺少‘ wait’操作符,将同步运行... ...”

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

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

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

83400 次浏览

一些“异步”操作同步完成,但出于多态性的考虑仍然遵循异步调用模型,这是完全合理的。

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

异步关键字只是方法的实现细节; 它不是方法签名的一部分。如果某个特定的方法实现或重写没有什么可等待的,那么只需省略 异步关键字并使用 任务返回一个已完成的任务:

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

如果您的方法返回类型是 任务而不是 任务 < TResult > ,那么返回 任务。完成任务:

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

注意: 任务。中添加了 CompletedTask。NET Framework 4.6.如果你的目标是。NET Framework 4.5.2或更早版本,则可以返回任何类型和值的已完成任务。Task.FromResult(0)似乎是一个受欢迎的选择:

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

处理例外情况

异步方法抛出的异常会立即向调用堆栈上传播,但是 异步方法抛出的异常会存储在返回的 Task 对象中,并且只在等待 Task 时传播。如果有人在等待 Task 之前调用你的方法 然后做点别的,那么结果会大不相同:

Task<string> task = Foo();   // If Foo is async and throws an exception,
DoSomethingElse();           // then this line will be executed,
string result = await task;  // and the exception will be rethrown here.

如果需要为非 异步方法保留此行为,则将整个方法包装在 试试... 接住语句中。将任何未处理的异常传递给 任务,并返回结果:

public Task<string> Foo()                       //  public async Task<string> Foo()
{                                               //  {
try                                         //
{                                           //
Baz(); // might throw                   //      Baz();
return Task.FromResult("Hello");        //      return "Hello";
}                                           //
catch (Exception ex)                        //
{                                           //
return Task.FromException<string>(ex);  //
}                                           //
}                                               //  }


public Task Bar()                               //  public async Task Bar()
{                                               //  {
try                                         //
{                                           //
Baz(); // might throw                   //      Baz();
return Task.CompletedTask;              //
}                                           //
catch (Exception ex)                        //
{                                           //
return Task.FromException(ex);          //
}                                           //
}                                               //  }

Task.FromException 的泛型参数必须与方法的返回类型匹配。

简化样板代码

您可以使用以下帮助器类自动调用 Task.FromResult 和 Task.FromException:

public static class TaskHelper
{
public static Task FromResultOf(Action action)
{
try
{
action();
return Task.CompletedTask;
}
catch (Exception ex)
{
return Task.FromException(ex);
}
}


public static Task<T> FromResultOf<T>(Func<T> func)
{
try
{
return Task.FromResult(func());
}
catch (Exception ex)
{
return Task.FromException<T>(ex);
}
}
}

使用方法:

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


public Task Bar()                       //    public async Task Bar()
{                                       //    {
return TaskHelper.FromResultOf(     //
() =>                           //
{                               //
Baz();                      //        Baz();
});                             //
}                                       //    }

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

我要回答你问题中“我应该担心警告吗”的部分。

答案是肯定的!

这样做的原因是,当您在不使用 await操作符的情况下调用一个在异步方法内返回 Task的方法时,经常会产生警告。我刚刚修复了一个并发 bug,这是因为我在实体框架中调用了一个操作,而没有等待之前的操作。

如果您能够小心翼翼地编写代码以避免编译器警告,那么当出现警告时,它就会显得非常突出。我本可以避免几个小时的调试。

现在可能为时已晚,但这可能是有用的调查:

编译代码的内部结构(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

正如你所看到的这些方法之间的巨大差异。如果您不使用等待内部异步方法,也不关心使用异步方法(例如 API 调用或事件处理程序) ,最好将其转换为普通的同步方法(它保存了应用程序的性能)。

更新:

微软文档还提供了其他信息:

异步方法需要在它们的主体中有一个 wait 关键字,否则它们将 永不屈服! 记住这一点很重要。如果等待不被使用 在异步方法体中,C # 编译器将生成一个 警告,但代码将编译和运行,就像它是一个正常的 注意,这也是非常低效的,因为 C # 编译器为异步方法生成的状态机将 一事无成。

返回 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.

(交叉邮件我的答案为 返回已完成任务的最佳方法是什么?)

只有当您实际调用所涉及的方法时,并且只有当性能是一个问题时。

这可以通过编写一个包含以下4个方法的程序,然后将它们反编译成 IL来演示(注意,提供的 IL 可能在不同的运行时版本之间发生变化; 下面是从。NET 核心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关键字会导致编译器为这些方法生成 异步状态机异步状态机:

.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关键字添加到实现中,从而导致了这个警告。你知道这个方法没有做任何异步工作,那么为什么还要添加关键字呢?你只是在无缘无故地膨胀。

这个警告当然可以被改进,用来指出一个事实,那就是你基本上是在犯傻,我已经在罗斯林回购协议上发表了一篇文章,希望能够达到这个目的