c#中奇怪的线程

本文关键字:线程 | 更新日期: 2023-09-27 18:13:53

我遇到了一个奇怪的c#线程问题。

这是我的示例程序,使用线程在agentList中的每个代理上"激活"Print()函数。

class Program {
    static void Main(string[] args) {
        List<Agent> agentList = new List<Agent>();
        agentList.Add(new Agent("lion"));
        agentList.Add(new Agent("cat"));
        agentList.Add(new Agent("dog"));
        agentList.Add(new Agent("bird"));
        foreach (var agent in agentList) {
            new Thread(() => agent.Print()).Start();
        }
        Console.ReadLine();
    }
}
class Agent {
    public string Name { get; set; }
    public Agent(string name) {
        this.Name = name;
    }
    public void Print() {
        Console.WriteLine("Agent {0} is called", this.Name);
    }
}

下面是我运行上面程序的结果:

Agent cat is called
Agent dog is called
Agent bird is called
Agent bird is called

但我期望的是包含所有4个代理的东西,如

Agent lion is called
Agent cat is called
Agent dog is called
Agent bird is called

最神奇的是,如果我在foreach之外调用线程,它会工作!

class Program {
    static void Main(string[] args) {
        List<Agent> agentList = new List<Agent>();
        agentList.Add(new Agent("leecom"));
        agentList.Add(new Agent("huanlv"));
        agentList.Add(new Agent("peter"));
        agentList.Add(new Agent("steve"));
        new Thread(() => agentList[0].Print()).Start();
        new Thread(() => agentList[1].Print()).Start();
        new Thread(() => agentList[2].Print()).Start();
        new Thread(() => agentList[3].Print()).Start();

        Console.ReadLine();
    }
}

上面代码的结果正是我所期望的。那么问题是什么呢?

c#中奇怪的线程

这里有一个闭包。你在foreach循环中关闭一个变量。发生的事情是变量在线程开始之前被覆盖,所以你有两个具有相同值的迭代。

简单的修复方法是在使用它之前捕获foreach循环中的值:

foreach(var a in agentList)
{
    var agent = a;
    new Thread(() => agent.Print()).Start();
}

您正在闭包中捕获agent,这可能会导致多线程问题。先赋值给一个局部变量:

    foreach (var agent in agentList) {
        var temp = agent;
        new Thread(() => temp.Print()).Start();
    }

像这样修改foreach循环:

foreach (var agent in agentList) 
{
    var agent1 = agent;
    new Thread(() => agent1.Print()).Start();         
} 

将值复制到局部变量(这看起来有点愚蠢)避免了使用可能在运行时发生变化的变量的线程。

这是因为var代理可以在线程中有相同的引用。

如果不使用某种线程同步(如ManualResetEvent),就不能保证线程被处理的顺序。如果你想按顺序执行多个步骤,我建议你把这些工作捆绑起来,然后在一个后台线程中执行所有这些工作。

我喜欢BackgroundWorker对象:

List<Agent> agentList = new List<Agent>();
agentList.Add(new Agent("leecom"));
agentList.Add(new Agent("huanlv"));
agentList.Add(new Agent("peter"));
agentList.Add(new Agent("steve"));
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (sender, e) =>
{
    foreach (var item in agentList)
    {
        item.Print();
    }
};
worker.RunWorkerAsync();