Thực hiện triển khai giao diện không đồng bộ


116

Tôi hiện đang cố gắng tạo ứng dụng của mình bằng một số phương pháp Async. Tất cả IO của tôi được thực hiện thông qua việc triển khai rõ ràng một giao diện và tôi hơi bối rối về cách làm cho các hoạt động không đồng bộ.

Khi tôi thấy những điều, tôi có hai tùy chọn trong việc triển khai:

interface IIO
{
    void DoOperation();
}

TÙY CHỌN1: Thực hiện không đồng bộ triển khai ngầm định và chờ đợi kết quả trong quá trình triển khai ngầm định.

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
}

TÙY CHỌN 2: Thực hiện không đồng bộ triển khai rõ ràng và chờ tác vụ từ triển khai ngầm định.

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

    #region IIOAsync Members

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

    #endregion
}

Một trong những cách triển khai này có tốt hơn cách triển khai kia không hay có cách nào khác để thực hiện mà tôi chưa nghĩ đến?

Câu trả lời:


231

Cả hai tùy chọn này đều không đúng. Bạn đang cố gắng triển khai giao diện đồng bộ một cách không đồng bộ. Đừng làm vậy. Vấn đề là khi DoOperation()trả về, hoạt động vẫn chưa hoàn tất. Tệ hơn nữa, nếu một ngoại lệ xảy ra trong quá trình hoạt động (điều này rất phổ biến với các hoạt động IO), người dùng sẽ không có cơ hội đối phó với ngoại lệ đó.

Những gì bạn cần làm là sửa đổi giao diện để nó không đồng bộ:

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

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

Bằng cách này, người dùng sẽ thấy rằng hoạt động là asyncvà họ sẽ có thể thực hiện được await. Điều này cũng buộc người dùng mã của bạn phải chuyển sang async, nhưng điều đó là không thể tránh khỏi.

Ngoài ra, tôi cho rằng việc sử dụng StartNew()trong triển khai của bạn chỉ là một ví dụ, bạn không cần điều đó để triển khai IO không đồng bộ. (Và new Task()thậm chí còn tồi tệ hơn, đó sẽ thậm chí không làm việc, bởi vì bạn không Start()những Task.)


Điều này sẽ trông như thế nào với một triển khai rõ ràng? Ngoài ra, bạn chờ đợi việc triển khai này ở đâu?
Moriya

1
Thực hiện Explicit @Animal sẽ trông giống nhau như mọi khi (chỉ cần thêm async): async Task IIO.DoOperationAsync(). Và bạn có nghĩa là bạn đã awaittrả lại ở Taskđâu? Bất cứ nơi nào bạn gọi DoOperationAsync().
svick

Về cơ bản, tôi nghĩ rằng tôi có thể chuyển câu hỏi của mình thành "Tôi đang chờ ở đâu?" Nếu tôi không đợi bên trong phương thức không đồng bộ, tôi sẽ nhận được cảnh báo biên dịch.
Moriya

1
Lý tưởng nhất là bạn không cần phải bọc mã IO vào Task.Run(), mã IO đó phải tự không đồng bộ và bạn thực hiện awaitđiều đó trực tiếp. Vd line = await streamReader.ReadLineAsync().
svick

4
Sau đó, không có nhiều điểm trong việc làm cho mã của bạn không đồng bộ. Xem bài viết Tôi có nên hiển thị trình bao bọc không đồng bộ cho các phương thức đồng bộ không?
svick

19

Giải pháp tốt hơn là giới thiệu một giao diện khác cho các hoạt động không đồng bộ. Giao diện mới phải kế thừa từ giao diện gốc.

Thí dụ:

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);
    }
}

PS Thiết kế lại các hoạt động không đồng bộ của bạn để chúng trả về Tác vụ thay vì vô hiệu, trừ khi bạn thực sự phải trả về giá trị vô hiệu.


5
Tại sao không GetAwaiter().GetResult()thay vì Wait()? Bằng cách đó, bạn không cần phải giải nén một AggregateExceptionđể tìm nạp ngoại lệ bên trong.
Tagc

Một biến thể là dựa vào các lớp thực hiện nhiều (có thể rõ ràng) giao diện: class Impl : IIO, IIOAsync. Tuy nhiên, bản thân IIO và IIOAsync là những hợp đồng khác nhau có thể tránh kéo 'hợp đồng cũ' sang mã mới hơn. var c = new Impl(); IIOAsync asAsync = c; IIO asSync = c.
dùng2864740
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.