【问题标题】:Objects Passed to ParameterizedThreadStart being overwritten?传递给 ParameterizedThreadStart 的对象被覆盖?
【发布时间】:2012-03-30 01:45:10
【问题描述】:

背景:

我正在开发一个小型应用程序,该应用程序将通过 WMI 从事件日志中远程读取事件。基本上我正在寻找工作站何时锁定和解锁。

问题:

我创建了一个线程数组。我遍历我的数据集(计算机名称)并启动多个 带有自定义对象 (LockHunterArgs) 的 ParameterizedThreadStart 对象。问题是我知道我的数据集没有重复项。我在线程函数的末尾添加了一个 console.writeline,它显示了重复项。

另外,在我尝试使用线程之前。如果我同步运行代码,它运行良好。只是花了很长时间。所以这就是我尝试引入多线程的原因。

示例输出:

//...在上面剪掉了一些独特的行

计算机:COMP 时间:2012 年 3 月 29 日上午 8:05:11 会话:3935dd76-6a10-41a9-bd96-86143c66482d 计算机:COMP 时间:2012 年 3 月 29 日上午 8:05:11 会话:3935dd76-6a10-41a9-bd96-86143c66482d

//...在下面剪掉一些独特和重复的行

我的假设:

如果我在 get_lock_data 函数的前几行放置一个断点,它正在转换并跳到下一行。这是随机的。它会向前走一次,然后两次击中同一条线。我什至看到它向下走两行然后向后走。我认为这是因为我正在触发线程并且它在不同的时间达到了点,给人一种它正在倒退的错觉。但这几乎就像传入的对象被后面的线程覆盖一样。

我尝试创建另一个 LockHunterArgs 数组并在线程触发过程中创建和分配它们,但这也没有用。

这可能是愚蠢的。提前致谢。

// 长矛

代码:

    public class LockHunterArgs
    {
        public LockHunterArgs(string comp, DateTime limit, Guid session)
        {
            Computer = comp;
            LimitTime = limit;
            sessionID = session;
        }
        public string Computer;
        public DateTime LimitTime;
        public Guid sessionID;
    }

    public class LockHunter
    {
        private void get_lock_data(object args)
        {
            string computer = ((LockHunterArgs)args).Computer;
            DateTime limitTime = ((LockHunterArgs)args).LimitTime;
            Guid sessionID = ((LockHunterArgs)args).sessionID;

            //....SNippet ... code connects to the box and pulls data...

            Console.WriteLine("Computer: " + computer + "  Time: " + limitTime.ToString() + " Session: " + sessionID.ToString());
        }

        public void HuntLocks()
        {
            //....Snippet... code connects to database and gets a list of objects (currentSessions)
            Thread[] threadArray = new Thread[currentSessions.Count];
            int cnt = 0;
            foreach (LINQ.session sesson in currentSessions)
            {
                DateTime mostRecentTimestamp = (from q in db.actions
                                                where q.session_id == sesson.uid
                                                orderby q.timestamp descending
                                                select q.timestamp).FirstOrDefault();

                ParameterizedThreadStart start = new ParameterizedThreadStart(get_lock_data);
                threadArray[cnt] = new Thread(start);
                threadArray[cnt].Start(new LockHunterArgs(sesson.computername , mostRecentTimestamp, sesson.uid));
                cnt++;
            }

            for (int i = 0; i < threadArray.Length; i++)
            {
                threadArray[i].Join();
            }
            Console.WriteLine(DateTime.Now.ToString() + " Threads have joined");
            //....Snippet of saving the gathered data from the threads to the database
        }
    }

解决方案:

