Несколько задач возвращает неверный результат - PullRequest
0 голосов
/ 06 ноября 2018

Что мне нужно сделать

Мне нужно запустить разные экземпляры класса в синхронном контексте, используя асинхронный метод.

Структура приложения

В моем приложении console я объявил List<Bot> класс:

private List<Bot> _bots = new List<Bot>(new Bot[10]);

класс Bot содержит некоторые методы, которые получают данные из Интернета, поэтому эти методы нужно ждать. Структура метода выглядит следующим образом:

public class Bot
{
    Competition Comp { get; set; }

    public async Task StartAsync(int instance) 
    {
         string url = "";

         //based on the instance I take the data from different source.
         switch(instance)
         {
             case 0:
                url = "www.google.com";
                break;
             case 1:
                url = "www.bing.com";
                break;
         }

         //Comp property contains different groups.
         Comp.Groups = await GetCompetitionAsync(Comp, url);

         if(Comp.Groups.Count > 0)
         {
             foreach(var gp in group)
             {
                //add data inside database.
             }
         }
     }
 }

класс Competition имеет следующий дизайн:

public class Competition 
{
    public string Name { get; set; }
    public List<string> Groups { get; set; } 
}

Я запускаю все экземпляры класса Bot, используя следующий код:

for(int i = 0; i < _bots.Count - 1; i++)
{
   _bots[i].StartAsync(i);
}

этот код будет вызывать разные времена StartAsync класса Bot, таким образом, я могу управлять каждым экземпляром бота, и в конце концов я могу остановить или запустить конкретный экземпляр в отдельном методе.

Проблема

Метод GetCompetitionAsync Создание List<string>:

public async Task<List<string>> GetCompetitionAsync(Competition comp, string url)
{
     if(comp == null)
        comp = new Competition();

     List<string> groups = new List<string();

     using (var httpResonse = await httpClient.GetAsync(url))
     {
        string content = await httpResponse.Content.ReadAsStringAsync();
        //fill list groups
     }

     return groups;
}

По сути, этот метод заполнит List<string>, доступный в Comp. Теперь, если я выполняю один экземпляр StartAsync, все работает хорошо, но когда я запускаю несколько экземпляров (как указано выше), объект Comp (который содержит Competition) имеет все свойства NULL.

Похоже, что когда у меня несколько Task, выполняющих контекст synchronous, он не ожидает контекст async, который в этом случае заполняет List<string>. Когда код достигает этой строки: if(Competition.Groups.Count > 0), я получаю исключение NULL, потому что Groups равно нулю, а другие свойства Comp равны NULL.

Как мне справиться с этой ситуацией?

UPDATE

После других попыток я решил создать List<Task> вместо List<Bot>:

List<Task> tasks = new List<Task>(new Task[10]);

тогда вместо:

for(int i = 0; i < _bots.Count - 1; i++)
{
   _bots[i].StartAsync(i);
}

Я сделал:

for (int i = 0; i < tasks.Count - 1; i++)
{
    Console.WriteLine("Starting " + i);

    if (tasks[i] == null)
        tasks[i] = new Task(async () => await new Bot().StartAsync(i));

видимо все работает хорошо, ошибок нет. Проблема в том, почему? Я думаю, что-то вроде deadlock, что я даже не могу решить, используя ConfigureAwait(false);.

Последнее решение также не позволяет мне получить доступ к методу Bot, потому что теперь это Task.

ОБНОВЛЕНИЕ 2

Хорошо, возможно, я понял проблему. По сути, await внутри асинхронного метода StartAsync пытается вернуться в основной поток, в то время как основной поток занят ожиданием завершения задачи, и это создаст deadlock.

Вот почему перемещение StartAsync() внутри List<Task> работает, потому что теперь вызов async теперь выполняется в потоке пула потоков, он не пытается вернуться к основному потоку, и все кажется к работам. Но я не могу использовать это решение по причинам, изложенным выше.

Ответы [ 2 ]

0 голосов
/ 07 ноября 2018

Я предпочитаю использовать темы вместо задач. ИМХО, темы более простые для понимания. Примечание: кажется, что свойство Bot.Comp в вашем коде НЕ инициализировано! Я исправляю эту проблему. Моя версия вашего кода:

public class Bot
{
    Competition Comp { get; set; }
    System.Thread _thread;
    private int _instance;

    public Bot()
    {
        Comp = new Competition ();
    }
    public void Start(int instance) 
    {
        _instance = instance;
        _thread = new Thread(StartAsync);
        _thread.Start();
    }

    private void StartAsync() 
    {
         string url = "";

         //based on the instance I take the data from different source.
         switch(_instance)
         {
             case 0:
                url = "www.google.com";
                break;
             case 1:
                url = "www.bing.com";
                break;
         }

         //Comp property contains different groups.
         GetCompetitionAsync(Comp, url);

         if(Comp.Groups.Count > 0)
         {
             foreach(var gp in group)
             {
                //add data inside database.
             }
         }
     }

     public List<string> GetCompetitionAsync(Competition comp, string url)
     {
          if(comp.groups == null)  comp.groups = new List<string>();

          using (var httpResonse = httpClient.GetAsync(url))
          {
             string content = await httpResponse.Content.ReadAsStringAsync();
             //fill list groups
          }
          return groups;
     }
}

Затем запускаем потоки:

for(int i = 0; i < _bots.Count - 1; i++)
{
   _bots[i].Start(i);
}

Каждый экземпляр бота запускает метод private void StartAsync () в своем собственном потоке.

Обратите внимание на реализацию метода Bot.Start ():

public void Start(int instance) 
{
    _instance = instance;
    _thread = new Thread(StartAsync); //At this line: set method Bot.StartAsync as entry point for new thread.
    _thread.Start();//At this line: call of _thread.Start() starts new thread and returns **immediately**.
}
0 голосов
/ 06 ноября 2018

Такого рода вещи намного проще, если вы думаете с точки зрения списков и «чистых» функций - функций, которые принимают ввод и возвращают вывод. Не передавайте им что-либо, чтобы заполнить или видоизменить.

Например, эта функция принимает строку и возвращает группы:

List<string> ExtractGroups(string content)
{
    var list = new List<string>();
    //Populate list
    return  list;
}

Эта функция принимает URL-адрес и возвращает его группы.

async Task<List<string>> GetCompetitionAsync(string url)
{
    using (var httpResponse = await httpClient.GetAsync(url))
    {
        string content = await httpResponse.Content.ReadAsStringAsync();
        return ExtractGroups(content);
    }
 }

И эта функция принимает список URL-адресов и возвращает все группы как один список.

async Task<List<string>> GetAllGroups(string[] urls)
{
    var tasks = urls.Select( u => GetCompetitionAsync(u) );
    await Task.WhenAll(tasks);
    return tasks.SelectMany( t => t.Result );
}

Затем вы можете поместить данные в базу данных, как и планировали.

var groups = GetAllGroups( new string[] { "www.google.com", "www.bing.com" }  );        
foreach(var gp in groups)
{
    //add data inside database.
}

Видите, насколько проще, когда вы разбиваете это таким образом?

...