C# WebAPI 非同期処理 WhenAll 動かない(返ってこない)

ASP.NET

おはこんばんにちは!やっこです。

今回、C#でWebAPIを作成する流れで非同期処理でWhenAllを使用したのですが、全く動かず。。。

その時の原因と対処法をまとめました

原因

コントローラー → ビジネス → データアクセスという構成でAPIを実現しようとしており、非同期処理はビジネス層に記載していました。

ビジネス層で非同期処理を行った場合は、上流(ここで言うコントローラー)も非同期処理として扱わないといけなかったです。。。

補足

ちなみにDBは下記のようなデータで、指定されたidのitemを返すようなAPIを作成します

そして、リクエストは下記になります

@localUrl = https://localhost:44397

###
GET {{localUrl}}/api/values/?ids=1,2,3,4
###

現象(コード)

コントローラー

 public class ValuesController : ApiController
    {
        // GET api/values/ids
        public string[] Get(string ids)
        {
            string[] ret = null;

            // 文字列のパラメータを数値配列に変換
            string[] strArray = ids.Split(',');
            int[] intArray = strArray.Select(int.Parse).ToArray();

            // ビジネス層呼び出し
            ret = Business.ValueBusiness.GetValueBusiness(intArray).Result;

            return ret;
        }
    }

ビジネス

public class ValueBusiness
    {
        public static async Task<string[]> GetValueBusiness(int[] ids)
        {
            // 非同期で処理
            List<Task<string>> tasks = new List<Task<string>>();

            // 指定されたidの数だけタスクを作成
            foreach (int id in ids)
            {
                tasks.Add(Task.Run(() => GetStringAndSleep(id)));
            }

            // タスクの実行
            string[] result = await Task.WhenAll(tasks);

            return result;
        }

        /// <summary>
        /// データアクセス層の呼び出して、一時待機
        /// </summary>
        /// <param name="id"></param>
        /// <returns>文字列</returns>
        private static string GetStringAndSleep(int id)
        {
            // Daoにて値を抽出
            string ret = DataAccess.ValueDao.GetValue(id);

            // 一時待機
            Thread.Sleep(3000);

            return ret;
        }
    }

コントローラーは単純にHTMLリクエストを受けてをビジネスの呼び出し戻り値の返却を行っています。

ビジネスでは受け取った「ids」を分解し、id一つ一つに対し「データアクセス呼び出す&3秒待機」するTaskを割り当て、15行目の「await Task.WhenAll(tasks)」にて非同期的にすべて実行しています。

データアクセス層は特に影響しないため記載しません。

上記のコードでリクエストを投げても何も返ってきません。

解決(コード)

コントローラー

public class ValuesController : ApiController
    {

        // GET api/values/5
        public async Task<string[]> Get(string ids)
        {
            string[] ret = null;

            // 文字列のパラメータを数値配列に変換
            string[] strArray = ids.Split(',');
            int[] intArray = strArray.Select(int.Parse).ToArray();

            // ビジネス層呼び出し
            ret = await Business.ValueBusiness.GetValueBusiness(intArray);

            return ret;
        }
    }

修正したのはコントローラーのみになります。

14行目に「await」を追記し、それに伴いメソッドを非同期メソッドとして扱うようにします(5行目)

このコードでリクエストを投げると。。。

戻り値が返ってきました。

所要時間も3024sなので非同期処理もできていますね。

まとめ

コントローラーまで非同期メソッドとしなければならないとは。。。
地味にハマってしまいました。

なんとか解決できましたが、使い方的にあっているのかは不明ですね

JavaScriptのPromiseといい、非同期処理には頭を悩まされます