คำเตือนไม่รอการเรียกนี้การดำเนินการของเมธอดปัจจุบันจะดำเนินต่อไป


142

เพียงแค่มี VS2012 asyncและพยายามที่จะได้รับการจัดการใน

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

// contrived example (edited in response to Servy's comment)
public static Task<string> PromptForStringAsync(string prompt)
{
    return Task.Factory.StartNew(() => {
        Console.Write(prompt);
        return Console.ReadLine();
    });
}

นี่คือตัวอย่างวิธีการที่เรียกมัน ถ้าPromptForStringAsyncไม่ใช่ async วิธีนี้จะต้องมีการซ้อนการโทรกลับภายในการโทรกลับ ด้วย async ฉันจะเขียนวิธีการของฉันด้วยวิธีที่เป็นธรรมชาตินี้:

public static async Task GetNameAsync()
{
    string firstname = await PromptForStringAsync("Enter your first name: ");
    Console.WriteLine("Welcome {0}.", firstname);

    string lastname = await PromptForStringAsync("Enter your last name: ");
    Console.WriteLine("Name saved as '{0} {1}'.", firstname, lastname);
}

จนถึงตอนนี้ดีมาก ปัญหาคือเมื่อฉันเรียก GetNameAsync:

public static void DoStuff()
{
    GetNameAsync();
    MainWorkOfApplicationIDontWantBlocked();
}

จุดรวมของGetNameAsyncมันคืออะซิงโครนัส ฉันไม่ต้องการให้บล็อกเพราะฉันต้องการกลับไปที่ MainWorkOfApplicationIDontWantBlocked ASAP และปล่อยให้ GetNameAsync ทำในพื้นหลัง อย่างไรก็ตามการเรียกมันด้วยวิธีนี้ทำให้ฉันมีคำเตือนเกี่ยวกับคอมไพเลอร์ในGetNameAsyncบรรทัด:

Warning 1   Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

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

ฉันชอบให้โค้ดของฉันคอมไพล์โดยไม่มีคำเตือน แต่ไม่มีอะไรต้อง "แก้ไข" ที่นี่เพราะโค้ดทำในสิ่งที่ฉันตั้งใจจะทำ ฉันสามารถกำจัดคำเตือนได้โดยเก็บค่าส่งคืนของGetNameAsync:

public static void DoStuff()
{
    var result = GetNameAsync(); // supress warning
    MainWorkOfApplicationIDontWantBlocked();
}

แต่ตอนนี้ฉันมีรหัสที่ไม่จำเป็น Visual Studio ดูเหมือนจะเข้าใจว่าฉันถูกบังคับให้เขียนโค้ดที่ไม่จำเป็นนี้เนื่องจากมันระงับคำเตือน "ค่าไม่เคยใช้" ปกติ

ฉันยังสามารถกำจัดคำเตือนได้โดยการตัด GetNameAsync ด้วยวิธีการที่ไม่ใช่ async:

    public static Task GetNameWrapper()
    {
        return GetNameAsync();
    }

แต่นั่นคือรหัสที่ไม่จำเป็นยิ่งไปกว่านั้น ดังนั้นฉันต้องเขียนโค้ดฉันไม่ต้องการหรือยอมรับคำเตือนที่ไม่จำเป็น

มีบางอย่างเกี่ยวกับการใช้ async ของฉันที่ผิดที่นี่หรือไม่?


2
BTW เมื่อใช้งานPromptForStringAsyncคุณจะทำงานได้มากกว่าที่คุณต้องการ เพียงแค่ส่งคืนผลลัพธ์ของTask.Factory.StartNew. เป็นงานที่มีค่าเป็นสตริงที่ป้อนในคอนโซลอยู่แล้ว ไม่จำเป็นต้องรอให้ผลลัพธ์กลับมา การทำเช่นนั้นจะไม่เพิ่มมูลค่าใหม่
Servy

