在foreach循环中打开线程

时间:2012-01-15 20:46:13

标签: c# multithreading foreach

我正在获取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个线程运行它时,我收到错误“对象引用未设置为对象的实例”。

我猜我的锁定有问题,因为我对线程不是很有经验。

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

干杯!

3 个答案:

答案 0 :(得分:4)

我注意到你正在运行一堆线程,其整个代码都包含在lock语句中。你也可以用这种方式按顺序运行方法,因为你没有得到任何并行性。

答案 1 :(得分:3)

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

您锁定的对象当前为static,因此它不是实例绑定的,这意味着,一旦一个线程进入您的ParseXML方法,其他任何人都无法执行任何操作,直到第一个完成了。

您没有在线程中的Parser类中共享任何数据(来自我能看到的代码),因此您不需要在ParseXML函数内部锁定。

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

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

创建线程是一项昂贵的操作,需要调用OS内核,因此您不希望这样做500次。这太昂贵了。此外,Windows中线程堆栈的最小保留内存为1MB,因此对于您的线程,仅为堆栈空间500MB。

最佳线程数应该等于机器中的核心数,但是因为大多数情况下这不是真的,你可以做两倍或三倍,但是你最好使用一个线程池,在那里你回收线程,而不是始终创建新线程。

答案 2 :(得分:2)

尽管这可能无法解决您的问题,但您应该使用ThreadPool,而不是创建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区块,是否有效?