Многопоточность цикла For проходит верхнюю границу

Сегодня мы учимся многопоточности в классе, и мы столкнулись с очень любопытной ошибкой. При выполнении цикла for в нашем новом потоке верхняя граница цикла for продолжает проходить. Поток уничтожается, но затем появляется другое значение и завершает другой поток.

В целях отладки ошибки я изменил верхнюю границу на 90, чтобы избежать исключения OutOfRange на индикаторе выполнения.

При выводе счетчика на индикатор выполнения и обновлении индикатора выполнения я получил это в окне вывода.

при обновлении индикатора выполнения

Если я прокомментировал обновление на индикаторе выполнения (pbLoad.Value = i;) Я получил это в моем окне вывода

не обновляя индикатор выполнения

Я попытался изменить цикл на i<101 а также пытался двигаться туда, где i++ было, но это не имело никакого значения

РЕДАКТИРОВАТЬ: Это исходит от BeginInvoke, Когда я переключил его на Invoke это сработало, но тогда я получу тупик при попытке использовать кнопку отмены.

Вот код:

public partial class Form1 : Form
    {
        Thread backgroundThread;
        bool stopExecution = false;

        public Form1()
        {
            InitializeComponent();
        }

        private void btnStart_Click(object sender, EventArgs e)
        {
            stopExecution = false;
            btnStart.Enabled = false;
            backgroundThread = new Thread(DoDomethingThatTakesAWhile);
            backgroundThread.Start();
        }

        private void DoDomethingThatTakesAWhile()
        {
            for (int i = 0; i <= 100; i++)
            {
                if (!stopExecution)
                {
                    Thread.Sleep(100);

                    if (pbLoad.InvokeRequired)
                    {
                        MethodInvoker myMethod
                            = new MethodInvoker(
                                delegate
                                {
                                    if (!stopExecution)
                                    {
                                        pbLoad.Value = i;
                                        Debug.WriteLine(i); //i to output window                                        
                                    }
                                });
                        pbLoad.BeginInvoke(myMethod);
                    }
                    else
                    {
                        pbLoad.Value = i;
                    }
                }
                else
                {
                    break;
                }
            }
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {
            //backgroundThread.Abort();
            stopExecution = true;
            backgroundThread.Join();

            pbLoad.Value = 0;
            btnStart.Enabled = true;
        }
    }

2 ответа

Решение

Когда вы звоните MethodInvoke это не произойдет в этот момент, но через некоторое время.

В вашем сценарии у вас есть шанс следовать:

  • вызванный код наконец выполняется;
  • цикл уже закончен (и i стать 101)
  • вы получаете доступ i прямо и вы прочитали 101.

И чтобы исправить это вы можете сделать копию i (передавая его в качестве параметра вызываемому методу):

pbLoad.BeginInvoke(new Action<int>(a =>
{
    if (!stopExecution)
    {
        pbLoad.Value = a;
        Debug.WriteLine(a); //a to output window                                        
    }
}), new object[] { i });

PS: вам не нужно проверять InvokeRequired, если вы не планируете звонить DoDomethingThatTakesAWhile метод напрямую, что я предполагаю, не тот случай.

Вы используете BeginInvoke что явно открывает возможность для гонок. Я рекомендую синхронный вызов.

Кроме того, вы захватываете i, а не его ценность. Это очень странно и работает только случайно, потому что ты спишь.

Любое из изменений решит проблему. Сделайте оба из них.

Если можете, отмените низкоуровневое использование синхронизации и используйте async/await.

Другие вопросы по тегам