我试图在循环中调用一个方法.它应该在10秒内只调用20次.我正在使用类似下面代码的信号量



首先使用下面的代码,一些调用没有被调用,比如说,在250、238个调用中,其余的没有。其次,我不确定这些调用是否以每10秒20个调用的速度进行。

public List<ShowData> GetAllShowAndTheirCast()
{
ShowResponse allShows = GetAllShows();
ShowCasts showCast = new ShowCasts();
showCast.showCastList = new List<ShowData>();

using (Semaphore pool = new Semaphore(20, 20))
{
for (int i = 0; i < allShows.Shows.Length; i++)
{
pool.WaitOne();
Thread t = new Thread(new ParameterizedThreadStart((taskId) =>
{
showCast.showCastList.Add(MapResponse(allShows.Shows[i]));
}));
pool.Release();
t.Start(i);
}
}
//for (int i = 0; i < allShows.Shows.Length; i++)
//{
//    showCast.showCastList.Add(MapResponse(allShows.Shows[i]));
//}
return showCast.showCastList;
}
public ShowData MapResponse(Show s)
{
CastResponse castres = new CastResponse();
castres.CastlistResponse = (GetShowCast(s.id)).CastlistResponse;
ShowData sd = new ShowData();
sd.id = s.id;
sd.name = s.name;
if (castres.CastlistResponse != null && castres.CastlistResponse.Any())
{
sd.cast = new List<CastData>();
foreach (var item in castres.CastlistResponse)
{
CastData cd = new CastData();
cd.birthday = item.person.birthday;
cd.id = item.person.id;
cd.name = item.person.name;
sd.cast.Add(cd);
}
}
return sd;
}
public ShowResponse GetAllShows()
{
ShowResponse response = new ShowResponse();
string showUrl = ClientAPIUtils.apiUrl + "shows";
response.Shows = JsonConvert.DeserializeObject<Show[]>(ClientAPIUtils.GetDataFromUrl(showUrl));
return response;
}
public CastResponse GetShowCast(int showid)
{
CastResponse res = new CastResponse();
string castUrl = ClientAPIUtils.apiUrl + "shows/" + showid + "/cast";
res.CastlistResponse = JsonConvert.DeserializeObject<List<Cast>>(ClientAPIUtils.GetDataFromUrl(castUrl));
return res;
}

所有的电话都应该拨打,但我不确定它们在哪里被中止,甚至请让我知道如何检查拨打的电话率。

我假设您的目标是处理有关演出的所有数据,但一次不超过20个。对于这种任务,您可能应该使用ThreadPool,并限制使用SetMaxThreads的最大并发线程数。

https://learn.microsoft.com/en-us/dotnet/api/system.threading.threadpool?view=netframework-4.7.2

您必须确保用于存储结果的集合是线程安全的。

showCast.showCastList = new List<ShowData>();

我不认为标准列表是线程安全的。线程安全集合是ConcurrentBag(还有其他集合)。您可以使标准列表线程安全,但它需要更多的代码。完成处理后,需要在列表或数组中显示结果,就可以将集合转换为所需的类型。

https://learn.microsoft.com/en-us/dotnet/api/system.collections.concurrent.concurrentbag-1?view=netframework-4.7.2

现在来谈谈信号量的用法。信号量所做的是确保一次最多可以创建20个线程。假设这个循环在你的应用程序主线程中运行,你的信号量就没有意义了。若要使其工作,您需要在线程完成后释放信号量;但您是在调用Release()之后调用线程Start()。这导致线程在"关键区域"之外执行。

using (Semaphore pool = new Semaphore(20, 20)) {
for (int i = 0; i < allShows.Shows.Length; i++) {
pool.WaitOne();
Thread t = new Thread(new ParameterizedThreadStart((taskId) =>
{
showCast.showCastList.Add(MapResponse(allShows.Shows[i]));
pool.Release();
}));            
t.Start(i);
}
}

我没有测试这个解决方案;可能会出现额外的问题。

这个程序的另一个问题是它没有等待所有线程完成。一旦启动了所有线程;程序将结束。有可能(在你的情况下,我相信)并不是所有线程都完成了它的操作;这就是为什么在程序结束时要完成约240个数据包的原因。

thread.Join();

但如果在Start()之后立即调用,它将停止主线程,直到它完成,所以为了保持程序的并发性,您需要创建一个线程列表,并在程序结束时Join()它们。这不是最好的解决方案。如何在程序可以添加到ThreadPool的所有线程上等待

等待所有线程在线程池中完成工作

最后要注意的是,您不能像那样访问循环计数器。循环计数器的最终值稍后进行评估,并运行测试;代码有两次处理奇数记录和跳过偶数记录的倾向。之所以会发生这种情况,是因为循环在执行前一个线程之前增加了计数器,并导致访问数组边界之外的元素。

可能的解决方案是创建将创建线程的方法。将其置于单独的方法中,将在下一次循环通过之前评估allShows.Shows[i]show

public void CreateAndStartThread(Show show, Semaphore pool, ShowCasts showCast)
{
pool.WaitOne();
Thread t = new Thread(new ParameterizedThreadStart((s) => {
showCast.showCastList.Add(MapResponse((Show)s));
pool.Release();
}));
t.Start(show);
}

并发编程很棘手,我强烈建议您做一些关于常见陷阱的示例练习。关于C#编程的书肯定会有一两章关于这个主题。有很多关于这个主题的在线课程和教程可以学习。

编辑:工作解决方案。仍然可能存在一些问题。

public ShowCasts GetAllShowAndTheirCast()
{
ShowResponse allShows = GetAllShows();
ConcurrentBag<ShowData> result = new ConcurrentBag<ShowData>();
using (var countdownEvent = new CountdownEvent(allShows.Shows.Length))
{
using (Semaphore pool = new Semaphore(20, 20))
{
for (int i = 0; i < allShows.Shows.Length; i++)
{
CreateAndStartThread(allShows.Shows[i], pool, result, countdownEvent);
}
countdownEvent.Wait();
}     
}
return new ShowCasts() { showCastList = result.ToList() };
}
public void CreateAndStartThread(Show show, Semaphore pool, ConcurrentBag<ShowData> result, CountdownEvent countdownEvent)
{
pool.WaitOne();
Thread t = new Thread(new ParameterizedThreadStart((s) =>
{
result.Add(MapResponse((Show)s));
pool.Release();
countdownEvent.Signal();
}));
t.Start(show);
}

最新更新