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


435

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

ฉันได้ไปTask.Delay(0)ข้างล่างนี้ แต่ฉันอยากจะรู้ว่าสิ่งนี้มีผลข้างเคียงของการทำงานหรือไม่ถ้าฟังก์ชั่นนี้มีการเรียกใช้มาก (เพื่อประโยชน์ในการโต้แย้งพูดหลายร้อยครั้งต่อวินาที):

  • น้ำตาลซินแทติกติกนี้ไม่ทำให้ลมออกมาเป็นเรื่องใหญ่หรือไม่?
  • มันเริ่มอุดตันกลุ่มแอปพลิเคชันของฉันหรือไม่
  • คอมไพเลอร์มีดเพียงพอที่จะจัดการกับDelay(0)แตกต่างกันหรือไม่?
  • จะreturn Task.Run(() => { });แตกต่างกันอย่างไร

มีวิธีที่ดีกว่า?

using System.Threading.Tasks;

namespace MyAsyncTest
{
    internal interface IFooFace
    {
        Task WillBeLongRunningAsyncInTheMajorityOfImplementations();
    }

    /// <summary>
    /// An implementation, that unlike most cases, will not have a long-running
    /// operation in 'WillBeLongRunningAsyncInTheMajorityOfImplementations'
    /// </summary>
    internal class LazyBar : IFooFace
    {
        #region IFooFace Members

        public Task WillBeLongRunningAsyncInTheMajorityOfImplementations()
        {
            // First, do something really quick
            var x = 1;

            // Can't return 'null' here! Does 'Task.Delay(0)' have any performance considerations?
            // Is it a real no-op, or if I call this a lot, will it adversely affect the
            // underlying thread-pool? Better way?
            return Task.Delay(0);

            // Any different?
            // return Task.Run(() => { });

            // If my task returned something, I would do:
            // return Task.FromResult<int>(12345);
        }

        #endregion
    }

    internal class Program
    {
        private static void Main(string[] args)
        {
            Test();
        }

        private static async void Test()
        {
            IFooFace foo = FactoryCreate();
            await foo.WillBeLongRunningAsyncInTheMajorityOfImplementations();
            return;
        }

        private static IFooFace FactoryCreate()
        {
            return new LazyBar();
        }
    }
}

2
คำถามที่เกี่ยวข้อง: stackoverflow.com/questions/4245968/create-a-completed-task
CodesInChaos

8
Task.FromResult<object>(null)ส่วนตัวผมไปกับ
CodesInChaos

คำตอบ:


625

การใช้Task.FromResult(0)หรือTask.FromResult<object>(null)สร้างค่าโสหุ้ยน้อยกว่าการสร้างTaskด้วยนิพจน์แบบไม่ใช้ เมื่อสร้าง a ที่Taskมีการกำหนดผลลัพธ์ล่วงหน้าจะไม่มีค่าใช้จ่ายในการกำหนดตารางเวลาที่เกี่ยวข้อง


วันนี้ฉันอยากจะแนะนำให้ใช้Task.CompletedTaskเพื่อทำสิ่งนี้ให้สำเร็จ


5
และถ้าคุณเกิดขึ้นที่จะใช้github.com/StephenCleary/AsyncExพวกเขาให้เป็นระดับ TaskConstants เพื่อให้งานที่เสร็จสมบูรณ์เหล่านี้พร้อมกับหลาย ๆ คนอื่น ๆ ที่มีประโยชน์มาก (0 int / เท็จจริงเริ่มต้น <T> ())
Quentin-starin

5
return default(YourReturnType);
ตำนาน

8
@Legends นั่นใช้ไม่ได้กับการสร้างงานโดยตรง
Reed Copsey

18
ฉันไม่แน่ใจ แต่Task.CompletedTaskอาจทำเคล็ดลับ! (แต่ต้องใช้. NET 4.6)
Peter

1
คำถาม: เป็นไปได้Task.FromResult<TResult>อย่างไรที่คืนค่า a Task<TResult>ตอบสนองประเภทคืนของTask(ไม่มีพารามิเตอร์ params)?
rory.ap

187

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

public static class TaskExtensions
{
    public static readonly Task CompletedTask = Task.FromResult(false);
}

ด้วยTaskExtensions.CompletedTaskคุณสามารถใช้อินสแตนซ์เดียวกันได้ตลอดทั้งโดเมนแอพ


รุ่นล่าสุดของกรอบ NET (v4.6)เพิ่มเพียงว่ามีTask.CompletedTaskคุณสมบัติคงที่

