การระงับ“ คำเตือน CS4014: เนื่องจากการโทรนี้ไม่รอการดำเนินการของวิธีการปัจจุบันยังคงดำเนินต่อไป…”


156

นี้ไม่ซ้ำกับ"วิธีการได้อย่างปลอดภัยเรียกวิธี async ใน C # โดยไม่ต้องรอคอย"

ฉันจะระงับคำเตือนต่อไปนี้ได้อย่างไร?

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

ตัวอย่างง่ายๆ:

static async Task WorkAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done!");
}

static async Task StartWorkAsync()
{
    WorkAsync(); // I want fire-and-forget 

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}

สิ่งที่ฉันพยายามและไม่ชอบ:

static async Task StartWorkAsync()
{
    #pragma warning disable 4014
    WorkAsync(); // I want fire-and-forget here
    #pragma warning restore 4014
    // ...
}

static async Task StartWorkAsync()
{
    var ignoreMe = WorkAsync(); // I want fire-and-forget here
    // ...
}

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


1
ดังนั้นคุณคิดว่า#pragmaไม่ดีเหรอ?
Frédéric Hamidi

10
@ FrédéricHamidiฉันทำ
noseratio

2
@Noseratio: อ่าใช่มั้ย ขออภัยฉันคิดว่ามันเป็นคำเตือนอื่น ๆ ไม่สนใจฉัน!
Jon Skeet

3
@Terribad: ฉันไม่แน่ใจจริงๆ - ดูเหมือนว่าคำเตือนค่อนข้างสมเหตุสมผลสำหรับกรณีส่วนใหญ่ โดยเฉพาะอย่างยิ่งคุณควรคิดเกี่ยวกับสิ่งที่คุณต้องการที่จะเกิดขึ้นกับความล้มเหลวใด ๆ - มักจะเป็น "ไฟและลืม" คุณควรหาวิธีบันทึกความล้มเหลว ฯลฯ
Jon Skeet

4
@Terribad ก่อนที่คุณจะใช้วิธีนี้หรืออย่างอื่นคุณควรมีภาพที่ชัดเจนว่ามีการแพร่กระจายข้อยกเว้นสำหรับวิธีการ async อย่างไร (ตรวจสอบสิ่งนี้ ) จากนั้นคำตอบโดย @ Knaģisให้วิธีการที่สวยงามโดยไม่สูญเสียข้อยกเว้นสำหรับไฟและลืมโดยใช้async voidวิธีการช่วยเหลือ
noseratio

คำตอบ:


160

ด้วย C # 7 ตอนนี้คุณสามารถใช้ทิ้ง :

_ = WorkAsync();

7
นี่เป็นคุณสมบัติภาษาเล็ก ๆ ที่มีประโยชน์ที่ฉันจำไม่ได้ มันเหมือนมี_ = ...อยู่ในสมองของฉัน
Marc L.

3
ฉันพบ SupressMessage ลบคำเตือนของฉันจาก Visual Studio "รายการข้อผิดพลาด" ของฉัน แต่ไม่ใช่ "เอาท์พุท" และ#pragma warning disable CSxxxxดูน่าเกลียดกว่าทิ้ง;)
David Savage

122

คุณสามารถสร้างวิธีการขยายที่จะป้องกันการเตือน วิธีการขยายสามารถว่างเปล่าหรือคุณสามารถเพิ่มการจัดการข้อยกเว้นกับที่.ContinueWith()นั่น

static class TaskExtensions
{
    public static void Forget(this Task task)
    {
        task.ContinueWith(
            t => { WriteLog(t.Exception); },
            TaskContinuationOptions.OnlyOnFaulted);
    }
}

public async Task StartWorkAsync()
{
    this.WorkAsync().Forget();
}

อย่างไรก็ตามASP.NETนับจำนวนของงานที่กำลังทำงานอยู่ดังนั้นมันจะไม่ทำงานกับForget()ส่วนขยายแบบง่ายดังที่แสดงไว้ด้านบนและอาจล้มเหลวด้วยข้อยกเว้น:

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

ด้วย. NET 4.5.2 มันสามารถแก้ไขได้โดยใช้HostingEnvironment.QueueBackgroundWorkItem:

public static Task HandleFault(this Task task, CancellationToken cancelToken)
{
    return task.ContinueWith(
        t => { WriteLog(t.Exception); },
        cancelToken,
        TaskContinuationOptions.OnlyOnFaulted,
        TaskScheduler.Default);
}

