Почему мой l oop не выходит в Release, но выходит в Debug? - PullRequest
1 голос
/ 09 июля 2020

Итак, у меня есть программа, которая выполняет множество асинхронных команд (веб-вызовы и т. Д. c) и функций, которые она вызывает параллельно. Чтобы не дать Main l oop сойти с рельсов и завершить работу до того, как все будет сделано, я создал переменную класса static int die, которая подсчитывает количество выполняемых функций. Я полагаю, упрощенная / наивная организация очереди задач. Перед вызовом функции I die++; и в конце каждой функции die--;, при этом это находится в конце моей функции Main ():

             while (die > 0) { }
            Console.WriteLine("Writing Log File and Exiting");
} //End of Main()

И он отлично работает при запуске в Отлаживать. Когда я запускаю его в Release, моя программа выполняется, но затем «зависает». Приостановка выполнения в Visual Studio говорит мне, что программа сидит в моем операторе while. Окно Immediate сообщает мне, что die имеет значение 0, а die > 0 ложно.

Почему мой l oop не завершается?

Обновление 1:

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

using Microsoft.Extensions.Configuration;
using System;
using System.IO;
using System.Net.Http;

namespace SOExample
{

    static class Program
    {
        static IConfigurationRoot appsettings;
        static int die;
        static HttpClient client;

        static void Main()
        {
            appsettings = new ConfigurationBuilder().SetBasePath(Directory.GetParent(AppContext.BaseDirectory).FullName).AddJsonFile("appsettings.json", true).Build();
            client = new HttpClient();
            die = 1;
            Function1();
            while (die > 0) { }
        }

        static async void Function1()
        {
            await client.GetAsync("http://google.com"); //Just to clear the async error;
            //Do Func1 Stuff
            if (appsettings.GetValue<bool>("doFunc2"))
            {
                die++;
                Function2();
            }
            die--;
        }

        static async void Function2()
        {
            //Do Other Function Stuff
            await client.GetAsync("http://google.com"); //Just to clear the async error;
            die--;
        }
    }
}

Следуя приведенному ниже ответу от Basi c ниже, Я заменил все модификаторы d ie на выражения Interlock, чтобы сделать их atomi c, но это не нарушило условия. Я пробовал несколько решений, которые кажутся работающими; Thread.Sleep(1000); внутри тела l oop и изменение условия while на !Equals(die,0) (предположительно, чтобы заблокировать значение и сравнить его атомарно), которые оба кажутся работающими, хотя я явно необходимо переосмыслить дизайн в целом.

1 Ответ

4 голосов
/ 09 июля 2020

die++ и die-- не являются потокобезопасными.

Внутренне это многоступенчатые операции ... чтение, увеличение / уменьшение, запись.

Если выполняются два потока одна и та же операция в одно и то же время, они, вероятно, будут неправильно считать.

Скажем, d ie находится на 5. Потоки 1 и 2 оба пытаются выполнить die-- одновременно.

  • Поток 1 читает die как 5
  • Поток 2 читает die как 5
  • Обновления потока 1 die до 4
  • Обновления потока 2 die до 4 (снова)

Classi c состояние гонки.

Это гораздо менее вероятно, что это произойдет в режиме DEBUG (но не невозможно), поскольку там больше проверок потоков и больше накладных расходов в целом, а это означает, что у вас гораздо меньше шансов, что два потока будут выполнять одну и ту же команду одновременно.

Вы следует использовать Interlocked.Increment(ref die) и Interlocked.Decrement(ref die)

Они будут добавлять / вычитать 1 поточно-ориентированным способом.

Другая потенциальная проблема ...

Ваш die переменная не объявлена ​​volatile.

Это определенно будет различие со сборками DEBUG

Очень упрощенно, это означает, что переменная может быть изменена несколькими потоками, и поэтому каждый из них должен быть повторно получить «реальное» значение, а не полагаться на локальный кеш.

Это все поэтому ограничивает оптимизацию, которая может быть добавлена ​​компилятором (в однопоточной работе компилятор может переупорядочивать операторы для оптимизации эффективности процессора, если они не влияют на результат. Это может быть намного сложнее угадать в многопоточных приложениях, и компилятор часто ошибается).

Это немного сложнее, чем это, но в результате вам нужно его использовать

https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/volatile

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...