线程和功能参数

时间:2013-04-16 22:40:25

标签: c# multithreading

以下代码在没有多线程的情况下工作正常。但如果我使用线程,它就会失败。如果我在checkedListBox中选择了多个项目,则第一个将被忽略而其他项目是随机的......

我认为提交数据存在问题。你觉得怎么样?

    private void sendCom(String com)
    {
        //send command to selected item
        int i=0;
        String IP;
        foreach (var item in checkedListBox1.CheckedItems)
        {
            Console.WriteLine(item.ToString());
            IP = item.ToString();
            theThreads[i] = new Thread(new ThreadStart(() => sendComThread(IP, com) ));
            theThreads[i].Start();
            //sendCom(IP, com);
            i++;
        }
    }

    private void sendComThread(String IP, String com)
    {
        // send an command
        System.Console.WriteLine(IP + com);
    }

4 个答案:

答案 0 :(得分:4)

基本问题是您的变量捕获捕获单个变量,然后在所有线程之间共享。因此,每次线程读取共享变量时,它都会获得最近放在那里的任何值。除了语义错误之外,共享变量还有明确的数据竞争。

最简单的解决方案是为每个线程创建一个变量。只需在循环中移动变量的声明即可。像这样:

foreach (var item in checkedListBox1.CheckedItems)
{
    ....          
    String IP = item.ToString(); //NB variable declared inside loop    
    theThreads[i] = new Thread(new ThreadStart(() => sendComThread(IP, com) ));     
    ....
}

现在每个线程都有自己的字符串变量的私有实例。

答案 1 :(得分:2)

这里的问题是你的线程正在新线程中从lambda表达式读取循环的状态,而不是将实际值传递给线程。

这意味着在新线程在CPU上进行调度时,循环实际上已经迭代到未知状态。这就是您的值显示为随机的原因。

以下是一步一步发生的事情:

  1. 创建() => sendComThread(IP, com) lambda,引用这两个参数。
  2. theThreads[i].Start();被调用,但保证该线程中的代码将立即运行。在系统上的线程调度程序将上下文切换到不同的线程之前,当前代码可能会持续一段时间。
  3. 发生下一个循环迭代并执行IP = item.ToString();,更改IP的值。这可能不止一次发生。
  4. 上下文切换发生在处理器上,另一个线程被执行,或者另一个线程在另一个处理器(核心)上执行,从lambda表达式中读取对IP的引用。
  5. 这会导致跨线程读取,这意味着IP的状态未定义。
  6. 解决方案是在创建线程期间传递值,以便将它们本地复制到线程:

    struct SendComThreadParams
    {
        public string IP;
        public string Com;
    
        public SendComThreadParams(string ip, string com)
        {
            this.IP = ip;
            this.Com = com;
        }
    }
    
    private void sendCom(String com)
    {
        //send command to selected item
        int i=0;
        String IP;
        foreach (var item in checkedListBox1.CheckedItems)
        {
            Console.WriteLine(item.ToString());
            IP = item.ToString();
            theThreads[i] = new Thread(new ParameterizedThreadStart(sendComThread));
            theThreads[i].Start(new SendComThreadParams(IP, com));
            i++;
        }
    }
    
    private void sendComThread(object threadParam)
    {
        var p = (SendComThreadParams)threadParam;
        // send an command
        System.Console.WriteLine(p.IP + p.Com);
    }
    

    这会将参数正确复制到线程,以确保它们的值处于已定义的状态。

答案 2 :(得分:1)

theThreads[i].Start()将不会立即运行新线程,并且IP变量可能会同时更改。

在for循环中定义IP变量将解决问题:

string IP = item.ToString();

答案 3 :(得分:0)

这是Polynomials代码的另一个版本。这次使用threadpools

struct SendComThreadParams
{
public string IP;
public string Com;

public SendComThreadParams(string ip, string com)
{
    this.IP = ip;
    this.Com = com;
}
}

private void sendCom(String com)
{
//send command to selected item
int i=0;
String IP;
foreach (var item in checkedListBox1.CheckedItems)
{
    Console.WriteLine(item.ToString());
    IP = item.ToString();
    ThreadPool.QueueUserWorkItem(new WaitCallback(sendComThread), (object)new SendComThreadParams(IP, com));
    i++;
}
}

private void sendComThread(object threadParam)
{
var p = (SendComThreadParams)threadParam;
// send an command
System.Console.WriteLine(p.IP + p.Com);
}