#c# #wpf #multithreading #process #external
#c# #wpf #многопоточность #процесс #внешний
Вопрос:
Мне нужно запустить несколько экземпляров внешнего исполняемого файла из моего приложения. Среднее время выполнения этого исполняемого файла составляет около 3 минут. Я хочу перенаправить вывод из этих процессов и обновить индикатор выполнения в моем графическом интерфейсе. Конечно, я не хочу ждать, пока они вернутся, прежде чем я смогу продолжить использовать свое приложение.
Я думаю, мне следует создать поток для каждого экземпляра и обновлять индикатор выполнения, когда поток завершается.
Правильный ли это подход?
Кроме того, вы рекомендуете хороший ресурс / документацию, чтобы понять, как это работает? Я нашел http://www.dotnetperls.com/threadpool только.
редактировать: эти процессы основаны на сети, т. Е. Время выполнения может сильно различаться в зависимости от задержки канала / пропускной способности.
Что касается индикатора выполнения, я хотел бы обновлять его каждый раз, когда процесс завершается. Есть ли обработчик для этого? Позже я добавлю более подробное обновление, основанное на выходных данных процессов, чтобы увеличить прогресс, достигнутый на каждом этапе выполнения.
правка 2 :
Спасибо за ваш вклад. Поскольку мне, возможно, придется запускать много процессов (до 20), и я не хочу перегружать полосу пропускания, я буду запускать 5 параллельно максимум. Каждый раз, когда процесс завершается, я увеличиваю счетчик выполнения (для моего индикатора выполнения) и запускаю другой, пока все они не будут завершены, используя :
Process p = new Process();
p.StartInfo.FileName = pathToApp;
p.EnableRaisingEvents = true;
p.Exited = OnCalibrationProcessExited;
p.Start();
private void OnCalibrationProcessExited(object sender, EventArgs e)
{
runAnotherOne function
}
Это правильно или есть более элегантный способ добиться этого?
Конечно, я не хочу, чтобы мое приложение блокировалось во время выполнения.
Лучше ли для этого использовать фоновых работников?
Комментарии:
1. Я добавил еще несколько комментариев к своему ответу на основе вашей правки
2. Если вы используете .Net 4, вы могли бы использовать библиотеку Task Parallel и не беспокоиться об управлении процессами самостоятельно: msdn.microsoft.com/en-us/library/dd537609.aspx
Ответ №1:
Вы должны использовать Process
и ProcessStartInfo
. Вам нужно будет установить ProcessStartInfo.UseShellExecute
на false
, ErrorDialog
на false
, RedirectStandardOutput
на true
(и, возможно RedirectStandardError
, тоже).
Вам также потребуется предоставить делегат объекту Process для обработки выходных данных, сгенерированных внешним процессом через OutputDataReceived
(и, возможно ErrorDataReceived
, также).
Вы также можете установить Exited
делегат, который будет вызываться при завершении процесса.
Пример:
ProcessStartInfo processInfo = new ProcessStartInfo("Write500Lines.exe");
processInfo.ErrorDialog = false;
processInfo.UseShellExecute = false;
processInfo.RedirectStandardOutput = true;
processInfo.RedirectStandardError = true;
Process proc = Process.Start(processInfo);
proc.ErrorDataReceived = (sender, errorLine) => { if (errorLine.Data != null) Trace.WriteLine(errorLine.Data); };
proc.OutputDataReceived = (sender, outputLine) => { if (outputLine.Data != null) Trace.WriteLine(outputLine.Data); };
proc.BeginErrorReadLine();
proc.BeginOutputReadLine();
proc.WaitForExit();
Комментарии:
1. Вы избавили меня от лишней траты времени.
Ответ №2:
Простое ожидание завершения каждого потока перед обновлением индикатора выполнения приводит к тому, что ничего не происходит… затем быстрый переход .. 3 раза. Вы также можете пропустить индикатор выполнения.
Правильным способом сделать это, ИМХО, было бы вычислить общую работу, проделанную во всех трех процессах:
общая работа = time1 time2 time3
Теперь, если у вас несколько процессоров, это займет больше времени, чем max (time1, time2, time3), но это нормально. Это представление работы.
Иметь общую переменную для выполняемой работы. Каждый раз, когда процесс выполняет еще какую-то работу, обновляйте индикатор выполнения, вычисляя выполненную работу = my-work-increment. Прогресс — это просто выполненная работа / totalwork.
Это даст хорошие результаты независимо от того, выполняются потоки последовательно или параллельно. Поскольку вы не знаете, как все будет выполняться (возможно, у вас однопроцессорный процессор), это наилучший подход.
Ответ №3:
Просто создайте несколько экземпляров класса Process, вызвав конструктор, задайте свойства для перенаправления выходного потока, а затем запустите их.
Ваша программа не будет ждать завершения вызванного процесса, пока вы не вызовете метод WaitForExit. Многопоточность не требуется.
Ответ №4:
Создайте единый поток.
В этом потоке (псевдокод):
Thread begins here
for each externalApp
Run the application with redirect output
Wait for exit
Update progress bar
end for
Thread ends here
Смотрите http://msdn.microsoft.com/en-us/library/ty0d8k56.aspx для ожидания выхода
Или… вы хотите запускать внешние приложения параллельно?
Редактировать: на основе последних обновлений в первоначальном сообщении:
Если вы не знаете фактический прогресс, тогда не используйте обычный индикатор выполнения. Как насчет бесконечного индикатора выполнения или значка «работает»?
Бесконечный индикатор выполнения может быть индикатором выполнения, который заполняется и запускается с самого начала, пока все не будет сделано. Рабочий значок похож на курсор занятости Windows (постоянно вращающийся круг).
Комментарии:
1. Привет, Виктор, действительно, это кажется лучшим подходом. Я отредактировал свое сообщение, поскольку оно было неясным. Но в вашем представлении псевдокода, если я буду ждать завершения, процессы не будут выполняться параллельно, поэтому я не буду использовать WaitForExit, как предложил @Shyc2001.
Ответ №5:
Как насчет создания наблюдаемой коллекции TaskProgressInfo,
где TaskProgressInfo — это пользовательский класс, в который вы записываете свой прогресс.
Привяжите WPF listview к этой коллекции, используя datatemplate (с целевым типом = TaskProgressInfo), чтобы отобразить панель прогресса для каждого элемента (задачи).
Создайте массив фоновых программ, которые запускают внешнее приложение и отслеживают его.
Каждый фоновый рабочий должен обновить свой TaskProgressInfo, обновляя таким образом источник данных progressbar.
По завершении каждый фоновый разработчик должен удалить свой TaskProgressInfo из ObservableCollection, таким образом, удалив индикатор выполнения из пользовательского интерфейса.
Поскольку BackgroundWorker использует фоновый поток (UI) для выполнения отчета и завершения, изменения в ObservableCollection будут выполнены его потоком создания (потокобезопасный).
За кулисами .NET будет использовать пул потоков — некоторые фоновые разработчики будут совместно использовать потоки.