Чтение вывода из консольного приложения и асинхронное построение графиков WPF - PullRequest
2 голосов
/ 20 февраля 2011

У меня есть консольное приложение, которое выводит около 160 строк информации каждую 1 секунду.

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

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

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

BackgroundWorker worker = new BackgroundWorker();

worker.DoWork += delegate(object s, DoWorkEventArgs args)
{
    _process = new Process();
    _process.StartInfo.FileName = "consoleApp.exe";
    _process.StartInfo.UseShellExecute = false;
    _process.StartInfo.RedirectStandardOutput = true;
    _process.StartInfo.CreateNoWindow = true;
    _process.EnableRaisingEvents = true;
    _process.OutputDataReceived += new DataReceivedEventHandler(SortOutputHandler);
    _process.Start();
    _process.BeginOutputReadLine();
    _watch.Start();
};
worker.RunWorkerAsync();

И обработчик, который заботится о разборе и построении данных:

private void SortOutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
    if (!String.IsNullOrEmpty(outLine.Data))
    {
            var xGroup = Regex.Match(outLine.Data, "x: ?([-0-9]*)").Groups[1];
            int x = int.Parse(xGroup.Value);

            var yGroup = Regex.Match(outLine.Data, "y: ?([-0-9]*)").Groups[1];
            int y = int.Parse(yGroup.Value);

            var zGroup = Regex.Match(outLine.Data, "z: ?([-0-9]*)").Groups[1];
            int z = int.Parse(zGroup.Value);


            Reading reading = new Reading()
            {
                Time = _watch.Elapsed.TotalMilliseconds,
                X = x,
                Y = y,
                Z = z
            };


            Dispatcher.Invoke(new Action(() =>
            {
                _readings.Enqueue(reading);
                _dataPointsCount++;

            }), System.Windows.Threading.DispatcherPriority.Normal);                    
    }
}

_readings - это пользовательский ObservableQueue<Queue>, определенный в в этом ответе .Я изменил его так, чтобы в очереди одновременно могло быть только 50 элементов.Поэтому, если добавляется новый элемент и число очередей> = 50, Dequeue() вызывается перед Enqueue().

. Есть ли способ улучшить производительность или я обречен из-за того, каксколько выводит консольное приложение?

Ответы [ 4 ]

0 голосов
/ 02 марта 2011

Вы можете просто сохранить точки в списке и вызывать диспетчер, только когда вы набрали, например, 160 баллов, поэтому вы не создаете много обновлений.В настоящее время вы вызываете окно сообщения каждые 6 мс, что слишком много.Когда вы обновляете пользовательский интерфейс, например, каждую секунду или каждые 160 пунктов, все будет намного более плавно.Если уведомлений все еще слишком много, вам нужно посмотреть, как вы можете приостановить перерисовку своего элемента управления, пока вы обновляете пользовательский интерфейс с 160 точками данных и возобновляете рисование после этого, чтобы избежать сильного мерцания.

0 голосов
/ 01 марта 2011

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

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

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

  1. Используйте обычную очередь, в которой содержимое содержит 50 элементов.Не беспокойтесь о событии NotifyCollectionChanged, происходящем в потоке пользовательского интерфейса.Вам также не придется маршалировать каждый элемент в потоке пользовательского интерфейса.

  2. Предоставить объект CollectionViewSource в вашей модели представления, который принимает очередь в качестве своей коллекции.

  3. Используйте поток таймера в пользовательском интерфейсе, чтобы вручную принудительно обновить CollectionViewSource.Начните с одного раза в секунду и уменьшите интервал, чтобы увидеть, что может обрабатывать ваш XAML и компьютер.Таким образом, вы контролируете, когда CollectionView создается и уничтожается.

0 голосов
/ 01 марта 2011

Вы можете попробовать передать обработанные данные в поток пользовательского интерфейса из события BackgroundWorker ProgressChanged.

Что-то вроде ....

// Standard warnings apply: not tested, no exception handling, etc.

     var locker = new object();
     var que = new ConcurrentQueue<string>();
     var worker = new BackgroundWorker();
     var proc = new Process();

     proc.StartInfo.FileName = "consoleApp.exe";
     proc.StartInfo.UseShellExecute = false;
     proc.StartInfo.RedirectStandardOutput = true;
     proc.StartInfo.CreateNoWindow = true;
     proc.EnableRaisingEvents = true;

     proc.OutputDataReceived +=
        (p, a) =>
        {
           que.Enqueue(a.Data);
           Monitor.Pulse(locker);
        };

     worker.DoWork +=
        (s, e) =>
        {
           var watch = Stopwatch.StartNew();
           while (!e.Cancel)
           {
              while (que.Count > 0)
              {
                 string data;
                 if (que.TryDequeue(out data))
                 {
                    if (!String.IsNullOrEmpty(data))
                    {
                       var xGroup = Regex.Match(data, "x: ?([-0-9]*)").Groups[1];
                       int x = int.Parse(xGroup.Value);

                       var yGroup = Regex.Match(data, "y: ?([-0-9]*)").Groups[1];
                       int y = int.Parse(yGroup.Value);

                       var zGroup = Regex.Match(data, "z: ?([-0-9]*)").Groups[1];
                       int z = int.Parse(zGroup.Value);

                       var reading = new Reading()
                       {
                          Time = watch.Elapsed.TotalMilliseconds,
                          X = x,
                          Y = y,
                          Z = z
                       };

                       worker.ReportProgress(0, reading);
                    }
                 }
                 else break;
              }
              // wait for data or timeout and check if the worker is cancelled.
              Monitor.Wait(locker, 50);
           }
        };

     worker.ProgressChanged +=
        (s, e) =>
        {
           var reading = (Reading)e.UserState;
           // We are on the UI Thread....do something with the new reading...
        };

     // start everybody.....
     worker.RunWorkerAsync();
     proc.Start();
     proc.BeginOutputReadLine();
0 голосов
/ 25 февраля 2011

Из того, что я могу сказать, происходит следующее:

  1. IU-поток раскручивает фоновый рабочий для запуска приложения консоли.
  2. Он перенаправляет выводконсоли и обрабатывает его с помощью обработчика в потоке пользовательского интерфейса
  3. Затем обработчик в потоке пользовательского интерфейса вызывает Dispatcher.Invoke 160 раз в секунду, чтобы обновить объект очереди в том же потоке.
  4. После 50 вызовов очередь начинает блокироваться, а элементы удаляются из очереди с помощью пользовательского интерфейса

Казалось бы, проблема заключается в следующем:

Если поток пользовательского интерфейса обрабатывает необработанный вывод из консоли и очередь и обновление графика.

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

Разрешение:

  1. Запуск другого фонового потока для управления данными из консольного приложения
  2. Новый поток должен: создать очередь;обрабатывать событие OutputDataReceived;и запустите процесс консольного приложения.
  3. Обработчик событий должен , а не использовать Dispatcher.Invoke для обновления очереди.Следует использовать прямой потокобезопасный вызов.
  4. Очередь действительно должна быть неблокируемой при обновлении пользовательского интерфейса, но у меня нет достаточной информации о том, как это реализовано, чтобы комментировать.

Надеюсь, это поможет -Крис

...