มันจะไม่สมเหตุสมผลกว่าที่GetNameAsyncจะระบุชื่อเต็มที่ผู้ใช้ให้มา (เช่นTask<Name>แทนที่จะส่งคืน a Task? DoStuffจากนั้นสามารถจัดเก็บงานนั้นและไม่awaitว่าจะตามวิธีอื่นหรือแม้กระทั่งส่งงานไปให้คนอื่น วิธีการเพื่อให้สามารถawaitหรือWaitบางส่วนในการนำไปใช้งานได้
Servy

@Servy: ถ้าฉันเพิ่งคืนงานฉันได้รับข้อผิดพลาด "เนื่องจากนี่เป็นวิธีการ async นิพจน์ที่ส่งคืนต้องเป็นประเภท 'string' แทนที่จะเป็น 'Task <string>'"
โคลน

1
ลบasyncคำหลัก
Servy

15
IMO นี่เป็นตัวเลือกที่ไม่ดีสำหรับคำเตือนในส่วนของทีม C # คำเตือนควรมีไว้สำหรับสิ่งที่เกือบจะผิดพลาด มีหลายกรณีที่คุณต้องการ "จุดไฟและลืม" วิธีการ async และในเวลาอื่น ๆ ที่คุณต้องการรอมันจริงๆ
MgSam

คำตอบ:


106

หากคุณไม่ต้องการผลลัพธ์จริงๆคุณสามารถเปลี่ยนGetNameAsyncลายเซ็นเพื่อส่งคืนvoid:

public static async void GetNameAsync()
{
    ...
}

พิจารณาเพื่อดูคำตอบสำหรับคำถามที่เกี่ยวข้อง: อะไรคือความแตกต่างระหว่างการคืนค่าโมฆะและการส่งคืนงาน?

อัปเดต

หากคุณต้องการผลลัพธ์คุณสามารถเปลี่ยนGetNameAsyncเพื่อส่งกลับโดยพูดว่าTask<string>:

public static async Task<string> GetNameAsync()
{
    string firstname = await PromptForStringAsync("Enter your first name: ");
    string lastname = await PromptForStringAsync("Enter your last name: ");
    return firstname + lastname;
}

และใช้มันดังนี้:

public static void DoStuff()
{
    Task<string> task = GetNameAsync();

    // Set up a continuation BEFORE MainWorkOfApplicationIDontWantBlocked
    Task anotherTask = task.ContinueWith(r => {
            Console.WriteLine(r.Result);
        });

    MainWorkOfApplicationIDontWantBlocked();

    // OR wait for the result AFTER
    string result = task.Result;
}

16
นั่นจะเป็นกรณีนี้ก็ต่อเมื่อเขาไม่เคยใส่ใจกับผลลัพธ์ตรงข้ามกับเพียงแค่ไม่ต้องการผลลัพธ์นี้เพียงครั้งเดียว
Servy

3
@ เซอร์ไวขอบคุณสำหรับคำชี้แจง แต่ OP GetNameAsyncไม่ส่งคืนค่าใด ๆ (ยกเว้นผลลัพธ์เอง)
Nikolay Khil

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

31
ตามกฎทั่วไปคุณไม่ควรมีasync voidวิธีการยกเว้นสำหรับตัวจัดการเหตุการณ์
Daniel Mann

2
สิ่งที่ควรทราบอีกประการหนึ่งก็คือพฤติกรรมจะแตกต่างกันเมื่อพูดถึงข้อยกเว้นที่ไม่ได้สังเกตเมื่อคุณเปลี่ยนไปasync voidใช้ข้อยกเว้นใด ๆ ที่คุณไม่ได้จับจะทำให้กระบวนการของคุณขัดข้อง แต่ใน. net 4.5 จะยังคงทำงานต่อไป
Caleb Vear

68

ฉันค่อนข้างสายในการสนทนานี้ แต่ยังมีตัวเลือกในการใช้#pragmaคำสั่งก่อนโปรเซสเซอร์ ฉันมีรหัส async ที่นี่และที่นั่นฉันไม่ต้องการรอในเงื่อนไขบางอย่างอย่างชัดเจนและฉันไม่ชอบคำเตือนและตัวแปรที่ไม่ได้ใช้เช่นเดียวกับคุณที่เหลือ:

#pragma warning disable 4014
SomeMethodAsync();
#pragma warning restore 4014

"4014"มาจากหน้านี้ MSDN: คอมไพเลอร์เตือน (ระดับ 1) CS4014

ดูเพิ่มเติมคำเตือน / คำตอบโดย @ ryan-horath นี่https://stackoverflow.com/a/12145047/928483

ข้อยกเว้นที่เกิดขึ้นระหว่างการโทรแบบ async ที่ไม่รอคอยจะหายไป ในการกำจัดคำเตือนนี้คุณควรกำหนดค่าส่งคืนงานของการเรียก async ให้กับตัวแปร สิ่งนี้ทำให้แน่ใจว่าคุณสามารถเข้าถึงข้อยกเว้นใด ๆ ที่เกิดขึ้นซึ่งจะระบุไว้ในค่าส่งคืน

อัปเดตสำหรับ C # 7.0

C # 7.0 เพิ่มคุณสมบัติใหม่ทิ้งตัวแปร: Discards - C # Guideซึ่งสามารถช่วยในเรื่องนี้ได้เช่นกัน

_ = SomeMethodAsync();

6
สุดยอดจริงๆ ฉันไม่รู้ว่าคุณจะทำสิ่งนี้ได้ ขอบคุณ!
Maxim Gershkovich

3
ไม่จำเป็นต้องพูดvarแค่เขียน_ = SomeMethodAsync();
เรย์

ขีดเส้นใต้ - เหมือนเดิม - ดี
Stefan Steiger

43

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

วิธีแก้ปัญหาของฉันคือการสร้างวิธีการขยายของงานที่เรียกว่า DoNotAwait () ที่ไม่ทำอะไรเลย สิ่งนี้จะไม่เพียง แต่ระงับคำเตือน ReSharper หรืออื่น ๆ เท่านั้น แต่ยังทำให้รหัสเข้าใจได้มากขึ้นและบ่งชี้ให้ผู้ดูแลรักษารหัสของคุณในอนาคตทราบว่าคุณตั้งใจจริงเพื่อไม่ให้มีการรอสาย

วิธีการขยาย:

public static class TaskExtensions
{
    public static void DoNotAwait(this Task task) { }
}

การใช้งาน:

public static void DoStuff()
{
    GetNameAsync().DoNotAwait();
    MainWorkOfApplicationIDontWantBlocked();
}

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


1
ฉันชอบสิ่งนี้ แต่ฉันเปลี่ยนชื่อเป็น Unawait ();)
Ostati

