การใช้งานอินเทอร์เฟซแบบ async


117

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

เมื่อฉันเห็นสิ่งต่างๆฉันมีสองทางเลือกในการใช้งาน:

interface IIO
{
    void DoOperation();
}

ตัวเลือกที่ 1: ทำการ async การใช้งานโดยนัยและรอผลในการนำไปใช้โดยปริยาย

class IOImplementation : IIO
{

     async void DoOperation()
    {
        await Task.Factory.StartNew(() =>
            {
                //WRITING A FILE OR SOME SUCH THINGAMAGIG
            });
    }

    #region IIO Members

    void IIO.DoOperation()
    {
        DoOperation();
    }

    #endregion
}

OPTION2: ทำการ async การนำไปใช้งานอย่างชัดเจนและรองานจากการนำไปใช้โดยปริยาย

class IOAsyncImplementation : IIO
{
    private Task DoOperationAsync()
    {
        return new Task(() =>
            {
                //DO ALL THE HEAVY LIFTING!!!
            });
    }

    #region IIOAsync Members

    async void IIO.DoOperation()
    {
        await DoOperationAsync();
    }

    #endregion
}

การใช้งานอย่างใดอย่างหนึ่งเหล่านี้ดีกว่าอีกวิธีหนึ่งหรือมีวิธีอื่นที่ฉันไม่ได้คิด

คำตอบ:


232

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

สิ่งที่คุณต้องทำคือแก้ไขอินเทอร์เฟซเพื่อให้เป็นแบบอะซิงโครนัส:

interface IIO
{
    Task DoOperationAsync(); // note: no async here
}

class IOImplementation : IIO
{
    public async Task DoOperationAsync()
    {
        // perform the operation here
    }
}

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

นอกจากนี้ฉันถือว่าการใช้StartNew()ในการใช้งานของคุณเป็นเพียงตัวอย่างเท่านั้นคุณไม่จำเป็นต้องใช้สิ่งนั้นในการใช้ IO แบบอะซิงโครนัส (และnew Task()จะยิ่งแย่กว่าที่จะไม่ได้ทำงานเพราะคุณทำไม่ได้.)Start()Task


สิ่งนี้จะมีลักษณะอย่างไรเมื่อใช้งานอย่างชัดเจน นอกจากนี้คุณรอการใช้งานนี้ที่ไหน
โมรียา

1
การดำเนินงานที่ชัดเจน @Animal จะมีลักษณะเดียวกันเช่นเคย (เพิ่ม):async async Task IIO.DoOperationAsync()และคุณหมายถึงที่awaitส่งคืนTaskหรือไม่? ทุกที่ที่คุณโทรDoOperationAsync().
svick

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

1
ตามหลักการแล้วคุณไม่จำเป็นต้องรวมรหัสTask.Run()IO เข้าด้วยกันรหัส IO นั้นควรเป็นแบบอะซิงโครนัสเองและคุณจะทำเช่นawaitนั้นโดยตรง เช่นline = await streamReader.ReadLineAsync().
svick

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

20

ทางออกที่ดีกว่าคือการแนะนำอินเทอร์เฟซอื่นสำหรับการดำเนินการ async อินเทอร์เฟซใหม่ต้องสืบทอดจากอินเทอร์เฟซดั้งเดิม

ตัวอย่าง:

interface IIO
{
    void DoOperation();
}

interface IIOAsync : IIO
{
    Task DoOperationAsync();
}


class ClsAsync : IIOAsync
{
    public void DoOperation()
    {
        DoOperationAsync().GetAwaiter().GetResult();
    }

    public async Task DoOperationAsync()
    {
        //just an async code demo
        await Task.Delay(1000);
    }
}


class Program
{
    static void Main(string[] args)
    {
        IIOAsync asAsync = new ClsAsync();
        IIO asSync = asAsync;

        Console.WriteLine(DateTime.Now.Second);

        asAsync.DoOperation();
        Console.WriteLine("After call to sync func using Async iface: {0}", 
            DateTime.Now.Second);

        asAsync.DoOperationAsync().GetAwaiter().GetResult();
        Console.WriteLine("After call to async func using Async iface: {0}", 
            DateTime.Now.Second);

        asSync.DoOperation();
        Console.WriteLine("After call to sync func using Sync iface: {0}", 
            DateTime.Now.Second);

        Console.ReadKey(true);
    }
}

ป.ล. ออกแบบการดำเนินการ async ของคุณใหม่เพื่อให้ส่งคืน Task แทนที่จะเป็นโมฆะเว้นแต่คุณจะต้องคืนโมฆะจริงๆ


5
ทำไมไม่GetAwaiter().GetResult()แทนที่จะเป็นWait()? ด้วยวิธีนี้คุณไม่จำเป็นต้องแกะกล่องAggregateExceptionเพื่อดึงข้อยกเว้นภายใน
Tagc

รูปแบบคือพึ่งพาการดำเนินการหลายชั้น (อาจจะชัดเจน) class Impl : IIO, IIOAsyncอินเตอร์เฟซ: อย่างไรก็ตาม IIO และ IIOAsync เป็นสัญญาที่แตกต่างกันซึ่งสามารถหลีกเลี่ยงการดึง 'สัญญาเก่า' มาเป็นรหัสที่ใหม่กว่า var c = new Impl(); IIOAsync asAsync = c; IIO asSync = c.
user2864740
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.