WPF DataGrid多线程崩溃 [英] WPF DataGrid Multithreading Crash

查看:596
本文介绍了WPF DataGrid多线程崩溃的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 DataGrid 的视图。
ViewModel as DataContext 我可以在后台对象中访问 DataTable
背景对象必须使用 DataTable 并保持更新。
还允许用户对 DataTable 进行更改。

I have a View with a DataGrid in it. A ViewModel as DataContext where i can access a DataTable in a background object. The background object has to work with the DataTable and keep it updated. The user has also be allowed to make changes to that DataTable.

如果我创建一个副本的 DataTable 它会停止崩溃,但用户无法正常处理数据。

If i create a copy of the DataTable it stops crashing but the user is obviousely not working on the data.

如果我离开访问权限对于用户,该程序不可避免地崩溃。

If i leave access open for the user the program crashed inevitabely.

这是一个简短的程序,将崩溃:

Here is a short program that will crash:

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();
    }
}

主xaml

<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()});
        }
    }

}

}

我如何阻止这个多线程崩溃?

How can i stop this multithread crashing?

编辑:

我不知道这个代码有多个原因导致崩溃。但是我尽力收集一些关于崩溃原因的信息:

I don't know if there are more than one reasons for this code to crash. But i did my best to gather some information about one reason to crash:

App.g.cs中的Nullpointer异常 - 自动生成部分。调试器不会进入它 - 所以我不能说任何关于它崩溃的线路。

Nullpointer exception in App.g.cs - the autogenerated portion. The Debugger wont step into it - so i cant say anything about the line it crashes in.

这是异常详细信息,对于德国人来说是不好的。

Here is the Exception Detail, sorry for the German.

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

StackTrace只显示Externer Code所以没有堆栈跟踪。

The Stacktrace only shows "Externer Code" so no stack to trace.

事情是WPF崩溃 - 我的代码可以处理它...不知何故我需要胶囊WPF,所以它不会崩溃,一种方法这样做是为了复制DataTable - 但是我松开了写回该表的能力,因为在编辑某些东西时,它不会调用setter。

The thing is that WPF crashes - my code can handle it... somehow i need to capsule WPF so it wont crash, one way to do that is to copy the DataTable - but then i loose the ability to write back that table since its setter is not called when something has gotten edited.

编辑#2:

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

I recreated this example to show the error i have in another program and i just found out that what crashes is actually related with the scrollbar. If i change the ammount of displayed data to a low number so that there is no scrollbar, the code will not crash.

推荐答案

viewmodel的以下更改解决了这个问题。

The following change to the viewmodel solves the problem.

我现在正在使用一个wpf的副本来处理,只有注意到它们会发生的。这个代码有一个很差的改进机制的问题 - 但这超出了这个问题的范围。

I am now using a copy for wpf to work on and only note the canges should they occur. This code has an issue with the poorly refined change mechanism - but that is beyond the scope of this question.

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"));
        }
    }
}

这篇关于WPF DataGrid多线程崩溃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