WPF DataGrid正在添加额外的“重影".排 [英] WPF DataGrid is adding extra "ghost" row

查看:137
本文介绍了WPF DataGrid正在添加额外的“重影".排的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Hei

在我的应用程序中,我正在使用DataGrid来显示一些数据.为了使一切都能与线程一起使用,我正在使用 AsyncObservableCollection 作为DataGrid的DataContext.当我的应用程序启动时,它将在某些文件夹中查找文件并更新AsyncObservableCollection.查找文件是在单独的线程上完成的:

Task.Factory.StartNew(() => _cardType.InitAllOrdersCollection())
    .ContinueWith((t) => ThrowEvent(), TaskContinuationOptions.None);

所有加载逻辑都在InitAllOrdersCollection()方法中.

现在这是发生问题的地方,当我出于某种原因启动应用程序时,即使集合中只有一项,而文件夹中只有一个文件,我也会在DataGrid中得到两行数据相同的数据.如果我在加载文件之前添加一个延迟(最小为Thread.Sleep() 50ms),则DataGrid会正确显示所有内容(没有多余的行).必须将延迟添加到线程中,以加载文件(使用Task.Factory.StartNew()创建的文件).

有人遇到过类似的事情吗?或者还有其他我应该尝试的事情吗? 预先感谢!

根据要求添加一些代码:

public AsyncObservableCollection<IGridItem> OrdersCollection = new AsyncObservableCollection<IGridItem>();

public void InitAllOrdersCollection()
{
    // Thread.Sleep(50); <-- this sleep here fixes the problem!
    foreach (var convention in FileNameConventions)
    {
        var namePatterns = convention.NameConvention.Split(',');
        foreach (var pattern in namePatterns)
        {
            var validFiles = CardTypeExtensions.GetFiles(this.InputFolder, pattern, convention);
            if (validFiles.Any())
            {
                this.FilesToOrders(validFiles, convention);
            }
        }
    }
}

public static List<string> GetFiles(string inputFolder, string pattern, FileNameConvention convention)
{
    var files = Directory.GetFiles(inputFolder, pattern);
    return files.Where(file => IsCorrect(file, convention)).AsParallel().ToList();
}

// Adds new order to OrdersCollection if its not there already!
private void FilesToOrders(List<string> dirFiles, FileNameConvention convention)
{
    foreach (var dirFile in dirFiles.AsParallel())
    {
        var order = new Order(dirFile, this, convention);

        if (!this.OrdersCollection.ContainsOrder(order))
        {
                this.OrdersCollection.Add(order);
        }
    }
}

public static bool ContainsOrder(this ObservableCollection<IGridItem> collection, Order order)
{
    return collection.Cast<Order>().Any(c=>c.Filepath == order.Filepath);
}

FilesToOrders()方法是一种将新订单添加到AsyncObservableCollection的方法. 希望这会有所帮助.

解决方案

也许我遗漏了一些明显的东西,但是您发布的链接中的AsyncObservableCollection实现对我而言似乎不是线程安全的.

我可以看到它包含在创建者(消费者)线程上触发CollectionChanged/PropertyChanged事件的代码,但是我看不到任何同步操作可以安全地访问集合线程中的项目.

更新

据我所知,您可以同时发生以下情况,而无需进行任何同步:

  • 工作者(生产者)线程正在插入项目

  • UI(消费者)线程正在枚举项目

一种可能是修改AsyncObservableCollection.InsertItem以调用SynchronizationContext.Send以便将项目插入使用者线程中,尽管这当然会影响性能(生产者在继续继续之前要等待使用者线程完成插入操作). /p>

另一种方法是使用仅在使用者线程上访问过的标准ObservableCollection,并使用SynchronizationContext.Post张贴要从生产者线程插入的项目.像这样:

foreach (var dirFile in dirFiles.AsParallel())
{
    var order = new Order(dirFile, this, convention);

    _synchronizationContext.Post(AddItem, order);

}

...

