ฉันควรกังวลเกี่ยวกับ "วิธีการ async นี้ไม่มีตัวดำเนินการ" รอ "และจะเรียกใช้คำเตือนพร้อมกัน"


93

ฉันมีอินเทอร์เฟซที่เปิดเผยวิธีการ async บางอย่าง โดยเฉพาะอย่างยิ่งมันมีวิธีการที่กำหนดไว้ซึ่งส่งคืน Task หรือ Task <T> ฉันใช้คำหลัก async / await

ฉันอยู่ระหว่างการใช้งานอินเทอร์เฟซนี้ อย่างไรก็ตามในวิธีการเหล่านี้บางส่วนการนำไปใช้งานนี้ไม่มีอะไรต้องรอ ด้วยเหตุนี้ฉันจึงได้รับคำเตือนของคอมไพเลอร์ "วิธีการ async นี้ไม่มีตัวดำเนินการ 'รอ' และจะทำงานพร้อมกัน ... "

ฉันเข้าใจว่าทำไมฉันถึงได้รับข้อผิดพลาด แต่ฉันสงสัยว่าฉันควรทำอะไรกับพวกเขาในบริบทนี้หรือไม่ รู้สึกผิดที่ละเลยคำเตือนของคอมไพเลอร์

ฉันรู้ว่าฉันสามารถแก้ไขได้โดยรองานเรียกใช้ แต่รู้สึกว่าผิดสำหรับวิธีการที่ใช้งานได้ไม่แพงเพียงไม่กี่อย่าง ดูเหมือนว่ามันจะเพิ่มค่าใช้จ่ายที่ไม่จำเป็นในการดำเนินการ แต่ฉันก็ไม่แน่ใจว่ามีอยู่แล้วหรือไม่เพราะมีคีย์เวิร์ด async อยู่

ฉันควรเพิกเฉยต่อคำเตือนหรือมีวิธีการแก้ไขที่ฉันไม่เห็นหรือไม่?


2
มันจะขึ้นอยู่กับข้อมูลจำเพาะ คุณแน่ใจจริงๆหรือว่าต้องการให้ดำเนินการเหล่านี้พร้อมกัน? หากคุณต้องการให้ดำเนินการพร้อมกันเหตุใดจึงทำเครื่องหมายวิธีการเป็นasync?
Servy

11
เพียงแค่ลบasyncคำหลัก คุณยังสามารถกลับมาใช้Task Task.FromResult
Michael Liu

1
@BenVoigt Google เต็มไปด้วยข้อมูลเกี่ยวกับเรื่องนี้ในกรณีที่ OP ยังไม่รู้
Servy

1
@BenVoigt Michael Liu ไม่ได้ให้คำใบ้นั้นแล้วหรือ? ใช้Task.FromResult.

1
@hvd: ที่แก้ไขในความคิดเห็นของเขาในภายหลัง
Ben Voigt

คำตอบ:


144

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 ตอนนี้ดูเหมือนชัดเจน แต่ฉันไม่เห็นมัน!
dannykay1710

1
งานสามารถทำได้โดยใช้สมาชิกแบบคงที่ตามบรรทัดของ Task ว่างเปล่าสำหรับวัตถุประสงค์นี้ ความตั้งใจจะชัดเจนขึ้นเล็กน้อยและทำให้ฉันเจ็บปวดที่ต้องนึกถึงงานที่ต้องปฏิบัติตามหน้าที่ทั้งหมดนี้ที่คืนศูนย์ที่ไม่จำเป็น
Rupert Rawnsley

await Task.FromResult(0)เหรอ? แล้วไงawait Task.Yield()?
Sushi271

1
@ Sushi271: ไม่ด้วยasyncวิธีที่ไม่ใช่คุณกลับมา Task.FromResult(0)แทนที่จะรอมัน
Michael Liu

