多个任务返回错误结果

时间:2018-11-06 17:56:43

标签: c# multithreading console task

我需要做什么

我需要使用async方法在同步上下文中启动类的不同实例。

应用程序结构

在我的console应用程序中,我声明了一个List<Bot>类:

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

Bot包含一些从Internet上获取数据的方法,因此需要等待这些方法。方法结构如下:

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

如何处理这种情况?

更新

经过其他尝试,我虽然创建了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 :(得分:1)

如果您考虑列表和“纯”函数(接受输入并返回输出的函数),这种事情要简单得多。不要传递任何东西让他们填充或变异。

例如,此函数接受一个字符串并返回组:

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.
}

看看以这种方式分解它有多简单?

答案 1 :(得分:1)

我更喜欢使用线程而不是任务。恕我直言,线程更容易理解。 注意:您的代码中的属性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);
}

每个Bot实例在其自己的线程中启动方法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**.
}