【问题标题】:WPF DataGrid Multithreading CrashWPF DataGrid 多线程崩溃
【发布时间】:2012-07-27 17:36:50
【问题描述】:

我有一个带有DataGrid 的视图。 一个 ViewModel 为DataContext,我可以在其中访问背景对象中的DataTable。 背景对象必须与DataTable 一起使用并保持更新。 还允许用户更改 DataTable

如果我创建DataTable 的副本,它会停止崩溃,但用户显然没有处理数据。

如果我为用户保留访问权限,则程序不可避免地崩溃了。

这是一个会崩溃的小程序:

app.cs

public partial class App : Application
{
    public App()
    {
        SomeBackgroundThing background = new SomeBackgroundThing();
        MainWindowViewModel viewmodel = new MainWindowViewModel(background);
        MainWindowView view = new MainWindowView(viewmodel);
        view.Show();
    }
}

主要的xml

<Window x:Class="NullPointerDataGrid.MainWindowView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <DataGrid Name="datagrid" ItemsSource="{Binding Path=table, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"/>
    </Grid>
</Window>

和程序代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Data;
using System.Diagnostics;
using System.Timers;
using System.ComponentModel;

namespace NullPointerDataGrid
{
    public partial class MainWindowView : Window
    {
        public MainWindowView(MainWindowViewModel model)
        {
            DataContext = model;
            InitializeComponent();
            datagrid.Loaded += new RoutedEventHandler(ScrollToBottom);
            datagrid.AutoGeneratedColumns += new EventHandler(StarSizeLastRow);

        }

    void ScrollToBottom(object sender, RoutedEventArgs e)
    {
        Debug.WriteLine("TableGrid_ScrollToBottom");
        if (datagrid.Items.Count > 0)
        {
            var border = VisualTreeHelper.GetChild(datagrid, 0) as Decorator;
            if (border != null)
            {
                var scroll = border.Child as ScrollViewer;
                if (scroll != null) scroll.ScrollToEnd();
            }
        }

    }

    void StarSizeLastRow(object sender, EventArgs e)
    {
        Debug.WriteLine("TableGrid_StarSizeLastColumn");
        try
        {
            datagrid.Columns[datagrid.Columns.Count - 1].Width = new DataGridLength(1, DataGridLengthUnitType.Star);
        }
        catch { }
    }

    }

public class MainWindowViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private SomeBackgroundThing thing;
    public DataTable table
    {
        get
        {
            lock (thing.table)
            {
                //DataTable wpfcopy = thing.table.Copy();
                return thing.table;
            };
        }
        set
        {
            Debug.Write("This never happens");
        }
    }

    public MainWindowViewModel(SomeBackgroundThing thing)
    {
        this.thing = thing;
        thing.Changed += new EventHandler(thing_Changed);
    }

    void thing_Changed(object sender, EventArgs e)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs("table"));
        }
    }
}

public class SomeBackgroundThing : IDisposable
{
    public DataTable table;
    private DataTable tablecopy;
    private System.Timers.Timer timer, slowrowchanger;

    public event EventHandler Changed = new EventHandler((o, e) => { ;});
    protected void CallChanged(object sender, EventArgs e)
    {
        Changed(sender, e);
    }

    public SomeBackgroundThing()
    {
        CreateTable();
        UpdateB(this, null);
        tablecopy = table.Copy();
        InitAndStartTimer(1);
    }

    #region timer

    private void UpdateA()
    {
        Boolean haschanged = false;
        DataTable newcopy = table.Copy(); ;
        if (newcopy.Rows.Count != tablecopy.Rows.Count)
        {
            Debug.WriteLine("Different ammount of rows");
            haschanged = true;
        }
        else if (newcopy.Columns.Count != tablecopy.Columns.Count)
        {
            Debug.WriteLine("Different ammount of columns");
            haschanged = true;
        }
        else
        {
            for (int i = 0; i < newcopy.Rows.Count; i++)
            {
                for (int j = 0; j < newcopy.Columns.Count; j++)
                {
                    if (newcopy.Rows[i][j].ToString() != tablecopy.Rows[i][j].ToString())
                    {
                        Debug.WriteLine(String.Format(
                            "Element [{0}/{1}]: {2} is different from {3}",
                            i, j, newcopy.Rows[i][j], tablecopy.Rows[i][j]
                            ));
                        haschanged = true;
                    }
                    if (haschanged) break;
                }
                if (haschanged) break;
            }
        }
        if (haschanged)
        {
            tablecopy = newcopy;
        }
    }

    private void InitAndStartTimer(int interval)
    {
        timer = new System.Timers.Timer();
        timer.Interval = interval;
        timer.AutoReset = true;
        timer.Elapsed += new ElapsedEventHandler((s, e) =>
        {
            UpdateA();
        });
        timer.Enabled = true;

        slowrowchanger = new System.Timers.Timer();
        slowrowchanger.Interval = 3000;
        slowrowchanger.AutoReset = true;
        slowrowchanger.Elapsed += new ElapsedEventHandler((s, e) =>
        {
            UpdateB(null, null);
        });
        slowrowchanger.Enabled = true;

    }

    public void Dispose()
    {
        timer.Enabled = false;
        slowrowchanger.Enabled = false;
        timer.Dispose();
        slowrowchanger.Dispose();
    }