1
ไม่จริง async ไม่ใช่แค่รายละเอียดการใช้งาน แต่ยังมีรายละเอียดมากมายที่ต้องระวัง :) สิ่งที่ต้องระวังคือส่วนใดที่ทำงานแบบซิงโครนัสส่วนใดแบบอะซิงโครนัสบริบทการซิงโครไนซ์ปัจจุบันคืออะไรและสำหรับการบันทึกเท่านั้นงานมักจะเร็วกว่าเล็กน้อยเนื่องจากไม่มีเครื่องสถานะอยู่หลังม่าน :)
ipavlu

16

เป็นเรื่องที่สมเหตุสมผลอย่างยิ่งที่การดำเนินการ "อะซิงโครนัส" บางอย่างจะสมบูรณ์แบบพร้อมกัน แต่ยังคงเป็นไปตามรูปแบบการโทรแบบอะซิงโครนัสเพื่อประโยชน์ของความหลากหลาย

ตัวอย่างในโลกแห่งความเป็นจริงนี้มาจาก OS I / O API การโทรแบบอะซิงโครนัสและการซ้อนทับบนอุปกรณ์บางอย่างจะดำเนินการแบบอินไลน์เสมอ (เช่นการเขียนไปป์ที่ดำเนินการโดยใช้หน่วยความจำที่ใช้ร่วมกันเป็นต้น) แต่ใช้อินเทอร์เฟซเดียวกับการดำเนินการหลายส่วนซึ่งดำเนินการต่อในพื้นหลัง


4

Michael Liu ตอบคำถามของคุณได้ดีเกี่ยวกับวิธีหลีกเลี่ยงคำเตือน: โดยการส่งคืน Task.FromResult

ฉันจะตอบส่วน "ฉันควรกังวลเกี่ยวกับคำเตือน" ของคำถามของคุณ

คำตอบคือใช่!

เหตุผลนี้ก็คือคำเตือนมักเกิดขึ้นเมื่อคุณเรียกใช้เมธอดที่ส่งคืนTaskภายในเมธอด async โดยไม่มีตัวawaitดำเนินการ ฉันเพิ่งแก้ไขข้อผิดพลาดพร้อมกันที่เกิดขึ้นเนื่องจากฉันเรียกใช้การดำเนินการใน Entity Framework โดยไม่รอการดำเนินการก่อนหน้านี้

หากคุณสามารถเขียนโค้ดของคุณอย่างพิถีพิถันเพื่อหลีกเลี่ยงคำเตือนของคอมไพเลอร์เมื่อมีคำเตือนก็จะโดดเด่นเหมือนนิ้วโป้งที่เจ็บ ฉันสามารถหลีกเลี่ยงการดีบักหลายชั่วโมงได้


5
คำตอบนี้ผิดเพียง นี่คือเหตุผล: อาจมีอย่างน้อยหนึ่งawaitวิธีในที่เดียว (จะไม่มี CS1998) แต่ไม่ได้หมายความว่าจะไม่มีการเรียกใช้วิธี asnyc อื่นที่จะขาดการซิงโครไนซ์ (โดยใช้awaitหรืออื่น ๆ ) ตอนนี้หากมีคนต้องการทราบวิธีตรวจสอบให้แน่ใจว่าคุณไม่พลาดการซิงโครไนซ์โดยไม่ได้ตั้งใจให้แน่ใจว่าคุณไม่เพิกเฉยต่อคำเตือนอื่น - CS4014 ฉันอยากจะแนะนำให้ขู่ว่าเป็นข้อผิดพลาด
Victor Yarema

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

และไม่มี async และวิธีการงาน:

 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

อย่างที่คุณเห็นความแตกต่างใหญ่ระหว่างวิธีการเหล่านี้ หากคุณไม่ใช้ await ภายในวิธี async และไม่สนใจเกี่ยวกับการใช้วิธี async (เช่นการเรียก API หรือตัวจัดการเหตุการณ์) แนวคิดที่ดีจะแปลงเป็นวิธีการซิงค์ปกติ (ช่วยประหยัดประสิทธิภาพแอปพลิเคชันของคุณ)

อัปเดต:

