C# 这个线安全吗?断点会被多次命中

C# 这个线安全吗?断点会被多次命中,c#,multithreading,C#,Multithreading,我有以下代码: public class EmailJobQueue { private EmailJobQueue() { } private static readonly object JobsLocker = new object(); private static readonly Queue<EmailJob> Jobs = new Queue<EmailJob>(); private static rea

我有以下代码:

public class EmailJobQueue
{
    private EmailJobQueue()
    {

    }

    private static readonly object JobsLocker = new object();
    private static readonly Queue<EmailJob> Jobs = new Queue<EmailJob>();

    private static readonly object ErroredIdsLocker = new object();
    private static readonly List<long> ErroredIds = new List<long>();

    public static EmailJob GetNextJob()
    {
        lock (JobsLocker)
        {
            lock (ErroredIdsLocker)
            {
                // If there are no jobs or they have all errored then get some new ones - if jobs have previously been skipped then this will re get them
                if (!Jobs.Any() || Jobs.All(j => ErroredIds.Contains(j.Id)))
                {
                    var db = new DBDataContext();
                    foreach (var emailJob in db.Emailing_SelectSend(1))
                    {
                        // Dont re add jobs that exist
                        if (Jobs.All(j => j.Id != emailJob.Id) && !ErroredIds.Contains(emailJob.Id))
                        {
                            Jobs.Enqueue(new EmailJob(emailJob));
                        }
                    }
                }

                while (Jobs.Any())
                {
                    var curJob = Jobs.Dequeue();
                    // Check the job has not previously errored - if they all have then eventually we will exit the loop
                    if (!ErroredIds.Contains(curJob.Id))
                        return curJob;
                }
                return null;
            }
        }
    }

    public static void ReInsertErrored(long id)
    {
        lock (ErroredIdsLocker)
        {
            ErroredIds.Add(id);
        }
    }
}
问题是,如果我在注释所在的位置放置一个断点,并向队列中添加一项,那么断点会被多次命中。这是我的代码的一个问题还是VS调试器的一个特性

谢谢


Joe

这是调试应用程序多线程部分的副作用

您将看到在每个线程上命中断点。调试应用程序的多线程部分很棘手,因为实际上您同时调试所有线程。事实上,有时,它会在您单步执行时在类之间跳转,因为它在所有这些线程上执行不同的操作,具体取决于您的应用程序

现在,解决它是否是线程安全的。这实际上取决于您如何使用这些线程上的资源。如果你只是在阅读,它很可能是线程安全的。但是如果您正在编写,您至少需要利用共享对象上的
锁定操作:

lock (someLockObject)
{
    // perform the write operation
}

似乎您正在从数据库中获取作业:

foreach (var emailJob in db.Emailing_SelectSend(1))
那个数据库调用是否在将来的查询中将记录标记为不可用?如果没有,我相信这就是为什么你多次达到临界点的原因

例如,如果我将对数据库的调用替换为以下内容,我将看到您的行为

// MockDB is a static configured as `MockDB.Enqueue(new EmailJob{Id = 1})`

private static IEnumerable<EmailJob> GetJobFromDB()
{
    return new List<EmailJob>{MockDB.Peek()};
}
//MockDB是一个静态文件,配置为`MockDB.Enqueue(new-EmailJob{Id=1})`
私有静态IEnumerable GetJobFromDB()
{
返回新列表{MockDB.Peek()};
}
然而,如果我真的从模拟数据库中退出队列,它只会命中断点一次

private static IEnumerable<EmailJob> GetJobFromDB()
{
    var list = new List<EmailJob>();

    if (MockDB.Any())
        list.Add(MockDB.Dequeue());

    return list;
}
私有静态IEnumerable GetJobFromDB()
{
var list=新列表();
if(MockDB.Any())
添加(MockDB.Dequeue());
退货清单;
}

对我来说,这听起来更像是一种竞赛条件。如果
EmailJobQueue.GetNextJob()
成功,则对它的下一次调用应为null。只有一个线程应该得到这个任务。但是肯定有一个线程应该得到它,然后满足if条件,命中断点,然后调试器启动了吗?@RobertHarvey,在一个线程上,是的,我明白你的意思。但是,除非我误解了,否则他会启动10个线程来做同样的事情,并且他的断点在
if
中。感觉它不止一次被命中,但实际上它是在不同的线程上被命中的。但是它不应该在不同的线程上被命中,因为它会退出队列并且只应该返回一次-除非上面的类不是线程安全的。你知道吗:
Jobs.All(j=>ErrorIds.Contains(j.Id))
正在根据每个错误id检查每个作业,如果您的列表变大,这将是一个严重的性能问题?您可能想要一种改进过滤的方法。
db.Emailing\u SelectSend(1)
做什么?想想看,这看起来像一个标准的生产者/消费者应用程序。您应该考虑重写它,以便有一个线程填充A,并且您的工作线程从该线程中读取。这样做会大大简化程序。如果这就是代码的范围,我看不到两个锁对象的好处。一个就足够了。作为旁注,由于类的所有方法和成员都是
静态的
,因此您应该将类本身标记为
静态的
,并放弃
私有的
构造函数。啊,您是对的。我确实从数据库中删除了该项,但我是在设置了断点之后才这样做的,这样我就可以两次获得相同的项。这也可能发生在生产环境中。非常感谢你发现了这一点,我没有。我发现这一点的唯一原因是因为我试图运行你的代码,但我没有DB,所以我只是中断了呼叫。这几乎进入了生产环境-如果我能两次投票给你,我会的。
private static IEnumerable<EmailJob> GetJobFromDB()
{
    var list = new List<EmailJob>();

    if (MockDB.Any())
        list.Add(MockDB.Dequeue());

    return list;
}