    #endregion

    #region editlastrow

    void UpdateB(object sender, EventArgs e)
    {
        Random rnd = new Random();
        List<String> cells = new List<string>{
                "The SAME", 
                rnd.Next(0,100).ToString(), 
                rnd.ToString(), 
                rnd.NextDouble().ToString()};
        lock (table)
        {
            OverwriteOrAppendLastRow(ref table, cells);
            table.AcceptChanges();
        }
        CallChanged(this, null);
    }

    private void OverwriteOrAppendLastRow(ref DataTable table, List<string> newrow)
    {
        if (table.Rows.Count == 0) CreteEmptyRow(ref table);
        if (newrow[0].ToString() != table.Rows[table.Rows.Count - 1][0].ToString())
        {
            Debug.WriteLine(String.Format("Creating row because '{0}' is different from '{1}'", newrow[0], table.Rows[table.Rows.Count - 1][0]));
            CreteEmptyRow(ref table);
        }
        OverwriteLastRow(ref table, newrow);
    }

    private void OverwriteLastRow(ref DataTable table, List<string> newrow)
    {
        for (int i = 0; i < newrow.Count() && i < table.Columns.Count; i++)
        {
            table.Rows[table.Rows.Count - 1][i] = newrow[i];
        }
    }

    private void CreteEmptyRow(ref DataTable table)
    {
        table.Rows.Add(new String[table.Columns.Count]);
    }

    #endregion

    private void CreateTable()
    {
        table = new DataTable();
        table.Columns.Add("FirstCell", typeof(String));
        table.Columns.Add("BananaCell", typeof(String));
        table.Columns.Add("CherryCell", typeof(String));
        table.Columns.Add("Blue", typeof(String));
        Random rnd = new Random();
        for (int i = 0; i < 145; i++)
        {
            table.Rows.Add(new String[]{
                rnd.Next().ToString(), 
                rnd.Next(0,i+1).ToString(), 
                rnd.ToString(), 
                rnd.NextDouble().ToString()});
        }
    }

}

}

如何阻止这种多线程崩溃?


编辑:

我不知道这段代码崩溃的原因是否不止一个。但我尽我所能收集有关崩溃原因的一些信息:

App.g.cs 中的 Nullpointer 异常 - 自动生成的部分。调试器不会介入 - 所以我不能说它崩溃的行。

这里是异常细节,对德国人感到抱歉。

System.NullReferenceException wurde nicht behandelt.
  Message=Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt.
  Source=PresentationFramework
  InnerException: 

Stacktrace 仅显示“外部代码”,因此没有要跟踪的堆栈。

问题是 WPF 崩溃了——我的代码可以处理它……不知何故我需要封装 WPF 使其不会崩溃,一种方法是复制 DataTable——但随后我失去了写回该数据的能力表,因为在编辑某些内容时不会调用它的设置器。

编辑#2:

我重新创建了这个示例来显示我在另一个程序中遇到的错误,我刚刚发现崩溃实际上与滚动条有关。如果我将显示数据的数量更改为较低的数字,以便没有滚动条,代码将不会崩溃。

【问题讨论】:

  • 你有例外吗?如果是这样,请编辑您的问题并添加异常消息和堆栈跟踪。
  • 根据设计,后台线程无法访问 UI 对象。我怀疑它在尝试更新 UI 时崩溃了。 UI 不会每次都检查正确的线程,因此您可能会进行一些更新以使您相信这不是问题所在。但它确实挂得很快,而且方式很糟糕。不能肯定地说这是问题,但我可以说我有类似的问题和类似的症状。我所做的是让 UI RO 然后在后台处理一个副本,然后在回调中将 UI 绑定到已处理的副本,并使 UI RW。

标签: c# wpf multithreading datatable wpfdatagrid


【解决方案1】:

以下对视图模型的更改解决了该问题。

我现在正在使用 wpf 的副本来处理,并且只注意它们发生时的变化。此代码存在改进机制不佳的问题 - 但这超出了此问题的范围。

public class MainWindowViewModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private SomeBackgroundThing thing;

    private DataTable wpftable;
    public DataTable table
    {
        get
        {
            lock (wpftable)
            {
                return wpftable;
            }

        }

        set
        {
            lock (wpftable)
            {
                wpftable = value;
            }
        }
    }

    public MainWindowViewModel(SomeBackgroundThing thing)
    {
        wpftable = thing.table.Copy();
        this.thing = thing;
        thing.Changed += new EventHandler(thing_Changed);
    }

    void thing_Changed(object sender, EventArgs e)
    {
        if (PropertyChanged != null)
        {
            DataTable wpftablecopy = wpftable.Copy();
            DataTable thintablecopy = thing.table.Copy();
            int rowcount = wpftablecopy.Rows.Count;
            for (int col = 0; col < 4; col++)
            {
                for (int row = 0; row < rowcount; row++)
                {
                    if (wpftablecopy.Rows[row][col] != thintablecopy.Rows[row][col])
                        wpftable.Rows[row][col] = thintablecopy.Rows[row][col];
                }
            }
            PropertyChanged(this, new PropertyChangedEventArgs("table"));
        }
    }
}

【讨论】:

    猜你喜欢
    • 2012-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-02-15
    • 1970-01-01
    • 2018-05-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多