Класс CSharpCodeProvider позволяет программе на C# компилировать код на C#. Обычный вопрос – «зачем». Обычные ответы:
- исполнение кода, данного пользователями, как на ideone.com,
- «ну мало ли зачем» и
- «а это уже отдельный вопрос»
Сегодня мы используем этот класс для удобного воспроизведения гонки двух процессов.
Есть следующий код, который здесь приводится только для иллюстрации сценария использования:
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 );
Значения констант подобраны таким образом, чтобы гонка развивалась всегда нужным образом и приводила к выбросу исключения.
Осталось только прикрутить монитор сделать из этого такой пример, который было бы удобно запускать.
Очевидный способ:
- сделать solution с двумя проектами типа Console Application,
- указать порядок сборки,
- выбрать проект ведущего процесса в качестве стартового,
- прописать относительный путь к исполняемому файлу ведомого процесса,…
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 и компиляции кода из кода.
Дмитрий Мещеряков,
департамент продуктов для разработчиков
Автор: DmitryMe