我需要做什么
我需要使用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
调用现在正在线程池线程上运行,所以它不会尝试返回到主线程,一切似乎正常。但由于上述原因,我无法使用此解决方案。
答案 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**.
}