首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >ThreadPool种族-条件,关闭,锁定或其他什么?

ThreadPool种族-条件,关闭,锁定或其他什么?
EN

Stack Overflow用户
提问于 2022-10-29 15:57:16
回答 2查看 40关注 0票数 0

线程仍然很新,所以我确信这是一个小问题和一个重复的问题,但我一直无法找到浏览线程的答案。

我在C#中有一个端口扫描应用程序。我使用线程池为每个端口旋转一个新的TcpClient,并探测它是否打开。在经历了闭包和线程同步的概念之后,当多个线程试图将其结果保存到Orchestrator.hosts (List)中的不同索引时,我遇到了一个问题。

我有多个线程试图更新单个列表结果对象。我的理解是,只要我在写时锁定对象,这是很好的,但是我发现在一些更新中,多个条目得到了相同的更新。IE,Thread #1应该将Hosts[0].Ports[0].Status更新为"Open",会发生什么:线程#1使用端口结果更新多个主机,尽管传递了主机的特定索引。Hosts[0].Ports[0].Status改为"Open",Hosts[1].Ports[0].Status改为"Open",Hosts[2].Ports[0].Status改为"Open",

不知道我的问题在哪里。我调用的静态方法来执行给定端口的探测

代码语言:javascript
复制
    public static void ScanTCPPorts()
    {
        // Create a list of portsToScan objects to send to thread workers
        //List<ScanPortRequest> portsToScan = new List<ScanPortRequest>();
    
        using (ManualResetEvent resetEvent = new ManualResetEvent(false))
        {
            int toProcess = 0;
            for (var i = 0; i < hostCount; i++) // Starting at Begining
            {
                int currentHostId = i;
                // To hold our current hosts ID (Assign outside of threaded function to avoid race-condition)
                if (hosts[i].IsAlive || scanDefinition.isForced())
                {
                    int portCount = hosts[i].Ports.Count;
                    for (int p = 0; p < portCount; p++)
                    {
                        // Thread-safe Increment our workQueue counter
                        Interlocked.Increment(ref toProcess);
    
                        int currentPortPosition = p;
    
                        // We need to send the arrayIndex in to the thread function
                        PortScanRequestResponse portRequestResponse = new PortScanRequestResponse(hosts[currentHostId], currentHostId, hosts[currentHostId].Ports[currentPortPosition], currentPortPosition);
    
                        ThreadPool.QueueUserWorkItem(
                            new WaitCallback(threadedRequestResponseInstance => {
                                PortScanRequestResponse portToScan = threadedRequestResponseInstance as PortScanRequestResponse;
                                PortScanRequestResponse threadResult = PortScanner.scanTCPPort(portToScan);
                                // Lock so Thread-safe update to result
                                lock (Orchestrator.hosts[portToScan.hostResultIndex])
                                {
                                    if (threadResult.port.status == PortStatus.Open)
                                    {
                                        // Update result
Orchestrator.hosts[portToScan.hostResultIndex].Ports[portToScan.portResultIndex].status = PortStatus.Open;
                                        //Logger.Log(hosts[currentHostId].IPAddress + " " + hosts[currentHostId].Ports[currentPortPosition].type + " " + hosts[currentHostId].Ports[currentPortPosition].portNumber + " is open");
                                    }
                                    else
                                    {
                                        Orchestrator.hosts[portToScan.hostResultIndex].Ports[portToScan.portResultIndex].status = PortStatus.Closed;
                                    }
                                    // Check if this was the last scan for the given host
                                    if (Orchestrator.hosts[portToScan.hostResultIndex].PortScanComplete != true)
                                    {
                                        if (Orchestrator.hosts[portToScan.hostResultIndex].isCompleted())
                                        {
                                            Orchestrator.hosts[portToScan.hostResultIndex].PortScanComplete = true;
                                            // Logger.Log(hosts[currentHostId].IPAddress + " has completed a port scan");
                                            Orchestrator.hosts[portToScan.hostResultIndex].PrintPortSummery();
                                        }
                                    }
                                }
                                // Safely decrement the counter
                                if (Interlocked.Decrement(ref toProcess) == 0)
                                    resetEvent.Set();
                            }), portRequestResponse);   // Pass in our Port to scan
                    }
                }
            }
            resetEvent.WaitOne();
        }
    }

下面是单独的公共静态类中的辅助进程。

代码语言:javascript
复制
    public static PortScanRequestResponse scanTCPPort(object portScanRequest) {
        PortScanRequestResponse portScanResponse = portScanRequest as PortScanRequestResponse;
        HostDefinition host = portScanResponse.host;
        ScanPort port = portScanResponse.port;
        try
        {
            using (TcpClient threadedClient = new TcpClient())
            {
                try
                {
                    IAsyncResult result = threadedClient.BeginConnect(host.IPAddress, port.portNumber, null, null);
                    Boolean success = result.AsyncWaitHandle.WaitOne(Orchestrator.scanDefinition.GetPortTimeout(), false);
    
                    if (threadedClient.Client != null)
                    {
                        if (success)
                        {
                            threadedClient.EndConnect(result);
                            threadedClient.Close();
                            portScanResponse.port.status = PortStatus.Open;
                            return portScanResponse;
                        }
                    }
                } catch { }
            }
        }
        catch
        { }
        portScanResponse.port.status = PortStatus.Closed;
        return portScanResponse;
    }

最初,我从一个空闲变量中提取主机索引,认为这是问题所在,将其移到委托内部。我试着在有写的地方锁定主机对象。我尝试过不同的线程同步技术(CountdownEventManualResetEvent)。

我想我还没有被介绍过一些基本的线程处理原理,或者我犯了一个非常简单的逻辑错误。

EN

回答 2

Stack Overflow用户

发布于 2022-10-29 21:16:26

我有多个线程试图更新单个列表结果对象。我的理解是,只要我在写时锁定对象,就可以了。

我还没有研究过您的代码,但仅上述说法是不正确的。在多线程环境中使用List<T>或任何其他非线程安全对象时,必须同步与对象的所有交互。一次只允许一个线程与对象交互。写入和读取都必须包含在lock语句中,使用相同的锁存对象。即使读取Count也必须是同步的。否则,使用是错误的,并且程序的行为是未定义的。

票数 0
EN

Stack Overflow用户

发布于 2022-10-30 20:51:29

我非常关注这是一个线程问题,因为这是我的第一线程项目。事实证明,我没有意识到List<>对象的副本是对原始对象(引用类型)的引用。我假设我的线程正在以一种不可预测的方式访问我的保存结构,但是我的端口数组都引用了同一个对象。

这是端口的List<>上的“引用类型”与“值类型”问题。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/74246727

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档