В цикле Parallel.ForEach я хочу увеличить переменную, но Interlock.Increment, похоже, не работает - PullRequest
0 голосов
/ 02 мая 2019

У меня есть процесс, который занимает много времени, поэтому я хочу разбить его на потоки.Мой подход с многопоточностью прекрасно работает с 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;

        }

    }
}

Привязка к интерфейсу работает, но я уверен, что на самом деле я не увеличиваю переменную столько раз, сколькоЯ должен быть.Например, это будет проходить 150 итераций, но после нескольких попыток я никогда не видел, чтобы мой счетчик был выше 146, что позволяет предположить, что существует состояние гонки, когда два потока пытаются обновить переменную одновременно.

Это не конец света, но я бы хотел сделать это «правильным путем», и мои исследования приводят меня к Interlock.Increment или Interlock.Add, но когда я пытаюсь увеличить свою переменнуюс любым из них я получаю эту ошибку:

Свойство или индексатор не могут быть переданы как параметр out или ref

1 Ответ

1 голос
/ 02 мая 2019

Я настоятельно рекомендую использовать IProgress<T> для обновления пользовательского интерфейса.Необычно использовать «прогресс с состоянием» (то есть «приращение») вместо «прогресса без состояния» (т. Е. «Элемент 13 завершен»), но это выполнимо.

Обратите внимание, что Progress<T> заботится о синхронизации с потоком пользовательского интерфейса, поэтому он решает за вас условия гонки.

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);
  });
});
...