使接口实现异步


116

我目前正在尝试使用一些异步方法来制作我的应用程序。我所有的IO都是通过接口的显式实现来完成的,我对如何使操作异步感到有些困惑。

如我所见,我在实现中有两个选择:

interface IIO
{
    void DoOperation();
}

选项1: 执行一个隐式实现异步并等待隐式实现中的结果。

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
}

选项2: 执行显式实现异步并等待隐式实现中的任务。

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

    #region IIOAsync Members

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

    #endregion
}

这些实现中的一种优于另一种吗?或者还有我没有想到的另一种方法吗?

Answers:


231

这些选项都不正确。您正在尝试异步实现同步接口。不要那样做 问题在于,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。)


显式实现的外观如何?另外,您在哪里等待该实施?
Moriya 2013年

1
@Animal明确执行看起来一如既往(只需添加相同asyncasync Task IIO.DoOperationAsync()。你的意思是你await回到Task哪里了?无论你在哪里打电话DoOperationAsync()
svick

基本上,我认为我可以将我的问题简化为“我在哪里等待?” 如果我不在异步方法中等待,则会收到编译警告。
Moriya 2013年

1
理想情况下,您不需要将IO代码包装在中Task.Run(),因为IO代码本身应该是异步的,而您可以await直接这样做。例如line = await streamReader.ReadLineAsync()
svick

4
那么,使代码异步就没有多大意义了。请参阅文章是否应为异步方法公开异步包装器?
svick

19

更好的解决方案是为异步操作引入另一个接口。新接口必须继承原始接口。

例:

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重新设计您的异步操作,以便它们返回Task而不是void,除非您确实必须返回void。


5
为什么不GetAwaiter().GetResult()代替Wait()呢?这样,您无需解压AggregateException即可获取内部异常。
Tagc

一个变化是依靠类实现多个(可能是显式)接口:class Impl : IIO, IIOAsync。但是,IIO和IIOAsync本身是不同的合同,可以避免将“旧合同”引入新代码中。var c = new Impl(); IIOAsync asAsync = c; IIO asSync = c
user2864740
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.