36148


`.Net Framework
4.6.1`以降のC#にて、戻り値が`void`で引数に`Action`を使う非同期処理用のメソッドに対してasync/awaitを適用する方法はあるのでしょうか？

下記のサンプルコードにおける`ReadSQLWrapper`メソッドのように、`Action`で戻ってきた値をラムダ式の外側で取得して、非同期実行の戻り値として返せることが理想です。

サンプルコード修正の前提条件として`DBManagerMock`自体は改修できないものとさせてください。

[code]

    using System;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                new Program().Execute();
            }
    
            private void Execute()
            {
                var sqls = new string[] { "select hoge;", "select fuga;", "select piyo;" };
                //理想のコード
                var tasks = sqls.Select(ReadSQLWrapper).ToArray();
                Task.WaitAll(tasks);    //Taskなら同期実行も非同期実行も思うがまま
                tasks.Select(t => t.Result).ToList().ForEach(Console.WriteLine);
            }
    
            //懸案となる、うまく動作しないasyncの疑似メソッド
            private async Task<string> ReadSQLWrapper(string sql)
            {
                var db = new DBManagerMock();
                string result = "ダミーの初期化子"; //DBManagerMockのReadSQLメソッドのActionを返したい
                //await db.ReadSQL(sql, r => { result = r; }); //エラー CS4008  'void' を待機することができません
                return result;
            }
        }
    
        //このクラスは改修不可
        public class DBManagerMock
        {
            public void ReadSQL(string sql, Action<string> result)
            {
                //非同期でDB接続して結果を返す
                result(string.Format("{0}の実行結果", sql));
            }
        }
    }
    
[/code]

`AutoResetEvent`でも理想に近いコーディングは可能ですが、非同期実行時の[実行待ち件数の上限がある](https://stackoverflow.com/q/2702545)ため、`async
Task`を返す方がより利便性が高いと期待しています。

そもそもasync/awaitの理解に問題があるようでしたらご指摘いただければ幸いです。


CC BY-SA 3.0
2017-07-06T09:49:17.217
36146
2017-07-07T01:50:14.377
2017-07-07T01:50:14.377
9820
9820
question
c#
C#にてActionを返す非同期メソッドをasyncメソッドでラップしたい


TPL以外の非同期処理をブリッジしたい場合は`TaskCompletionSource<T>`で`Task<T>`を作成するとタスクの状態を明示的に制御できます。

[code]

    var tcs = new TaskCompletionSource<string>();
    new DBManagerMock().ReadSQL(tcs.SetResult);
    return tcs.Task;
    
[/code]

なお例外が発生した場合には`SetException`を呼び出すなどの考慮が必要ですが、元のAPIでの扱いが不明なので無視しています。


CC BY-SA 3.0
2017-07-06T11:29:27.830
36148
2017-07-06T11:29:27.830
5750
36146
answer


@pgrho さんの回答を元にTaskCompletionSourceで非同期処理をブリッジし、例外処理を追加してみました。

[code]

    using System;
    using System.Linq;
    using System.Threading.Tasks;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            static void Main(string[] args)
            {
                new Program().Execute();
            }
    
            private void Execute()
            {
                var sqls = new string[] { "select hoge;", "select fuga;", "select piyo;", null };
                var tasks = sqls.Select(ReadSQLWrapper).ToArray();
                try
                {
                    Task.WaitAll(tasks);    //Taskなら同期実行も非同期実行も思うがまま
                }
                catch (AggregateException ae)
                {
                    ae.Handle(e =>
                    {
                        return (e is FormatException);  //不正なSQLがあっても処理を続行する
                    });
                }
                //Where文で、ハンドル済みのエラーを除外
                tasks.Where(t => !t.IsFaulted).Select(t => t.Result).ToList().ForEach(Console.WriteLine);
            }
    
            private Task<string> ReadSQLWrapper(string sql)
            {
                var tcs = new TaskCompletionSource<string>();
                try
                {
                    new DBManagerMock().ReadSQL(sql, tcs.SetResult);
                }
                catch (Exception e)
                {
                    //try-catchで例外処理
                    tcs.SetException(e);
                }
                return tcs.Task;
            }
        }
    
        public class DBManagerMock
        {
            public void ReadSQL(string sql, Action<string> result)
            {
                if (string.IsNullOrEmpty(sql)) throw new FormatException("不正なSQLです"); //空文字は不正なSQLとして例外を返す
                //非同期でDB接続して結果を返す
                result(string.Format("{0}の実行結果", sql));
            }
        }
    }
    
[/code]


CC BY-SA 3.0
2017-07-07T01:49:23.017
36167
2017-07-07T01:49:23.017
9820
36146
answer