Task completedTask = Task.CompletedTask;

ฉันจำเป็นต้องส่งคืนหรือรออยู่หรือไม่
พิกซาร์

@Pixar คุณหมายถึงอะไร คุณสามารถทำได้ทั้งสองอย่าง แต่การรอคอยมันจะดำเนินต่อไปพร้อมกัน
i3arnon

ขออภัยที่ผมต้องพูดถึงบริบท :) ที่ผมเห็นมันตอนนี้เราสามารถทำเช่นเดียวกับpublic Task WillBeLongRunningAsyncInTheMajorityOfImplementations() public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations()ดังนั้นเราสามารถหรือreturn CompletedTask; await CompletedTask;อะไรที่เป็นที่นิยมมากขึ้น (อาจจะมีประสิทธิภาพมากขึ้นหรือสอดคล้องกันมากขึ้น)?
พิกซาร์

3
@ พิกซาร์ฉันไม่ชัดเจน ฉันหมายถึง "'แบบไม่ซิงค์' จะมีประสิทธิภาพมากกว่า" การสร้างเมธอด async สั่งให้คอมไพเลอร์แปลงเป็นสถานะ มันจะสร้างงานใหม่ทุกครั้งที่คุณเรียกมันว่า การส่งคืนงานที่เสร็จสมบูรณ์แล้วจะมีความชัดเจนและมีประสิทธิภาพมากกว่า
i3arnon

3
@ Asad จะลดการจัดสรร (และด้วยเวลา GC) แทนที่จะจัดสรรหน่วยความจำใหม่และสร้างอินสแตนซ์งานในแต่ละครั้งที่คุณต้องการงานที่เสร็จสมบูรณ์คุณทำได้เพียงครั้งเดียว
i3arnon

38

Task.Delay(0) ในคำตอบที่ได้รับการยอมรับเป็นวิธีการที่ดีเพราะเป็นสำเนาของแคชที่เสร็จสมบูรณ์ Taskเช่นเดียวกับในคำตอบที่ได้รับการยอมรับเป็นวิธีการที่ดีในขณะที่มันเป็นสำเนาที่เก็บไว้ของเสร็จ

จาก 4.6 มีตอนนี้Task.CompletedTaskที่ชัดเจนยิ่งขึ้นในวัตถุประสงค์ของมัน แต่ไม่เพียง แต่Task.Delay(0)ยังคงส่งคืนอินสแตนซ์แคชเดียวมันกลับมาเหมือนกันTask.CompletedTaskเช่นแคชเดียวไม่

ลักษณะแคชไม่รับประกันว่าจะคงที่ แต่เป็น optimisations การดำเนินการขึ้นอยู่กับว่าเป็นเพียงการดำเนินการขึ้นอยู่กับว่าเป็น optimisations (นั่นคือพวกเขายังคงทำงานอย่างถูกต้องถ้าการดำเนินการเปลี่ยนแปลงเพื่อสิ่งที่ยังคงถูกต้อง) ใช้Task.Delay(0)เป็น ดีกว่าคำตอบที่ยอมรับ


1
ฉันยังคงใช้ 4.5 และเมื่อฉันทำการวิจัยบางอย่างฉันถูกขบขันที่พบว่า Task.Delay (0) เป็นกรณีพิเศษเพื่อส่งคืนสมาชิก CompletedTask แบบคงที่ ซึ่งฉันแคชในสมาชิก CompletedTask คงที่ของฉันเอง : P
Darren Clark

2
ฉันไม่รู้ว่าทำไม แต่Task.CompletedTaskไม่สามารถใช้ในโครงการ PCL ได้แม้ว่าฉันจะตั้งค่ารุ่น. net เป็น 4.6 (โปรไฟล์ 7) เพิ่งทดสอบใน VS2017
เฟลิกซ์

@Fay ฉันเดาว่ามันจะต้องไม่เป็นส่วนหนึ่งของพื้นผิว PCL API แม้ว่าสิ่งเดียวที่ทำอะไรกับตอนนี้ที่สนับสนุน PCL ยังรองรับ 4.5 ดังนั้นฉันจึงต้องใช้ตัวเองTask.CompletedTask => Task.Delay(0);เพื่อสนับสนุนสิ่งนั้นดังนั้นฉันจึงไม่ ไม่รู้แน่ชัดว่าส่วนบนของหัวฉัน
Jon Hanna