นอกจากนี้ยังมีข้อมูลเพิ่มเติมจาก microsoft docs https://docs.microsoft.com/en-us/dotnet/standard/async-in-depth :

วิธีการ async จำเป็นต้องมีคีย์เวิร์ดรออยู่ในตัวไม่เช่นนั้นจะไม่มีวันยอม! นี่เป็นสิ่งสำคัญที่ต้องจำไว้ หากไม่ได้ใช้ await ในเนื้อความของวิธีการ async คอมไพลเลอร์ C # จะสร้างคำเตือน แต่โค้ดจะคอมไพล์และรันเหมือนเป็นวิธีการปกติ โปรดทราบว่าสิ่งนี้จะไม่มีประสิทธิภาพอย่างไม่น่าเชื่อเนื่องจากเครื่องสถานะที่สร้างโดยคอมไพเลอร์ C # สำหรับวิธี async จะไม่สามารถทำอะไรได้เลย


2
นอกจากนี้ข้อสรุปสุดท้ายของคุณเกี่ยวกับการใช้งานasync/awaitนั้นมีขนาดใหญ่เกินไปอย่างมากเมื่อคุณพิจารณาจากตัวอย่างที่ไม่สมจริงของการดำเนินการเดียวที่เชื่อมต่อกับ CPU Taskเมื่อใช้อย่างเหมาะสมช่วยให้แอปพลิเคชันมีประสิทธิภาพและการตอบสนองที่ดีขึ้นเนื่องจากการทำงานพร้อมกัน (เช่นแบบขนาน) และการจัดการและการใช้เธรดที่ดีขึ้น
MickyD

นั่นเป็นเพียงการทดสอบตัวอย่างที่เรียบง่ายตามที่ฉันพูดในโพสต์นี้ นอกจากนี้ฉันยังพูดถึงคำขอไปยัง api และผู้ดูแลเหตุการณ์ซึ่งเป็นไปได้โดยใช้วิธีการทั้งสองเวอร์ชัน (async และปกติ) PO ยังกล่าวเกี่ยวกับการใช้วิธี async โดยไม่ต้องรอภายใน โพสต์ของฉันคือเกี่ยวกับเรื่องนี้ Tasksแต่ไม่ได้เกี่ยวกับการใช้อย่างถูกต้อง เป็นเรื่องน่าเศร้าที่คุณไม่ได้อ่านข้อความทั้งหมดของโพสต์และทำการสรุปอย่างรวดเร็ว
Oleg Bondarenko

1
มีความแตกต่างระหว่างวิธีการที่ส่งคืนint(เช่นในกรณีของคุณ) และวิธีที่ส่งกลับTaskเช่นที่กล่าวโดย OP อ่านโพสต์ของเขาและคำตอบที่ได้รับการยอมรับอีกครั้งแทนที่จะทำสิ่งต่างๆเป็นการส่วนตัว คำตอบของคุณไม่มีประโยชน์ในกรณีนี้ คุณไม่ต้องกังวลที่จะแสดงความแตกต่างระหว่างวิธีการที่มีawaitอยู่ภายในหรือไม่ ตอนนี้คุณได้ทำสิ่งนั้นแล้วจะดีมากที่คุ้มค่ากับการ
โหวต

ฉันเดาว่าคุณไม่เข้าใจความแตกต่างระหว่างวิธีการ async กับวิธีปกติที่เรียกด้วย api หรือตัวจัดการเหตุการณ์ มีการกล่าวถึงเป็นพิเศษในโพสต์ของฉัน ขออภัยคุณจะหายไปที่อีกครั้ง
Oleg Bondarenko

1

หมายเหตุเกี่ยวกับลักษณะการทำงานของข้อยกเว้นเมื่อส่งคืน 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.

(โพสต์ข้ามคำตอบของฉันสำหรับเมื่อ async Task <T> ต้องการโดยอินเทอร์เฟซวิธีรับตัวแปรส่งคืนโดยไม่มีคำเตือนของคอมไพเลอร์ )

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.