インターフェース実装を非同期にする


116

現在、いくつかの非同期メソッドを使用してアプリケーションを作成しようとしています。私のすべてのIOは、インターフェイスの明示的な実装を通じて行われ、操作を非同期にする方法について少し混乱しています。

私が見ているように、私は実装に2つのオプションがあります:

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
}

これらの実装の1つが他の実装よりも優れているか、または私が考えていない別の方法がありますか?

回答:


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


これは明示的な実装でどのように見えるでしょうか?また、この実装をどこで待ちますか?
守谷

1
@Animal明示的な実装は常に(ちょうど追加と同じになりますasync): async Task IIO.DoOperationAsync()。そして、あなたはどこawaitに戻ってきたのTaskですか?どこに電話するDoOperationAsync()
2013年

基本的に、私は「どこで待ちますか」という質問に要約できると思います。asyncメソッド内で待機しないと、コンパイル警告が表示されます。
守谷

1
理想的には、IOコードをでラップする必要はありませんTask.Run()。そのIOコードはそれ自体が非同期でありawait、直接それを行う必要があります。例えばline = await streamReader.ReadLineAsync()
2013年

4
その場合、コードを非同期にすることにはあまり意味がありません。記事を参照してください。万一、私は同期メソッドの非同期ラッパーを公開?
2013年

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実際にvoidを返す必要がない限り、非同期操作を再設計して、voidではなく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.