Использование многопоточности с Async-await C # - PullRequest
0 голосов
/ 27 июня 2018

Я написал асинхронную функцию для вызова данных из Facebook, она работает, но проблема в том, я полагаю, она работает. Может кто-нибудь объяснить мне?

public class FacebookData
    {
        static string fb_api_version = ConfigurationManager.AppSettings["fb_ver"];
        static string accessToken = ConfigurationManager.AppSettings["accessToken"];
        static string fb_id = "";
        private HttpClient _httpClient;
        public FacebookData(string input_id)
        {
            fb_id = input_id;
            _httpClient = new HttpClient
            {
                BaseAddress = new Uri("https://graph.facebook.com/" + fb_api_version + "/"),
                Timeout = TimeSpan.FromSeconds(15)
            };
            _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        }

        public async Task<T> getData<T>()
        {
            var response = await _httpClient.GetAsync($"{fb_id}?access_token={accessToken}");
            if (!response.IsSuccessStatusCode)
                return default(T);

            var result = await response.Content.ReadAsStringAsync();
            return JsonConvert.DeserializeObject<T>(result);
        }
    }

Класс вызова является типичным, я ожидаю ответа.

Но проблема в том, где я это называю.

На основной

static void Main(string[] args)
{
   string[] data_Set = [//ids_group]

   for (int i = 0; i < data_Set.length; ++i){
       Console.WriteLine("Running Thread " + (i+1).ToString());
       var dataSet = facebookRequestCmd(data_Set[i]);
       writeToTXT(dataSet);
       Console.WriteLine("Finished Thread " + (i + 1).ToString());
       //do sth
   }
}

В facebookRequestCmd

static Dictionary<string, string[]> facebookRequestCmd(string ids){
   Dictionary<string, string[]> allData = new Dictionary<string, string[]>();
   string[] ids_arr = ids.split(",")
   for (var i = 0; i < ids.length; i++){
      var facebook_client = new FacebookData(sqlData);
      var response = facebook_client.getData<dynamic>();
      Task.WaitAll(response);

      //then use the result to do sth
   }
}

В моем понимании, каждый раз, когда я вызываю getData, он уже возвращается к основному потоку, поскольку ожидает данные. Таким образом, задача на самом деле не запускает новый поток.

Таким образом, async await работает для ожидания http-запроса, но Threading не должен работать.

Тем не менее,

Console.WriteLine("Running Thread " + (i+1).ToString());

выскакивает одновременно, как будто я действительно делаю Thread в цикле for в основной функции.

Почему? И это способ использовать многопоточность с Async-await. Как я хочу сделать несколько звонков одновременно.

Изначально я использую Parallel.ForEach для запуска вызова, однако это не асинхронно и блокирует поток.

1 Ответ

0 голосов
/ 27 июня 2018

Хорошо, не стесняйтесь игнорировать все сделанные мной изменения, но я не мог не изменить способ чтения некоторых переменных и внешний вид кода. Это не рабочее приложение, и я, очевидно, не проверял его. Это просто исправленная версия того, что у вас есть, с предложенным способом использования Task. Это также издевается, используя только код, который вы предоставили, так что это то, что есть. # 2, как я полагаю, вам нужен ответ.

  1. В Main я удалил слова «нить», поскольку на самом деле это не то, что происходит. Может быть, но мы не знаем, действительно ли HttpClient запускает новый поток или просто удерживает / возвращает после вызова rest. Использование async / await не всегда означает, что Thread был запущен (хотя обычно так думают).
  2. Я использовал .Result (не Wait(), как я предлагал в комментариях), чтобы получить результат задачи. Это нормально, поскольку это консольное приложение, но оно не идеально для реального приложения, которое должно работать без блокировки. Я также удалил Task.WaitAll с этим изменением.
  3. Я переименовал функции, чтобы они были в полном порядке, потому что, IMO, функции должны выполнять работу, а имена должны описывать выполняемую работу.
  4. Я переименовал некоторые переменные, потому что, IMO, переменные должны быть PascalCase, когда их область видимости отсутствует в методе или private, и camelCase, когда они есть. Имена также должны быть, IMO, такими, за которыми следует Type, что имеет смысл.
  5. Я добавил 'Async' к именам функций, которые возвращают работающий Task.
  6. Изменено FacebookClient на одноэлементное и разрешено использовать только один HttpClient вместо многих и разрешено его удаление; плюс еще.
  7. Добавлена ​​альтернативная версия функции GetFacebookData, которая вызывает задачи и ожидает их всех одновременно.

static void Main(string[] args)
{
    string[] dataSet = new string[] { /* mocked */ };  // [ids_group]; <- no idea what this is so I mocked it.

    for (int i = 0; i < dataSet.Length; i++)
    {
        Console.WriteLine("Main... " + (i + 1).ToString());
        var result = GetFacebookData(dataSet[i]);
        WriteToTxt(result);
        Console.WriteLine("Complete... " + (i + 1).ToString());
        //do sth
    }

    Console.Read();
}

private static Dictionary<string, string[]> GetFacebookData(string idsString)
{
    var allDataDictionary = new Dictionary<string, string[]>();
    var idsArray = idsString.Split(',');

    foreach (var id in idsArray)
    {
        var response = FacebookClient.Instance.GetDataAsync<string[]>(id).Result;
        allDataDictionary.Add(id, response);
    }

    return allDataDictionary;
}

public class FacebookClient
{
    private readonly HttpClient httpClient;
    private readonly string facebookApiVersion;
    private readonly string accessToken;

    public static FacebookClient Instance { get; } = new FacebookClient();

    FacebookClient()
    {
        facebookApiVersion = ConfigurationManager.AppSettings["fb_ver"];
        accessToken = ConfigurationManager.AppSettings["accessToken"];

        httpClient = new HttpClient
        {
            BaseAddress = new Uri("https://graph.facebook.com/" + facebookApiVersion + "/"),
            Timeout = TimeSpan.FromSeconds(15)
        };

        httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    }

    public async Task<T> GetDataAsync<T>(string facebookId)
    {
        var response = await httpClient.GetAsync($"{facebookId}?access_token={accessToken}");
        if (!response.IsSuccessStatusCode) return default;
        var result = await response.Content.ReadAsStringAsync();
        return JsonConvert.DeserializeObject<T>(result);
    }

    ~FacebookClient() => httpClient.Dispose();
}

Вот версия, которая запускает все задачи, а затем ожидает их всех одновременно. Я полагаю, что это может вызвать некоторые проблемы с HttpClient, но посмотрим.


private static Dictionary<string, string[]> GetFacebookData(string idsString)
{
    var allDataDictionary = new Dictionary<string, string[]>();
    var idsArray = idsString.Split(',');
    var getDataTasks = new List<Task<string[]>>();

    foreach (var id in idsArray)
    {
        getDataTasks.Add(FacebookClient.Instance.GetDataAsync<string[]>(id));         
    }

    var tasksArray = getDataTasks.ToArray();
    Task.WaitAll(tasksArray);
    var resultsArray = tasksArray.Select(task => task.Result).ToArray();

    for (var i = 0; i < idsArray.Length; i++)
    {
        allDataDictionary.Add(idsArray[i], resultsArray[i]);
    }

    return allDataDictionary;
}
...