29

async void ไม่ดี!

  1. อะไรคือความแตกต่างระหว่างการคืนค่าว่างเปล่าและการคืนงาน?
  2. https://jaylee.org/archive/2012/07/08/c-sharp-async-tips-and-tricks-part-2-async-void.html

สิ่งที่ฉันแนะนำคือให้คุณเรียกใช้อย่างชัดเจนTaskผ่านวิธีการไม่ระบุชื่อ ...

เช่น

public static void DoStuff()
{
    Task.Run(async () => GetNameAsync());
    MainWorkOfApplicationIDontWantBlocked();
}

หรือหากคุณไม่ต้องการให้บล็อกคุณสามารถรอด้วยวิธีการไม่ระบุตัวตน

public static void DoStuff()
{
    Task.Run(async () => await GetNameAsync());
    MainWorkOfApplicationThatWillBeBlocked();
}

อย่างไรก็ตามหากGetNameAsyncวิธีการของคุณต้องโต้ตอบกับ UI หรือแม้แต่สิ่งใดก็ตามที่ผูกกับ UI (WINRT / MVVM ฉันกำลังมองหาคุณ) ก็จะได้รับความสนุกสนานเล็กน้อย =)

คุณจะต้องส่งต่อการอ้างอิงถึงตัวเลือกจ่ายงาน UI เช่นนี้ ...