public async Task StartWorkAsync()
{
    System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(
        cancelToken => this.WorkAsync().HandleFault(cancelToken));
}

8
ฉันได้พบTplExtensions.Forgetแล้ว Microsoft.VisualStudio.Threadingมีความดีมากขึ้นภายใต้ ฉันหวังว่ามันจะให้บริการสำหรับการใช้งานนอก Visual Studio SDK
noseratio

1
@Noseratio และ Knagis ฉันชอบวิธีนี้และฉันวางแผนที่จะใช้ ฉันโพสต์คำถามติดตามที่เกี่ยวข้อง: stackoverflow.com/questions/22864367/fire-and-forget-approach
แมตต์สมิ ธ

3
@stricq จุดประสงค์ใดที่จะเพิ่ม ConfigureAwait (false) ไปยัง Forget () ให้บริการ ตามที่ฉันเข้าใจแล้ว ConfigureAwait มีผลกับการซิงค์เธรดเท่านั้น ณ จุดที่ใช้งานการรอคอย แต่วัตถุประสงค์ของการลืม () คือการทิ้งภารกิจดังนั้นงานจึงไม่สามารถรอได้ดังนั้น ConfigureAwait ที่นี่จึงไม่มีประโยชน์
dthorpe

3
หากเธรดการวางไข่หายไปก่อนที่ไฟและงานการลืมจะเสร็จสิ้นโดยไม่มี ConfigureAwait (false) มันจะยังคงพยายามกลับไปยังเธรดที่วางไข่อีกครั้งเธรดนั้นจะหายไป การตั้งค่า ConfigureAwait (false) บอกให้ระบบไม่ให้มาร์แชลล์กลับเข้าสู่เธรดการโทร
stricq

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

39

คุณสามารถตกแต่งวิธีด้วยแอตทริบิวต์ต่อไปนี้:

[System.Diagnostics.CodeAnalysis.SuppressMessage("Await.Warning", "CS4014:Await.Warning")]
static async Task StartWorkAsync()
{
    WorkAsync();
    // ...
}

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

ส่วนที่สำคัญของรหัสนี้คือพารามิเตอร์ที่สอง ส่วน "CS4014:" เป็นสิ่งที่ยับยั้งคำเตือน คุณสามารถเขียนสิ่งที่คุณต้องการในส่วนที่เหลือ


ใช้งานไม่ได้สำหรับฉัน: Visual Studio สำหรับ Mac 7.0.1 (build 24) ดูเหมือนว่าควร แต่ - ไม่
IronRod

1
[SuppressMessage("Compiler", "CS4014")]ไม่แสดงข้อความในหน้าต่างรายการข้อผิดพลาด แต่หน้าต่างเอาท์พุทยังคงแสดงบรรทัดคำเตือน
David Ching

35

วิธีการสองอย่างของฉันในการจัดการกับเรื่องนี้

