首页 > 解决方案 > 在 Parallel.ForEach 循环中,我想增加一个 var 但 Interlock.Increment 似乎不起作用

问题描述

我有一个需要很长时间的过程,所以我想把它分解成线程。我的线程方法适用于Parallel.ForEach,只是我想通知用户到目前为止我们已经处理了多少可变数量的项目。

这是我正在做的一个示例。

namespace TestingThreading
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        private int _idCounter;
        public int IdCounter
        {
            get { return _idCounter; }
            set
            {
                if (value != _idCounter)
                {
                    _idCounter = value;
                    OnPropertyChanged("IdCounter");
                }
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string name)
        {
            var handler = System.Threading.Interlocked.CompareExchange(ref PropertyChanged, null, null);
            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(name));
            }
        }

        public MainWindow()
        {
            InitializeComponent();
            Counter.SetBinding(ContentProperty, new Binding("IdCounter"));
            DataContext = this;
            IdCounter = 0;
        }

        //random wait as a stand in for a variable length task
        private void GetWait()
        {
            Random random = new Random();
            int w = random.Next(3, 15);
            System.Threading.Thread.Sleep(100 * w);
        }

        private async void CreateClientsButton_Click(object sender, RoutedEventArgs e)
        {   
            //setup my a list of strings to iterate through 
            List<String> DeviceList = new List<string>();
            for (int i = 0; i < 150; i++)
            {
                string ThisClient = "Fox" + i;
                DeviceList.Add(ThisClient);

            }

            var myTask = Task.Run(() =>
            {
                Parallel.ForEach(DeviceList, new ParallelOptions { MaxDegreeOfParallelism = 15 }, device =>
                {
                    GetWait();
                    IdCounter++;
                    // both below give compiler errors
                    //System.Threading.Interlocked.Add(ref IdCounter, 1);
                    //var c = Interlocked.Increment(ref DeviceCounter);
                });
            });

            await myTask;

        }

    }
}

与 UI 的绑定有效,但我很确定我实际上并没有像我应该的那样多次增加变量。例如,这将运行 150 次迭代,但经过多次尝试,我从未见过我的计数器结束高于 146,这让我相信当两个线程尝试同时更新变量时存在竞争条件。

这不是世界末日,但我很想以“正确的方式”做到这一点,我的研究将我引向Interlock.Incrementor Interlock.Add,但是当我尝试使用其中任何一个来增加我的变量时,我得到了这个错误:

属性或索引器不能作为 out 或 ref 参数传递

标签: c#parallel.foreachinterlocked

解决方案


我强烈推荐IProgress<T>用于更新 UI。使用“有状态”进度(即“增量”)而不是“无状态”进度(即“第 13 项已完成”)是不寻常的,但它是可行的。

请注意,Progress<T>它负责与 UI 线程同步,因此它为您解决了竞争条件。

var progress = new Progress<int>(_ => IdCounter++) as IProgress<int>;
var myTask = Task.Run(() =>
{
  Parallel.ForEach(DeviceList, new ParallelOptions { MaxDegreeOfParallelism = 15 }, device =>
  {
    GetWait();
    progress.Report(0);
  });
});

推荐阅读