Task.Run(async () => await GetNameAsync(CoreApplication.MainView.CoreWindow.Dispatcher));

จากนั้นในวิธีการ async ของคุณคุณจะต้องโต้ตอบกับ UI หรือองค์ประกอบที่ถูกผูกไว้ของ UI ที่คิดว่าผู้มอบหมายงาน

dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {  this.UserName = userName; });

ส่วนขยายงานของคุณยอดเยี่ยมมาก ไม่รู้ว่าทำไมฉันไม่เคยใช้มาก่อน
Mikael Dúi Bolinder

10
แนวทางแรกที่คุณกล่าวถึงทำให้เกิดคำเตือนที่แตกต่างกัน: This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread. สิ่งนี้ทำให้เกิดเธรดใหม่ด้วยในขณะที่เธรดใหม่ไม่จำเป็นต้องสร้างด้วย async / await เพียงอย่างเดียว
gregsdennis

คุณควรจะขึ้นครับ!
Ozkan

17

นี่คือสิ่งที่ฉันกำลังทำอยู่:

SomeAyncFunction().RunConcurrently();

ที่RunConcurrentlyกำหนดเป็น ...

 /// <summary> 
 /// Runs the Task in a concurrent thread without waiting for it to complete. This will start the task if it is not already running. 
 /// </summary> 
 /// <param name="task">The task to run.</param> 
 /// <remarks>This is usually used to avoid warning messages about not waiting for the task to complete.</remarks> 
 public static void RunConcurrently(this Task task) 
 { 
     if (task == null) 
         throw new ArgumentNullException("task", "task is null."); 

     if (task.Status == TaskStatus.Created) 
         task.Start(); 
 } 

https://github.com/docevaad/Anchor/blob/master/Tortuga.Anchor/Tortuga.Anchor.source/shared/TaskUtilities.cs

https://www.nuget.org/packages/Tortuga.Anchor/


1
+1. ดูเหมือนว่าจะหลีกเลี่ยงปัญหาทั้งหมดที่ถูกเปรียบเทียบในความคิดเห็นของคำตอบอื่น ๆ ขอบคุณ.
Grault

3
คุณต้องการสิ่งนี้: public static void Forget(this Task task) { }
Shital Shah

1
คุณหมายถึงอะไรที่นั่น @ShitalShah
Jay Wick

@ShitalShah ที่จะทำงานเฉพาะกับงานเริ่มต้นอัตโนมัติเช่นงานที่สร้างด้วยasync Task. งานบางอย่างต้องเริ่มด้วยตนเอง
Jonathan Allen

7

ตามบทความของ Microsoft เกี่ยวกับคำเตือนนี้คุณสามารถแก้ไขได้โดยเพียงแค่มอบหมายงานที่ส่งคืนให้กับตัวแปร ด้านล่างนี้เป็นการแปลรหัสที่ให้ไว้ในตัวอย่างของ Microsoft:

    // To suppress the warning without awaiting, you can assign the 
    // returned task to a variable. The assignment doesn't change how
    // the program runs. However, the recommended practice is always to
    // await a call to an async method.
    // Replace Call #1 with the following line.
    Task delayTask = CalledMethodAsync(delay);

โปรดทราบว่าการทำเช่นนี้จะทำให้เกิดข้อความ "Local variable is never used" ใน ReSharper


