Parallel.ForEach竞争条件

时间:2015-01-26 22:01:32

标签: c# parallel.foreach

我定期进入我认为是parallel.ForEach循环中的竞争条件。我这样说是因为它总是挂在代码的那一部分上。

try
{
    Parallel.ForEach(Directory.EnumerateFiles(directory, "*.tracex", SearchOption.TopDirectoryOnly), _po, (path, ls) =>
        {
            DebugFile file;
            if (filterDate)
            {
                if (filterUser)
                {
                    file = new DebugFile(path, startTime, endTime, user);
                }
                else file = new DebugFile(path, startTime, endTime);
            }
            else if (filterUser)
            {
                file = new DebugFile(path, user);
            }
            else file = new DebugFile(path);
            if (!file.IsFiltered()) 
            {
                _files.Add(file);
            }
            Interlocked.Increment(ref _loadCount);  // increment how many we've checked
            if (_po.CancellationToken.IsCancellationRequested)
            {
                ls.Break();
            }
        });
}
catch (OperationCanceledException oce)
{
    Debug.WriteLine(oce.ToString());
}

在我的_files对象中,我在调用Add方法时处理锁定。

public virtual void Add(T item)
{
    _lock.EnterWriteLock();
    try
    {
        _bindingList.Add(item);
    }
    finally
    {
        _lock.ExitWriteLock();
    }
    OnListChanged(new ListChangedEventArgs(ListChangedType.ItemAdded, _bindingList.Count - 1));
}

任何想法我在这里做错了什么?它不会每次都挂起,只是间歇性地。此外,至少对我而言,第一次调用代码时不会发生这种情况。只有当我打电话一次,然后再次打电话,通常是第二次或第三次时,它才会发生。

谢谢!

更新 我意识到我正在使用自定义任务调度程序。当我删除它时,我不再看到挂起。我这样做了所以我可以自定义我运行的线程数。我的想法是,由于我主要通过网络读取文件,因此IO会减慢速度,因此我可以立即运行更多任务。以下是我构建调度程序的方法:

public class TaskSchedulerForSlowIO : TaskScheduler
{
    /// <summary>
    /// maximum number of tasks to run concurrently
    /// </summary>
    private int _maxConcurrencyLevel;

    /// <summary>
    /// lock for reading tasks array
    /// </summary>
    private ReaderWriterLockSlim _listLock = new ReaderWriterLockSlim();

    /// <summary>
    /// list of tasks running
    /// </summary>
    private LinkedList<Task> _tasks = new LinkedList<Task>();

    /// <summary>
    /// Default constructor - This will increase threadpool limits if necessary
    /// </summary>
    public TaskSchedulerForSlowIO()
        : base()
    {
        _maxConcurrencyLevel = Environment.ProcessorCount * 10;
        int workerThreads, ioThreads, minimumConcurrency;
        minimumConcurrency = Environment.ProcessorCount * 2;
        ThreadPool.GetMaxThreads(out workerThreads, out ioThreads);
        if (workerThreads < _maxConcurrencyLevel)
        {
            if (ioThreads < _maxConcurrencyLevel)
            {
                ioThreads = _maxConcurrencyLevel;
            }
            ThreadPool.SetMaxThreads(_maxConcurrencyLevel, ioThreads);
        }
        ThreadPool.GetMinThreads(out workerThreads, out ioThreads);
        if (workerThreads < minimumConcurrency)
        {
            if (ioThreads < minimumConcurrency)
            {
                ioThreads = minimumConcurrency;
            }
            ThreadPool.SetMinThreads(minimumConcurrency, ioThreads);
        }
    }

    /// <summary>
    /// Implementing TaskScheduler
    /// </summary>
    public override int MaximumConcurrencyLevel
    {
        get
        {
            return _maxConcurrencyLevel;
        }
    }

    /// <summary>
    /// Scheduler Implementation
    /// </summary>
    /// <returns>ScheduledTasks</returns>
    protected override IEnumerable<Task> GetScheduledTasks()
    {
        Task[] tasks;
        _listLock.EnterReadLock();
        try
        {
            tasks = _tasks.ToArray();
        }
        finally
        {
            _listLock.ExitReadLock();
        }
        return tasks;
    }

    /// <summary>
    /// Queues the specified task
    /// </summary>
    /// <param name="task">Task to queue</param>
    protected override void QueueTask(Task task)
    {
        int count;
        _listLock.EnterReadLock();
        try
        {
            _tasks.AddLast(task);
            count = _tasks.Count;
        }
        finally
        {
            _listLock.ExitReadLock();
        }
        if (count <= _maxConcurrencyLevel)
        {
            ThreadPool.UnsafeQueueUserWorkItem(ProcessTask, task);
        }
    }

    /// <summary>
    /// Scheduler Implementation
    /// </summary>
    /// <param name="task">Task to remove</param>
    /// <returns>Success</returns>
    protected override bool TryDequeue(Task task)
    {
        _listLock.EnterWriteLock();
        try
        {
            return _tasks.Remove(task);
        }
        finally
        {
            _listLock.ExitWriteLock();
        }
    }

    /// <summary>
    /// Scheduled Implementation
    /// </summary>
    /// <param name="task">Task to execute</param>
    /// <param name="taskWasPreviouslyQueued">Was the task previously queued</param>
    /// <returns></returns>
    protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
    {
        //We're not going to inline slow IO
        return false;
    }

    void ProcessTask(object o)
    {
        try
        {
            Task t = o as Task;
            if (t != null)
            {
                if (base.TryExecuteTask(t))
                {
                    if(!(t.IsCanceled || t.IsFaulted)) t.Wait();
                    TryDequeue(t);
                }
            }
        }
        catch(AggregateException a)
        {
            var e = a.Flatten();
            foreach (Exception ex in e.InnerExceptions)
            {
                Debug.WriteLine(ex.ToString());
            }
        }
    }
}

1 个答案:

答案 0 :(得分:-1)

可能有很多原因。 例如

1)起源,因此filterUserfilterDateIsFiltered()的变异特征......在代码上看不清楚,这可能会导致问题。

2)通常,代码是不可扩展的。避免并行访问(读取)文件,因为IO设备(在我的情况下我假设的硬盘)不是并行读取设备,并且在简单的串行处理的情况下,您很可能会获得更差的性能。

Suggession:将线程亲和性设置为仅2个线程/核心(我再次假设你有更多),并调试,看看会发生什么。很可能你会达到冲突加剧的程度。