void AddItem(object item)
{
    // this is executed on the consumer thread
    // All access to OrderCollection on this thread so no need for synchnonization
    Order order = (Order) item;
    if (!OrdersCollection.ContainsOrder(order))
    {
        OrdersCollection.Add(order);
    }
}

Hei,

In my application i'm using DataGrid to show some data. To get everything working with threading i'm using AsyncObservableCollection as DataContext of DataGrid. When my application starts it looks for files in some folders and updates AsyncObservableCollection. Finding files is done on a separate thread:

Task.Factory.StartNew(() => _cardType.InitAllOrdersCollection())
    .ContinueWith((t) => ThrowEvent(), TaskContinuationOptions.None);

Where all the loading logic is in InitAllOrdersCollection() method.

Now here's where things go bad, when i start the application for some reason i get 2 rows with same data in DataGrid even if there is one item in collection and only one file in folders. If i add a delay(Thread.Sleep() 50ms minimum) before loading files then DataGrid show everything correctly (no extra row). Delay has to be added to the Thread what is loading the files (The one created with Task.Factory.StartNew()).

Have anybody encountered something similar or is there something else i should try? Thanks in advance!

EDIT: Adding some code as requested:

public AsyncObservableCollection<IGridItem> OrdersCollection = new AsyncObservableCollection<IGridItem>();

public void InitAllOrdersCollection()
{
    // Thread.Sleep(50); <-- this sleep here fixes the problem!
    foreach (var convention in FileNameConventions)
    {
        var namePatterns = convention.NameConvention.Split(',');
        foreach (var pattern in namePatterns)
        {
            var validFiles = CardTypeExtensions.GetFiles(this.InputFolder, pattern, convention);
            if (validFiles.Any())
            {
                this.FilesToOrders(validFiles, convention);
            }
        }
    }
}

public static List<string> GetFiles(string inputFolder, string pattern, FileNameConvention convention)
{
    var files = Directory.GetFiles(inputFolder, pattern);
    return files.Where(file => IsCorrect(file, convention)).AsParallel().ToList();
}

// Adds new order to OrdersCollection if its not there already!
private void FilesToOrders(List<string> dirFiles, FileNameConvention convention)
{
    foreach (var dirFile in dirFiles.AsParallel())
    {
        var order = new Order(dirFile, this, convention);

        if (!this.OrdersCollection.ContainsOrder(order))
        {
                this.OrdersCollection.Add(order);
        }
    }
}

public static bool ContainsOrder(this ObservableCollection<IGridItem> collection, Order order)
{
    return collection.Cast<Order>().Any(c=>c.Filepath == order.Filepath);
}

FilesToOrders() method is the one what adds the new orders to the AsyncObservableCollection. Hope this helps.

解决方案

Maybe I'm missing something obvious, but the AsyncObservableCollection implementation in the link you posted doesn't look thread-safe to me.

I can see it includes code to fire the CollectionChanged / PropertyChanged events on the creator (consumer) thread, but I don't see any synchronization to make access to the items in the collection thread-safe.

UPDATE

As far as I can see you can have the following happening concurrently, without any synchronization:

  • the worker (producer) thread is inserting item(s)

  • the UI (consumer) thread is enumerating items

One possibility might be to modify AsyncObservableCollection.InsertItem to call SynchronizationContext.Send to insert the item on the consumer thread, though this will of course have an effect on performance (producer waits for consumer thread to complete insertion before continuing).

An alternative approach would be to use a standard ObservableCollection that is only ever accessed on the consumer thread, and use SynchronizationContext.Post to post items to insert from the producer thread. Something like:

foreach (var dirFile in dirFiles.AsParallel())
{
    var order = new Order(dirFile, this, convention);

    _synchronizationContext.Post(AddItem, order);

}

...

void AddItem(object item)
{
    // this is executed on the consumer thread
    // All access to OrderCollection on this thread so no need for synchnonization
    Order order = (Order) item;
    if (!OrdersCollection.ContainsOrder(order))
    {
        OrdersCollection.Add(order);
    }
}

这篇关于WPF DataGrid正在添加额外的“重影".排的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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