ใช่ว่าจะระงับการเตือน แต่ไม่นั่นไม่ได้ช่วยแก้ปัญหาอะไรได้เลย Task- ฟังก์ชั่นการคืนค่าควรจะawait-ed เว้นแต่คุณจะมีเหตุผลที่ดีมากที่จะไม่ทำ ไม่มีเหตุผลที่นี่ว่าทำไมการทิ้งงานจะดีไปกว่าคำตอบที่ยอมรับอยู่แล้วของการใช้async voidวิธีการ

ฉันชอบวิธีโมฆะด้วย สิ่งนี้ทำให้ StackOverflow ฉลาดกว่า Microsoft แต่แน่นอนว่าควรได้รับ
devlord

5
async voidแนะนำปัญหาร้ายแรงเกี่ยวกับการจัดการข้อผิดพลาดและส่งผลให้เกิดรหัสที่ไม่สามารถทดสอบได้ (ดูบทความ MSDN ของฉัน ) มันจะดีกว่ามากถ้าใช้ตัวแปร - ถ้าคุณแน่ใจจริงๆว่าต้องการให้มีการยกเว้นอย่างเงียบ ๆ มีโอกาสมากขึ้นที่ op จะต้องการเริ่มสองTaskวินาทีแล้วทำawait Task.WhenAllไฟล์.
Stephen Cleary

@StephenCleary ว่าสามารถกำหนดค่าข้อยกเว้นจากงานที่ไม่ได้ตรวจสอบที่ถูกละเว้นได้หรือไม่ หากมีโอกาสที่รหัสของคุณจะถูกใช้ในโครงการของคนอื่นอย่าปล่อยให้สิ่งนั้นเกิดขึ้น คุณasync void DoNotWait(Task t) { await t; }สามารถใช้วิธีการช่วยเหลือง่ายๆเพื่อหลีกเลี่ยงข้อเสียของasync voidวิธีการที่คุณอธิบาย (และฉันไม่คิดว่าTask.WhenAllเป็นสิ่งที่ OP ต้องการ แต่ก็ดีมาก)

@hvd: วิธีการช่วยเหลือของคุณจะทำให้สามารถทดสอบได้ แต่ก็ยังมีการรองรับการจัดการข้อยกเว้นที่แย่มาก
Stephen Cleary


1

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

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


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

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

ฉันทำได้ แต่ถ้าไม่มี async นั่นหมายถึงการเรียกกลับที่ซ้อนกันเช่นนี้MethodWithCallback((result1) => { Use(result1); MethodWithCallback((result2) => { Use(result1,result2); })แม้ในตัวอย่างที่ไม่สำคัญนี้มันก็เป็นเรื่องเลวร้ายที่จะแยกวิเคราะห์ ด้วย async โค้ดที่เทียบเท่าจะถูกสร้างขึ้นสำหรับฉันเมื่อฉันเขียนresult1 = await AsyncMethod(); Use(result1); result2 = await AsyncMethod(); Use(result1,result2); ซึ่งอ่านง่ายกว่ามาก (แม้ว่าจะไม่สามารถอ่านได้มากนักในความคิดเห็นนี้!)
โคลน

@ มุด: ครับ แต่คุณสามารถเรียกวิธีการ async ที่สองได้ทันทีหลังจากวิธีแรกไม่มีเหตุผลที่จะต้องรอการโทรแบบซิงโครนัUse
Guffa

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


0

หากคุณไม่ต้องการเปลี่ยนลายเซ็นวิธีที่จะส่งคืนvoid(เนื่องจากการส่งคืนvoidควรเป็นโมฆะเอ็ดเสมอ) คุณสามารถใช้ C # 7.0+ ทิ้งคุณสมบัติเช่นนี้ซึ่งดีกว่าการกำหนดให้กับตัวแปรเล็กน้อย (และควรลบอื่น ๆ คำเตือนเครื่องมือตรวจสอบแหล่งที่มา):

public static void DoStuff()
{
    _ = GetNameAsync(); // we don't need the return value (suppresses warning)
    MainWorkOfApplicationIDontWantBlocked();
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.