需要帮助调整c#多线程例程吗

需要帮助调整c#多线程例程吗,c#,multithreading,C#,Multithreading,我创建了一个windows服务,它使用Parallel.ForEach在一台有24核、48虚拟机的机器上运行多线程例程。该服务在生产环境中运行良好,它将数据批量复制到SQL Server数据库中。目前它做得很好,大约每秒6000次插入,但我相信它可以调整。下面是我正在使用的代码的一部分;这里有一个关于当前功能和调整建议更改的示例。从代码中可以看出,当前每个要添加的调用都有一个锁,我认为这使得是并行的。所以我在寻找一个“修复”;希望我的新方法,也在代码中定义,能起到作用 public c

我创建了一个windows服务,它使用
Parallel.ForEach
在一台有24核、48虚拟机的机器上运行多线程例程。该服务在生产环境中运行良好,它将数据批量复制到SQL Server数据库中。目前它做得很好,大约每秒6000次插入,但我相信它可以调整。下面是我正在使用的代码的一部分;这里有一个关于当前功能和调整建议更改的示例。从代码中可以看出,当前每个要添加的调用都有一个锁,我认为这使得
是并行的。所以我在寻找一个“修复”;希望我的新方法,也在代码中定义,能起到作用


    public class MainLoop
    {
        public void DoWork()
        {
            var options = new ParallelOptions
            {
                MaxDegreeOfParallelism = System.Environment.ProcessorCount * 2
            };

            var workQueueManager = new ObjWorkQueueManager(queueSize: 1000);

            // ignore the fact that this while loop would be a never ending loop,
            // there's other logic not shown here that exits the loop!
            while (true)
            {
                ICollection<object> work = GetWork();

                Parallel.ForEach(work, options, (item) =>
                {
                    workQueueManager.AddOLD(item);
                });
            }
        }

        private ICollection<object> GetWork()
        {
            // return list of work from some arbitrary source
            throw new NotImplementedException();
        }
    }

    public class ObjWorkQueueManager
    {
        private readonly int _queueSize;
        private ObjDataReader _queueDataHandler;
        private readonly object _sync;

        public ObjWorkQueueManager(int queueSize) 
        {
            _queueSize = queueSize;
            _queueDataHandler = new ObjDataReader(queueSize);
            _sync = new object();
        }

        // current Add method works great, but blocks with EVERY call
        public void AddOLD(object value)
        {
            lock (_sync)
            {
                if (_queueDataHandler.Add(value) == _queueSize)
                {
                    // create a new thread to handle copying the queued data to repository
                    Thread t = new Thread(SaveQueuedData);
                    t.Start(_queueDataHandler);

                    // start a new queue 
                    _queueDataHandler = new ObjDataReader(_queueSize);
                }
            }
        }

        // hoping for a new Add method to work better by blocking only 
        // every nth call where n = _queueSize
        public void AddNEW(object value)
        {
            int queued;
            if ((queued = _queueDataHandler.Add(value)) >= _queueSize)
            {
                lock (_sync)
                {
                    if (queued == _queueSize)
                    {
                        Thread t = new Thread(SaveQueuedData);
                        t.Start(_queueDataHandler);
                    }
                }
            }
            else if (queued == 0)
            {
                lock (_sync)
                {
                    _queueDataHandler = new ObjDataReader(_queueSize);

                    AddNEW(value);
                }
            }
        }

        // this method will Bulk Copy data into an SQL DB
        private void SaveQueuedData(object o)
        {
            // do something with o as ObjDataReader
        }
    }

    // implements IDataReader, Read method of IDataReader dequeues from _innerQueue
    public class ObjDataReader
    {
        private readonly int _capacity;
        private Queue<object> _innerQueue;

        public ObjDataReader(int capacity)
        {
            _capacity = capacity;
            _innerQueue = new Queue<object>(capacity);
        }

        public int Add(object value)
        {
            if (_innerQueue.Count < _capacity)
            {
                _innerQueue.Enqueue(value);
                return _innerQueue.Count;
            }

            return 0;
        }
    }


