打开foreach循环中的线程

本文关键字:线程 循环 foreach 打开 | 更新日期: 2023-09-27 18:20:17

我得到一个XML提要,并在我的MQ服务器上解析它,然后我有一个服务可以侦听MQ服务器并读取它的所有消息。

我有一个foreach循环,每次迭代都会打开一个新线程,以便更快地进行解析,因为MQ中大约有500条消息(意味着有500个XML)

foreach (System.Messaging.Message m in msgs)
{
    byte[] bytes = new byte[m.BodyStream.Length];
    m.BodyStream.Read(bytes, 0, (int)m.BodyStream.Length);
    System.Text.ASCIIEncoding ascii = new System.Text.ASCIIEncoding();
    ParserClass tst = new ParserClass(ascii.GetString(bytes, 0, (int)m.BodyStream.Length));
    new Thread( new ThreadStart(tst.ProcessXML)).Start();
}

在ParserClass中,我有这样的代码:

private static object thLockMe = new object();
public string xmlString { get; set; }
public ParserClass(string xmlStringObj)
{
    this.xmlString = xmlStringObj;
}
public void ProcessXML()
{
    lock (thLockMe)
    {
        XDocument reader = XDocument.Parse(xmlString);
        //Some more code...
    }
}

问题是,当我只用1个线程运行这个foreach循环时,它运行得很完美,但速度很慢。

当我用超过1个线程运行它时,我会得到一个错误"Object reference not set to a instance of a Object"。

我想我的锁有问题,因为我在线程方面不是很有经验。

我有点绝望,希望你能帮忙!

干杯!

打开foreach循环中的线程

我注意到您正在运行一堆线程,它们的整个代码都封装在lock语句中。您还不如以这种方式按顺序运行这些方法,因为您没有获得任何并行性。

由于在循环的每次迭代中都要创建一个新的ParserClass实例,并且在每次迭代中也要创建和启动一个新线程,因此ParseXML方法中不需要锁。

锁定的对象当前是static,因此它没有实例绑定,这意味着,一旦一个线程在ParseXML方法中,在第一个线程完成之前,其他线程将无法执行任何操作。

您没有在线程之间共享Parser类中的任何数据(从我可以看到的代码来看),因此您不需要ParseXML函数中的锁。

如果您使用的是线程之间共享的数据,那么您应该有一个锁。

如果你要使用很多线程,那么你最好使用ThreadPool,从你的池中提取一个有限的(可能是4个),给它们分配一些工作,并将它们回收用于接下来的4个任务。

创建线程是一项昂贵的操作,需要调用操作系统内核,所以您不想做500次。这太贵了。此外,在Windows中,线程堆栈的最小保留内存为1MB,因此仅在堆栈空间中,线程就有500MB的保留内存。

线程的最佳数量应该等于机器中的核心数量,然而,由于这在大多数情况下都不是真实的,你可以将其增加一倍或三倍,但最好使用线程池,在那里你回收线程,而不是一直创建新线程。

尽管这可能无法解决您的问题,但您不应该同时创建500个线程,而应该使用ThreadPool,它以更高效的方式管理线程:

foreach (System.Messaging.Message m in msgs)
{
    byte[] bytes = new byte[m.BodyStream.Length];
    m.BodyStream.Read(bytes, 0, (int)m.BodyStream.Length);
    System.Text.ASCIIEncoding ascii = new System.Text.ASCIIEncoding();
    ParserClass tst = new ParserClass(ascii.GetString(bytes, 0, (int)m.BodyStream.Length));
    ThreadPool.QueueUserWorkItem(x => tst.ProcessXML());
}

为了确保它们尽可能同时运行,请像这样更改ParserClass中的代码(假设您确实有在线程之间共享的资源——如果没有,则根本不必锁定):

private static object thLockMe = new object();
public string XmlString { get; set; }
public ParserClass(string xmlString)
{
    XmlString = xmlString;
}
public void ProcessXML()
{
    XDocument reader = XDocument.Parse(xmlString);
    // some more code which doesn't need to access the shared resource
    lock (thLockMe)
    {
        // the necessary code to access the shared resource (and only that)
    }
    // more code
}

关于您的实际问题:

与其用相同的参数多次调用OddService.InsertEvent(...)(该方法充满了远程调用和副作用…),不如只调用一次,将结果存储在一个变量中,然后对该变量执行所有后续操作。这样,您还可以方便地检查它是否不是那种有时返回null的精确方法(当同时访问时?)。

编辑:

如果你把所有对OddService.*的调用都放在lock块中,它能工作吗?