Компилируем код из кода для воспроизведения гонки двух процессов

    Класс CSharpCodeProvider позволяет программе на C# компилировать код на C#. Обычный вопрос – «зачем». Обычные ответы:
    1. исполнение кода, данного пользователями, как на ideone.com,
    2. «ну мало ли зачем» и
    3. «а это уже отдельный вопрос»

    Сегодня мы используем этот класс для удобного воспроизведения гонки двух процессов.

    Есть следующий код, который здесь приводится только для иллюстрации сценария использования:

    Process process = …;
    process.Run();
    //blahblahblah
    if (!process.WaitForExit(sometime))
    {
        process.Kill();
    }
    
    и оказывается, что в случае, если процесс завершится после возврата из WaitForExit(), но до начала содержательной работы Kill(), последний выбрасывает InvalidOperationException с сообщением «никак нельзя, процесс уже завершился».

    Да, действительно иногда повторяется. Это типичная гонка.

    Возникает желание сообщить об этом как об ошибке (при этом будем считать, что «правильное» поведение Kill() в таком случае – ничего не делать). КРАЙНЕ НЕОЖИДАННО Естественно, у нас попросят код для воспроизведения этой ситуации. Нам нужно сделать два процесса, которые бы надежно воспроизводили именно такое развитие гонки, которое приводит к «неправильному» поведению.

    Первый (ведущий) процесс будет выполнять такой код:
    var process = new Process();
    process.StartInfo.FileName = secondExeName;
    process.Start();
    if (!process.WaitForExit(900))
    {
         System.Threading.Thread.Sleep(500);
         process.Kill();
    }
    

    второй (ведомый) – такой код:
    System.Threading.Thread.Sleep( 1000 );
    

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

    Осталось только прикрутить монитор сделать из этого такой пример, который было бы удобно запускать.

    Очевидный способ:
    1. сделать solution с двумя проектами типа Console Application,
    2. указать порядок сборки,
    3. выбрать проект ведущего процесса в качестве стартового,
    4. прописать относительный путь к исполняемому файлу ведомого процесса,…

    TL;DR;, все равно у того, кто откроет solution, будет не та версия Visual Studio, так что либо после конвертации потеряется одна из настроек, либо проект просто не откроется, так как «требует более новой версии». Даже если версия та же, бессмысленное упражнение по открыванию архива и распаковке его в очередную папку не добавит радости всем тем, кто будет этот код запускать (будьте уверены, только для разбора и оценки одного сообщения об ошибке один и тот же код приходится открыть и запустить многим людям). Наконец, крайне цинично требовать скачивать и открывать архив, чтобы просто прочитать несколько десятков строк кода.

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

    using System;
    using System.Linq;
    using Microsoft.CSharp;
    using System.CodeDom.Compiler;
    using System.Diagnostics;
    
    namespace ConsoleApplicationNPlusOne
    {
        class Program
        {
            static void Main(string[] args)
            {
                var secondExecutableName = "GuidedProcess.png";
                compileGuidedExecutable(secondExecutableName);
    
                using (var guidedProcess = new Process())
                {
                    guidedProcess.StartInfo.FileName = secondExecutableName;
                    guidedProcess.StartInfo.UseShellExecute = false;
                    guidedProcess.Start();
                    if (!guidedProcess.WaitForExit(900))
                    {
                        System.Threading.Thread.Sleep(500);
                        guidedProcess.Kill();
                    }
                }
            }
    
            static void compileGuidedExecutable(string filePath)
            {
                using (var compiler = new CSharpCodeProvider())
                {
                    var parameters = new CompilerParameters(null, filePath, true);
                    parameters.GenerateExecutable = true;
                    var compilationResult = compiler.CompileAssemblyFromSource(
                        parameters, guidedProcessCode);
                    var compilationErrors = compilationResult.Errors;
                    if (compilationErrors.HasErrors)
                    {
                        var firstError = compilationErrors.Cast<CompilerError>().First();
                        throw new InvalidOperationException(String.Format(
                            "Compilation failed. Line {0}: {1}", firstError.Line, firstError.ErrorText));
                    }
                }
            }
    
            static readonly String guidedProcessCode =
                @"class Program {
                    public static void Main(string[] args)
                    {
                        System.Threading.Thread.Sleep( 1000 );
                    }
                }";
        }
    }
    


    Код обоих процессов аккуратно сложен вместе, его можно бездумно скопировать в только что созданный пустой проект Console Application и нажать F5 в Visual Studio, а можно попробовать скомпилировать и выполнить в уме.

    Это был еще один пример использования CSharpCodeProvider и компиляции кода из кода.

    Дмитрий Мещеряков,
    департамент продуктов для разработчиков
    ABBYY
    113.20
    Решения для интеллектуальной обработки информации
    Share post

    Comments 11

      +4
      Доводы «надо сделать компиляцию» несостоятельны (усложнили пример лишним мусором и т.д.). А «компиляцию» сделать как написано в MSDN. К чему нам тут перепечатывать его?
      код
       static void Main(string[] args)
              {
                  if (args.Length == 0)
                  {
                      var process = new Process();
                      process.StartInfo.FileName = Assembly.GetExecutingAssembly().Location;
                      process.StartInfo.Arguments = "second";
                      process.Start();
                      if (!process.WaitForExit(900))
                      {
                          System.Threading.Thread.Sleep(500);
                          process.Kill();
                      }
                  }
                  else if (args[0]=="second")
                  {
                      System.Threading.Thread.Sleep(1000);
                  }
              }
      

        +13
        Извращение какое-то. Описываемая проблема «двух проектов» решается вот так:
        public static void Main (string[] args)
        {
        	if (args.Length != 0 && args[0] == "second")
        		Thread.Sleep(1000);
        	else
        	{
        		var proc = Process.Start(typeof (Program).Assembly.GetModules()[0].FullyQualifiedName, "second");
        		proc.WaitForExit(500);
        		Thread.Sleep(1000);
        		proc.Kill();
        	}
        }
        
          0
          Мне кажется или всё-таки jonie тебя опередил?
          А вообще, мне больше интересно, что Microsoft ответит, если им прислать такой баг-репорт.
            +1
            мне больше интересно, что Microsoft ответит, если им прислать такой баг-репорт
            Вопрос о реакции по существу или о реакции на такой способ организации кода для повторения?
              +1
              По существу. А именно, что ответит, в какие сроки, отписка это будет или что-то ещё.
                +2
                Перевели запрос в состояние «Won't Fix», что примерно означает «да, проблема есть, но пока мы не готовы ее решать».
                  –1
                  Не знаю, что они там имели в виду, но ваша проблема не является багой .NET Framework, т.к. поведение соответствует заявленному. Исправлять, скорее всего, не будут. Ловите исключение или делайте по-другому.
                    –1
                    Я, кстати, уже предвижу, что у вас в коде (если вы это где-то используеете) есть ещё одна бага: Process.Kill не ожидает завершения процесса. После него нужно снова вызывать WaitForExit
                      0
                      или делайте по-другому
                      Как еще можно подождать завершения процесса, а потом его убить, чтобы при этом не было гонки?
                        –2
                        Понятия не имею. С чего бы мне искать для вас альтернативное решение вашей задачи вместо уже имеющегося? Вам не нравится ловить исключение — вы и ищите альтернативы.
              –5
              Сейчас самое время убрать топик в черновики.

            Only users with full accounts can post comments. Log in, please.