Ошибка многопоточности в выпуске с включенной оптимизацией кода

Я открываю новое подокно из основного при нажатии кнопки. После загрузки подокно запускает задачу с отслеживанием положения курсора мыши. При закрытии подокна сигнальной переменной stopMouseTracker присваивается значение true, и бесконечный цикл в задаче должен завершиться. Код ниже работает как в режиме отладки, так и в режиме выпуска без оптимизации кода. Но когда я запускаю свой код в Release с включенной оптимизацией кода, подокно зависает после того, как я пытаюсь его закрыть. Что может вызвать проблему?

Главное окно:

public MainWindow()
{
    InitializeComponent();
}

private void button_Click(object sender, RoutedEventArgs e)
{
    SubWindow subWindow = new();

    subWindow.ShowDialog();
}

Подокно:

bool mouseTrackerOn = false;
bool stopMouseTracker = false;

public SubWindow()
{
    InitializeComponent();

    StartMouseTracker();
}

private void StartMouseTracker()
{
    mouseTrackerOn = true;

    Task.Factory.StartNew(() =>
    {
        while (!stopMouseTracker)
        {
            var p = System.Windows.Forms.Cursor.Position;
            
            // Write cursor coordinates to text box tbXY
            tbXY.Dispatcher.BeginInvoke(() => tbXY.Text = $"{p.X} {p.Y}");

            Thread.Sleep(1);
        }

        mouseTrackerOn = false;
    });
}

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    stopMouseTracker = true;

    // This blocks the thread completely in Release with code optimization on
    while (mouseTrackerOn) ;
}

попробуйте сделать переменные bool «изменчивыми»

JeffRSon 01.07.2024 15:36

@Perotto: Более глубокая причина заключается в том, что переменная была встроена в регистр, где другой поток никогда больше не получит доступ к этой ячейке памяти, что приводит к наблюдаемому вами бесконечному циклу.

Alois Kraus 01.07.2024 16:14
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
54
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

У вас есть незаблокированная переменная, которую вы проверяете из другого потока. Хотя volatile или Interlocked.Read и т. д. исправят это, вам все равно не нужна вся эта возня с задачами и Dispatcher.

Просто используйте CancellationToken, чтобы прервать цикл, и поместите цикл в поток пользовательского интерфейса, используя async.

private CancellationTokenSource _stopMouseTracker = new();

public SubWindow()
{
    InitializeComponent();
    StartMouseTracker(_stopMouseTracker.Token);
}

private async Task StartMouseTracker(CancellationToken cancellationToken)
{
    try
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            var p = System.Windows.Forms.Cursor.Position;
            // Write cursor coordinates to text box tbXY
            tbXY.Text = $"{p.X} {p.Y}";
            await Task.Delay(1, cancellationToken);
        }
    }
    catch
    { //
    }
}

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    stopMouseTracker.Cancel();
}

Я бы предпочел использовать System.Windows.Threading.DispatcherTimer или System.Threading.PeriodicTimer, ни один из которых не требует CancellationTokenSource и проглатывания исключений.

Theodor Zoulias 01.07.2024 16:43

Я вас слышу, думаю, единственное исключение, которого вы ожидаете, это OperationCanceled. Кнопка Ответить, кстати, находится внизу.

Charlieface 01.07.2024 16:53

Charlieface Я не склонен отвечать на этот вопрос. Вероятно, существует полдюжины дубликатов, связанных с эффектом пропуска volatile в логическом флаге, и мне сейчас лень их искать.

Theodor Zoulias 01.07.2024 17:05

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