公共类主循环
{
公共工作
{
var options=新的并行选项
{
MaxDegreeOfParallelism=System.Environment.ProcessorCount*2
};
var workQueueManager=new ObjWorkQueueManager(queueSize:1000);
//忽略这个while循环将是一个永无止境的循环的事实,
//这里没有显示退出循环的其他逻辑!
while(true)
{
i收集工作=获取工作();
Parallel.ForEach(工作、选项、(项目)=>
{
workQueueManager.AddOLD(项);
});
}
}
私有ICollection GetWork()
{
//从任意源返回工作列表
抛出新的NotImplementedException();
}
}
公共类ObjWorkQueueManager
{
私有只读int_queueSize;
私有ObjDataReader _queueDataHandler;
私有只读对象_sync;
公共ObjWorkQueueManager(int queueSize)
{
_队列大小=队列大小;
_queueDataHandler=新的ObjDataReader(queueSize);
_sync=新对象();
}
//当前的Add方法工作得很好,但每次调用都会阻塞
公共void AddOLD(对象值)
{
锁定(同步)
{
if(_queueDataHandler.Add(value)==\u queueSize)
{
//创建一个新线程来处理将排队数据复制到存储库的操作
线程t=新线程(SaveQueuedData);
t、 启动(_queueDataHandler);
//启动新队列
_queueDataHandler=新的ObjDataReader(_queueSize);
}
}
}
//希望有一种新的Add方法能够通过阻塞更好地工作
//每n次调用,其中n=\u队列大小
public void AddNEW(对象值)
{
int排队;
if((排队=_queueDataHandler.Add(value))>=_queueSize)
{
锁定(同步)
{
如果(排队==\u队列大小)
{
线程t=新线程(SaveQueuedData);
t、 启动(_queueDataHandler);
}
}
}
else if(排队==0)
{
锁定(同步)
{
_queueDataHandler=新的ObjDataReader(_queueSize);
新增(价值);
}
}
}
//此方法将数据大容量复制到SQL DB中
私有void SaveQueuedData(对象o)
{
//使用o作为ObjDataReader执行某些操作
}
}
//实现IDataReader,从_innerQueue中读取IDataReader的方法
公共类ObjDataReader
{
专用只读int_容量;
专用队列innerQueue;
公共ObjDataReader(内部容量)
{
_容量=容量;
_innerQueue=新队列(容量);
}
公共整数加法(对象值)
{
如果(_innerQueue.Count<_容量)
{
_innerQueue.Enqueue(值);
返回_innerQueue.Count;
}
返回0;
}
}

您使用什么建立SQL连接?如果使用EF Core,您将能够完全避免该锁定,只需执行大容量复制。您需要确保的是每个请求都有自己的DbContext。但我假设任何其他建立SQL连接的方法也可以,只要不同任务之间不共享相同的连接;除非未显示的其他代码正在使用新插入的PKs进行进一步的工作,否则我看不到任何可能的争用。我很好奇,在滚动自己的任务池之前,您是否首先尝试使用简单的异步任务,如果是,那么这些任务对您不起作用呢?基于您的注释引用了IDataReader这一事实,我假设您使用的是SqlBulkCopy。我很想知道你的用例是什么。您是否希望GetWork返回非常少量的您尝试批处理的工作,或者GetWork返回非常大量的您尝试分解的工作(每次调用)?或者两者都有?如果实现两个线程的效率不高,我会感到惊讶——一个用于生产者(调用GetWork,并填充到ConcurrentQueue),第二个用于列表中的TryDequeue,直到达到批处理大小或返回false,然后在同一线程上调用批量插入。如果TryDequeue返回false,并且您还没有复制任何内容,那么只需进入睡眠状态—大约100ms。我建议这样做,因为实际上Sql Server通常(但不总是……硬件、分区和工作负载相关)处理多个大容量复制的速度不会明显快于单线程处理。