我添加了一个新课程。然后遍历我的 LINQ-to-SQL 结果以创建该新类的列表。然后,我从该列表而不是 LINQ-to-SQL 生成的列表中触发我的线程。一切都很好。谁能解释一下?

    public class TempSession
    {
        public TempSession(LINQ.session sess)
        {
            this.computername = sess.computername;
            this.timestamp = sess.start_time;
            this.uid = sess.uid;
        }
        public string computername;
        public DateTime timestamp;
        public Guid uid;
    }

    public void HuntLocks()
    {
        //select EventCode,TimeGenerated,Message from Win32_NTLogEvent WHERE logfile='Security' and (EventCode='4800' or EventCode='4801') and TimeGenerated > '20120327 08:08:08'
        // 4800 = locked
        // 4801 = unlocked

        LINQ.Login_ActionsDataContext db = new LINQ.Login_ActionsDataContext();
        List<LINQ.session> currentSessions = (from q in db.sessions
                                              where q.end_time == null
                                              orderby q.computername ascending
                                              select q).ToList();


        // START Solution Changes

        List<TempSession> newCurrentSessions = new List<TempSession>();
        foreach (LINQ.session session in currentSessions)
        {
            newCurrentSessions.Add(new TempSession(session));
        }

        Thread[] threadArray = new Thread[newCurrentSessions.Count];

        // END solution changes

        for (int i = 0; i < newCurrentSessions.Count; i++)
        {
            DateTime mostRecentTimestamp = (from q in db.actions
                                            where q.session_id == newCurrentSessions[i].uid
                                            orderby q.timestamp descending
                                            select q.timestamp).FirstOrDefault();

            ParameterizedThreadStart start = new ParameterizedThreadStart(get_lock_data);
            threadArray[i] = new Thread(start);
            threadArray[i].Start(new LockHunterArgs(newCurrentSessions[i].computername, mostRecentTimestamp, newCurrentSessions[i].uid));
        }

        for (int i = 0; i < threadArray.Length; i++)
        {
            threadArray[i].Join();
        }

        Console.WriteLine(DateTime.Now.ToString() + " Threads have joined");

        db.actions.InsertAllOnSubmit(newActions);
        Console.WriteLine(DateTime.Now.ToString() + " Found " + newActions.Count.ToString() + " locks");
        db.SubmitChanges();
        newActions = new List<LINQ.action>();

    }

【问题讨论】:

  • 既然我们知道这不是一个闭包问题,我的直觉让我认为这与在遍历集合时进行另一个 Linq To SQL 调用有关。新代码在进行另一个 linq to sql 调用时不会迭代 linq to SQL 生成的列表。它现在使用一个简单的 for 循环索引计数器。

标签: c# multithreading


【解决方案1】:

使用临时变量来存储迭代值:

foreach (LINQ.session sesson in currentSessions)
{
     var tempSession = session; // now use tempSession 
     ....

这是迭代值闭包的已知副作用。

【讨论】:

  • 刚才试过了……结果一样。
  • @LanceIngle: 所以only 引用sesson 是在它被分配给tempSession 的时候? (我看到了 3)
  • 你确定你替换了所有引用的会话吗?
  • 是的...我什至将它从 foreach 更改为 for 循环。结果相同。
  • 您是否尝试过在循环内分配Guid sessionId = sesson.uid,然后在您的Linq 语句中使用sessionId
【解决方案2】:

我会说问题很可能出在您剪掉的内容上。我无法用这些伪造的数据重现您的问题:

var guids = Enumerable.Range(1, 10)
    .Select(i => Guid.NewGuid())
    .ToArray();

var currentSessions = Enumerable.Range(1, 10)
    .Select(i => new {computername = "pc" + i})
    .Zip(guids,(a,g) => new {a.computername, uid = g});

var dbactions = Enumerable.Range(1, 10)
    .Select(i => DateTime.Now.AddHours(-1*i))
    .Zip(guids, (t,g) => new {session_id = g, timestamp = t});

鉴于此,您能否提供一个不依赖任何本地资源的工作示例?

【讨论】:

  • 我让它工作了。我不明白为什么它会起作用。我添加了一个新课程。循环遍历我的 LINQ-to-SQL 结果并创建我的新类的列表。然后我在我的 for 循环中使用新类,一切都很好。我唯一能想到的是 LINQ-to-SQL 列表不是线程安全的?
  • 您可能一直在遍历您对我们隐藏的封闭变量。您必须向我们展示该代码才能确定。
  • 我在解决方案中发布了上述所有内容(完整功能)。
猜你喜欢
  • 2021-09-10
  • 1970-01-01
  • 1970-01-01
  • 2014-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-29
  • 2013-10-10
相关资源
最近更新 更多