บันทึกลงในตัวแปรทิ้ง (C # 7)

ตัวอย่าง

_ = Task.Run(() => DoMyStuff()).ConfigureAwait(false);

นับตั้งแต่มีการแนะนำการทิ้งใน C # 7 ตอนนี้ฉันคิดว่านี่ดีกว่าการเตือน เพราะมันไม่เพียง แต่เตือนความจำเท่านั้น แต่ยังทำให้ความตั้งใจที่จะลืมและดับเพลิงนั้นชัดเจน

นอกจากนี้คอมไพเลอร์จะสามารถปรับให้เหมาะสมในโหมดการปล่อย

เพียงระงับมัน

#pragma warning disable 4014
...
#pragma warning restore 4014

เป็นทางออกที่ดีพอที่จะ "ยิงและลืม"

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

หากคุณมีปัญหาในการจำวิธีสะกด#pragma warning disable 4014ให้ให้ Visual Studio เพิ่มให้คุณ กด Ctrl + เพื่อเปิด "Quick Actions" จากนั้น "ระงับ CS2014"

ทั้งหมดในทุก

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


สิ่งนี้ทำงานได้ใน Visual Studio สำหรับ Mac 7.0.1 (build 24)
IronRod

1
มันโง่ที่จะสร้างวิธีการที่ใช้เห็บอีกสองสามตัวเพื่อดำเนินการเพียงเพื่อจุดประสงค์ในการระงับคำเตือน - อันนี้ไม่ได้เพิ่มเห็บพิเศษเลยและ IMO สามารถอ่านได้มากขึ้น:[MethodImpl(MethodImplOptions.AggressiveInlining)] void Forget(this Task @this) { } /* ... */ obj.WorkAsync().Forget();
noseratio

1
@Noseratio หลายครั้งเมื่อฉันใช้AggressiveInliningคอมไพเลอร์เพียงแค่ละเว้นมันด้วยเหตุผลใดก็ตาม
fjch1997

1
ฉันชอบตัวเลือก pragma เพราะมันง่ายมากและใช้ได้กับโค้ดบรรทัดปัจจุบัน (หรือส่วน) เท่านั้นไม่ใช่วิธีการทั้งหมด
wasatchwizard

2
อย่าลืมที่จะใช้รหัสข้อผิดพลาดเช่นนั้นจะเรียกคืนเตือนหลังจากนั้นด้วย#pragma warning disable 4014 #pragma warning restore 4014มันยังคงทำงานโดยไม่มีรหัสข้อผิดพลาด แต่ถ้าคุณไม่เพิ่มหมายเลขข้อผิดพลาดมันจะระงับข้อความทั้งหมด
DunningKrugerEffect

11

วิธีง่ายๆในการหยุดคำเตือนก็คือกำหนดภารกิจเมื่อเรียกมัน:

Task fireAndForget = WorkAsync(); // No warning now

ดังนั้นในโพสต์ต้นฉบับของคุณคุณจะทำ:

static async Task StartWorkAsync()
{
    // Fire and forget
    var fireAndForget = WorkAsync(); // Tell the compiler you know it's a task that's being returned 

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}

ฉันพูดถึงวิธีการนี้ในคำถามตัวเองเป็นหนึ่งในคนที่ฉันไม่ชอบโดยเฉพาะ
noseratio

อ๊ะ! ไม่ได้สังเกตว่าเพราะมันอยู่ในส่วนรหัสเดียวกับ pragma ของคุณ ... และฉันกำลังมองหาคำตอบ นอกเหนือจากนั้นคุณไม่ชอบอะไรเกี่ยวกับวิธีการนี้
noelicus

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

ยุติธรรมมาก - ฉันมีความรู้สึกคล้ายกันซึ่งเป็นเหตุผลว่าทำไมฉันจึงตั้งชื่อfireAndForget... ดังนั้นฉันคาดว่ามันจะไม่ได้รับการอ้างอิงจากนี้ไป
noelicus

4

สาเหตุของการเตือนคือ WorkAsync ส่งคืนสิ่งTaskที่ไม่เคยอ่านหรือรอคอย คุณสามารถตั้งค่าประเภทการคืนของ WorkAsync เป็นvoidและคำเตือนจะหายไป

โดยทั่วไปแล้วเมธอดจะส่งคืน a Taskเมื่อผู้เรียกจำเป็นต้องทราบสถานะของผู้ปฏิบัติงาน ในกรณีที่เกิดไฟไหม้และลืมให้คืนค่าเป็นโมฆะให้ดูเหมือนว่าผู้โทรเป็นอิสระจากวิธีการที่เรียก

static async void WorkAsync()
{
    await Task.Delay(1000);
    Console.WriteLine("Done!");
}

static async Task StartWorkAsync()
{
    WorkAsync(); // no warning since return type is void

    // more unrelated async/await stuff here, e.g.:
    // ...
    await Task.Delay(2000); 
}

2

ทำไมไม่ห่อมันด้วยวิธี async ที่คืนค่าเป็นโมฆะ? ความยาวบิต แต่ใช้ตัวแปรทั้งหมด

static async Task StartWorkAsync()
{   
     async void WorkAndForgetAsync() => await WorkAsync();
     WorkAndForgetAsync(); // no warning
}

1

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

    delegate Task IntermediateHandler();



    static async Task AsyncOperation()
    {
        await Task.Yield();
    }

และเรียกมันว่าอย่างนั้น

(new IntermediateHandler(AsyncOperation))();

...

ฉันคิดว่ามันน่าสนใจที่คอมไพเลอร์จะไม่เตือนเหมือนกันเมื่อใช้ผู้รับมอบสิทธิ์


ไม่จำเป็นต้องประกาศผู้รับมอบสิทธิ์ แต่คุณก็อาจทำเช่นนั้น(new Func<Task>(AsyncOperation))()แม้ว่า IMO จะยังละเอียดเกินไป
noseratio
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.