Многопоточность цикла 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.