2

更新

我已经解决了这个问题。我发现我没有正确同步对折线和图表对象的访问。我没有在处理程序期间锁定它们,而是仅在使用它们的那一刻锁定它们。我认为动态数据显示库中的某些东西正在使用延迟执行并在我没想到时尝试访问其中一个对象。

TL;DR:正确同步锁。


我目前正在开发一个使用 WPF 来可视化某个类库中的算法的 GUI。我在 GUI 中使用的所有控件要么是标准 WPF,要么来自 Microsoft Dynamic Data Display库。

我遇到了非常常见的“调用线程无法访问此对象,因为另一个线程拥有它”错误。我已经进行了大量搜索,但我自己找不到解决方案。

现在,如果线程不拥有它,我对如何在图表中添加线条有点困惑。我已经尝试使用该站点上的许多其他答案所建议的调度程序,但它并没有解决这个问题。


摘要窗口旨在作为主窗口的子窗口,在模拟运行之前打开并实时显示数据。

模拟器发布了许多异步处理的事件(在内部,它使用 BeginInvoke 和 EndInvoke)。这会在这种情况下导致问题吗?

摘要视图模型注册了许多可视化,它们依次创建自己的控件并将它们添加到摘要窗口提供的画布中。

代码的一个非常简短的大纲如下

public class MainWindowModel
{
    //...
    public async void RunSimulation()
    {
        WindowService.OpenWindowFor<SimulationSummaryViewModel>(SimSummaryVM);
        await Simulator.Run();
    }
}

public class SimulatorSummaryViewModel
{
    //...
    public SimulatorSummaryViewModel()
    {
        Visualisations.Add(new RealTimeChart());
    }
    //...
}

public class RealTimeChart : IVisualisation
{
    // ...
    private ChartPlotter _Chart; //From Dynamic Data Display library

    //Handles adding information to the current line 
    private void OnSimulatorStepHandler(Args a)
    { 
      /* The exception occurs in here, when adding a data point, 
         whilst another handler is adding a line to the chart.
         I have tried adding a lock to the chart and line objects, but it had no effect
      */ 
    }

    //Handles adding the current line to the chart
    private void OnSimulatorRepetitionComplete(Args a)
    {
        //lineToAdd is an EnumerableDataSource<DataPoint>.
        //DataPoint is a simple class with two primitive properties.
        _Chart.Dispatcher.Invoke(new Action(()=>
        {
            _Chart.AddLineGraph(lineToAdd);
        }));
    }
}

public class SummaryWindow : Window
{
    // ...
    public SummaryWindow()
    {
        // ...
        foreach(IVisualisation Vis in ViewModel.Visualisations)
        {
            Vis.Draw(this.VisCanvas);
        }
    }
}
4

2 回答 2

2

我假设 OnSimulatorRepetitionComplete 是从另一个线程调用的,从而使对图表的调用发生在该线程中。

在 WPF 中,所有的 GUI 操作都需要在 Dispatcher Thread 中完成。在方法中的内容周围添加以下代码:

Dispatcher.BeginInvoke(new Action(() =>
{

}));

所以它变成了这样:

private void OnSimulatorStepHandler(Args a)
{ 
    Dispatcher.BeginInvoke(new Action(() =>
    {
        // Add your code here
    }));
}
于 2013-03-07T14:08:15.637 回答
0

我已经解决了这个问题,所以我将在这里发布我的解决方案。


由于模拟器发布的异步处理事件和可视化中隐含的同步处理(发生多个步骤,然后重复结束,然后重复),需要更好地同步访问图表和线条。以下是我的(真实)原始代码。

我正在使用的图表控件推迟使用seriesToAdd集合(引用行数据),直到添加图表。由于线数据(和图表)在处理程序的生命周期内没有被锁定,当线数据没有被锁定(由我)但在图表控件中使用时,就有机会访问线数据。

我不会发布生成的代码,因为它只是移动锁来封装完整的方法。

[STAThread]
private void OnRepetitionComplete(Evolve.Core.Simulation.RepetitionResult Result)
{
    //Convert the data series to something usable for the chart
    EnumerableDataSource<DataPoint> seriesToAdd = new EnumerableDataSource<DataPoint>(_obCurrentRepetitionDataSeries);

    //Translate the data point properties in to X&Y coords on the graph
    seriesToAdd.SetXYMapping(DP => new System.Windows.Point(DP.Step, DP.Number));

    //Get the line colour and add the line to the chart
    System.Windows.Media.Color lineColor = GetLineColor(Result.ReasonForCompletion);

    lock (chartLockObject)
    {
        lock (lineLockObject)
        {
            _obChart.Dispatcher.Invoke(new Action(() =>
            {
                _obChart.AddLineGraph(seriesToAdd, lineColor, 0.5d);
            }));

            //Renew the data series
            _obCurrentRepetitionDataSeries = new DataSeries();
        }
    }
}

private void OnStep(int StepNumber, int HealthyNodes, int MutantNodes)
{
    //Make sure there's a data series to add to
    if (_obCurrentRepetitionDataSeries == null)
    {
        _obCurrentRepetitionDataSeries = new DataSeries();
    }

    lock (lineLockObject)
    {
        //Add the step to the series
        _obCurrentRepetitionDataSeries.Add(new DataPoint() { Number = MutantNodes, Step = StepNumber });
    }
}
于 2013-03-07T14:19:48.493 回答