2

以下代码无需多线程即可正常工作。但是如果我使用线程,它会失败。如果我在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

4 回答 4

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) ));     
    ....
}

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

于 2013-04-16T23:26:08.790 回答
2

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

这意味着当新线程被调度到 CPU 上时,循环实际上已经向前迭代到一个未知状态。这就是为什么您的值看起来是随机的。

这是一步一步发生的事情:

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

解决方案是在线程创建期间传递值,以便将它们本地复制到线程:

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);
}

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

于 2013-04-16T22:49:49.557 回答
1

theThreads[i].Start()不会立即运行新线程,同时 IP 变量可能会发生变化。

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

string IP = item.ToString();
于 2013-04-16T22:57:22.317 回答
0

这里是另一个版本的多项式代码。这次是线程池

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);
}
于 2013-04-16T23:33:03.477 回答