17

พบปัญหานี้เมื่อไม่นานมานี้และยังคงได้รับคำเตือน / ข้อผิดพลาดเกี่ยวกับวิธีการที่เป็นโมฆะ

เราอยู่ในธุรกิจการปิดตัวคอมไพเลอร์และสิ่งนี้ล้างมัน:

    public async Task MyVoidAsyncMethod()
    {
        await Task.CompletedTask;
    }

นี่เป็นการรวมคำแนะนำที่ดีที่สุดของทั้งหมดไว้ที่นี่ ไม่จำเป็นต้องมีคำสั่ง return ใด ๆ เว้นแต่ว่าคุณกำลังทำอะไรบางอย่างในวิธีการนั้น


17
นั่นเป็นสิ่งที่ผิดอย่างสมบูรณ์ คุณได้รับข้อผิดพลาดคอมไพเลอร์เนื่องจากคำจำกัดความของวิธีการประกอบด้วย async ดังนั้นคอมไพเลอร์คาดหวังว่าจะรอ การใช้งาน "ถูกต้อง" จะเป็นงานสาธารณะ MyVoidAsyncMethog () {ส่งคืน Task.CompletedTask;}
Keith

3
ไม่แน่ใจว่าทำไมสิ่งนี้ถึงได้ลงคะแนนเพราะนี่เป็นคำตอบที่สะอาดที่สุด
webwake

3
เนื่องจากความคิดเห็นของ Keith
noelicus

4
เขาไม่ได้ผิดทั้งหมดเขาเพิ่งลบคำสำคัญ async วิธีการของฉันสำนวนมากขึ้น เขาเป็นคนที่เรียบง่าย ถ้าไม่หยาบคายสักหน่อย
Alexander Trauzzi

1
มันไม่สมเหตุสมผลเห็นด้วยกับ Keith อย่างสมบูรณ์ฉันไม่ได้รับ upvotes ทั้งหมดจริงๆ ทำไมคุณต้องเพิ่มรหัสที่ไม่จำเป็น? public Task MyVoidAsyncMethod() {}เป็นวิธีเดียวกับข้างต้นอย่างสมบูรณ์ หากมี usecase สำหรับการใช้งานเช่นนี้โปรดเพิ่มรหัสเพิ่มเติม
Nick N.



3

ฉันชอบTask completedTask = Task.CompletedTask;วิธีการแก้ปัญหาของ. Net 4.6 แต่อีกวิธีหนึ่งคือการทำเครื่องหมายวิธีการ async และส่งคืนโมฆะ:

    public async Task WillBeLongRunningAsyncInTheMajorityOfImplementations()
    {
    }

คุณจะได้รับคำเตือน (CS1998 - ฟังก์ชัน Async โดยไม่ต้องรอนิพจน์) แต่การเพิกเฉยในบริบทนี้จะปลอดภัย


1
หากวิธีการของคุณกลับเป็นโมฆะคุณสามารถมีปัญหากับข้อยกเว้น
Adam Tuliper - MSFT

0

หากคุณกำลังใช้ข้อมูลทั่วไปคำตอบทั้งหมดจะทำให้เรารวบรวมข้อผิดพลาด return default(T);คุณสามารถใช้ ตัวอย่างด้านล่างเพื่ออธิบายเพิ่มเติม

public async Task<T> GetItemAsync<T>(string id)
            {
                try
                {
                    var response = await this._container.ReadItemAsync<T>(id, new PartitionKey(id));
                    return response.Resource;
                }
                catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
                {

                    return default(T);
                }

            }

ทำไมต้องลงคะแนน?
Karthikeyan VK

คำถามที่ไม่ได้เกี่ยวกับวิธีการ async :)
โฟรดนิลเซน

0
return await Task.FromResult(new MyClass());

3
แม้ว่ารหัสนี้อาจแก้ปัญหาได้รวมถึงคำอธิบายว่าทำไมและวิธีแก้ปัญหานี้จะช่วยปรับปรุงคุณภาพการโพสต์ของคุณได้อย่างไรและอาจส่งผลให้คะแนนมากขึ้น จำไว้ว่าคุณกำลังตอบคำถามสำหรับผู้อ่านในอนาคตไม่ใช่เพียงแค่คนที่ถามตอนนี้ โปรดแก้ไขคำตอบของคุณเพื่อเพิ่มคำอธิบายและระบุข้อ จำกัด และสมมติฐานที่ใช้
David Buck
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.