我有一个控制台应用程序,每1秒输出约160行信息。
数据输出是可用于在图表上绘图的点。
在我的WPF应用程序中,我已成功连接并且正在绘制控制台应用程序的数据输出,但是,在大约500个左右的数据点之后,我看到应用程序和UI线程锁定显着减慢
我认为这是由于我正在使用的异步操作:
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
是this answer中定义的自定义ObservableQueue<Queue>
。我已对其进行了修改,因此一次只能有50个项目在队列中。因此,如果要添加新项目并且队列计数> = 50,则会在Dequeue()
之前调用Enqueue()
。
我有什么方法可以提高性能,或者因为控制台应用程序输出多少而注定失败?
答案 0 :(得分:0)
从我可以看出的是它看起来是怎么回事:
麻烦似乎是:
让UI线程处理来自控制台的原始输出和队列和对图表的更新。
一旦UI超过50个可能导致级联故障的数据项,在enqueue和dequeue之间阻塞也存在潜在问题。 (我看不到足够的代码以确保这一点)
分辨率:
希望这会有所帮助 -Chris
答案 1 :(得分:0)
我怀疑在UI线程上发生线程饥饿问题,因为您的后台线程正在编组对可观察集合的调用,这可能会强制每次都重新创建基础CollectionView。这可能是一项相当昂贵的操作。
根据您配置XAML的方式也是一个问题。单独测量/布局变化可能会让你失望。我想,按照数据传入的速度,用户界面没有机会正确评估基础数据的发生情况。
我建议不要直接将View绑定到Queue。不要像你建议的那样使用Observable Queue,而应考虑:
使用包含50个项目内容的常规队列。不要担心UI线程上发生的NotifyCollectionChanged事件。您也不必将每个项目编组到UI线程。
在ViewModel中展示CollectionViewSource对象,该对象将Queue作为其集合。
使用UI上的计时器线程手动强制刷新CollectionViewSource。从每秒一次开始,减少间隔以查看您的XAML和机器可以处理的内容。以这种方式,您可以控制何时创建和销毁CollectionView。
答案 2 :(得分:0)
您可以尝试将处理后的数据从BackgroundWorker ProgressChanged事件传递到UI线程。
像......那样的东西。
// 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();
答案 3 :(得分:0)
您只需将这些积分存储在一个列表中,并且只有当您拥有以下内容时,才能调用调度程序。达到160分,因此您不会创建许多更新消息。目前你每6ms造成一次窗口消息太多了。更新用户界面时,例如每秒或每160点的事情会更顺畅。如果通知仍然太多,您需要看看如何在用160个数据点更新UI时暂停重绘控件,然后继续绘制,这样就不会出现大量闪烁。
List<Reading> _Readings = new List<Reading>();
DateTime _LastUpdateTime = DateTime.Now;
TimeSpan _UpdateInterval = new TimeSpan(0,0,0,0,1*1000); // Update every 1 second
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
};
// create a batch of readings until it is time to send it to the UI
// via ONE window message and not hundreds per second.
_Readings.Add(reading);
DateTime current = DateTime.Now;
if( current -_LastUpdateTime > _UpdateInterval ) // update ui every second
{
_LastUpdateTime = current;
List<Reading> copy = _Readings; // Get current buffer and make it invisible to other threads by creating a new list.
// Since this is the only thread that does write to it this is a safe operation.
_Readings = new List<Reading>(); // publish a new empty list
Dispatcher.Invoke(new Action(() =>
{
// This is called as part of a Window message in the main UI thread
// once per second now and not every 6 ms. Now we can upate the ui
// with a batch of 160 points at once.
// A further optimization would be to disable drawing events
// while we add the points to the control and enable it after
// the loop
foreach(Reading reading in copy)
{
_readings.Enqueue(reading);
_dataPointsCount++;
}
}),
System.Windows.Threading.DispatcherPriority.Normal);
}
}
}