[英]Should I worry about "This async method lacks 'await' operators and will run synchronously" warning
我有一個接口,它公開了一些異步方法。 更具體地說,它定義了返回Task
或Task<T>
的方法。 我正在使用 async/await 關鍵字。
我正在實現這個接口。 然而,在其中一些方法中,這個實現沒有任何等待。 出於這個原因,我收到編譯器警告“CS1998:此異步方法缺少 'await' 運算符,將同步運行......”
我理解為什么我會收到這些警告,但我想知道在這種情況下我是否應該對它們做任何事情。 忽略編譯器警告感覺不對。
我知道我可以通過等待Task.Run
來修復它,但是對於只執行一些廉價操作的方法來說感覺不對。 聽起來它也會給執行增加不必要的開銷,但我也不確定這是否已經存在,因為存在async
關鍵字。
我應該忽略警告還是有辦法解決這個我沒有看到的?
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.CompletedTask :
public Task Bar() // public async Task Bar()
{ // {
Baz(); // Baz();
return Task.CompletedTask; //
} // }
Task.CompletedTask 是在 .NET Framework 4.6 中添加的。 如果您的目標是早期版本,則可以改為返回任何類型和值的已完成任務。 Task.FromResult(0)
似乎是一個流行的選擇:
public Task Bar() // public async Task Bar()
{ // {
Baz(); // Baz();
return Task.FromResult(0); //
} // }
非異步方法拋出的異常會立即沿調用堆棧向上傳播,但異步方法拋出的異常會存儲在返回的 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.
如果您需要為非異步方法保留此行為,則將整個方法包裝在try...catch語句中。 將任何未處理的異常傳遞給Task.FromException ,並返回結果:
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 的泛型參數必須與方法的返回類型匹配。
一些“異步”操作同步完成,但為了多態性仍然符合異步調用模型,這是完全合理的。
一個真實的例子是 OS I/O API。 某些設備上的異步和重疊調用總是內聯完成(例如,寫入使用共享內存實現的管道)。 但是它們實現了與在后台繼續進行的多部分操作相同的接口。
僅當您實際調用所涉及的方法時,並且僅當性能是一個問題時。
這可以通過編寫一個包含以下 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
關鍵字會導致編譯器為這些方法生成異步狀態機:
.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
但您知道並打算它不執行任何此類工作。 但是編譯器無法知道 - 對於編譯器來說,它看起來與前一種情況完全相同,因此您會收到相同的警告。
老實說,在第二種情況下,您自己通過在 implementation 中不必要地添加async
關鍵字而引起了警告。 您知道該方法沒有做任何異步工作,那么為什么還要添加關鍵字呢? 你只是無緣無故地膨脹它。
當然可以改進警告以指出您基本上是愚蠢的事實,我已經在 Roslyn repo 中打開了一個問題,希望能夠完成。
可能為時已晚,但它可能是有用的調查:
關於編譯代碼的內部結構( 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 關鍵字,否則它們將永遠不會屈服! 記住這一點很重要。 如果 async 方法的主體中未使用 await,C# 編譯器將生成警告,但代碼將像普通方法一樣編譯和運行。 請注意,這也會非常低效,因為 C# 編譯器為 async 方法生成的狀態機不會完成任何事情。
Michael Liu 很好地回答了您關於如何避免警告的問題:通過返回 Task.FromResult。
我將回答您問題的“我應該擔心警告”部分。
答案是肯定的!
這樣做的原因是,當您在沒有await
運算符的情況下調用在異步方法中返回Task
的方法時,經常會出現警告。 我剛剛修復了一個並發錯誤,因為我在 Entity Framework 中調用了一個操作,而沒有等待上一個操作。
如果您可以精心編寫代碼以避免編譯器警告,那么當出現警告時,它會像大拇指一樣突出。 我本可以避免幾個小時的調試。
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.
我發現了一個繞過此警告的技巧,請注意,這不是一種解決方案,僅提供給您參考(我不推薦):
public async Task<object> test()
{
//a pseudo code just to disable the warning about lack of await in async code!
var xyz = true ? 0 : await Task.FromResult(0); //use a var name that's not used later
//... your code statements as normal, eg:
//throw new NotImplementedException();
}
該await關鍵字的存在會欺騙編譯器不發出警告,即使我們知道它永遠不會被調用! 由於條件為true
因此它總是返回三進制條件(?:)的第一部分,並且由於此var是不使用的var,因此在Release版本中將其省略。 我不確定這種方法是否有副作用。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.