将大量项目添加到DataGrid时,UI会冻结

时间:2014-05-07 16:17:44

标签: c# wpf multithreading datagrid

我得到了一个DataGrid,它在处理了一个更新函数之后被填充了几个项目,该函数在BackgroundWorker内执行。

BackgroundWorker实际上请求JSON API并解析传入的响应。在该过程中,UI根本没有锁定,一切都仍然响应。现在,当使用先前获取的响应填充DataGrid时,UI会锁定并冻结,直到添加完所有项目。

代码:

更新方法:

public override void Update()
{
    CanUpdate = false;
    AddEverythingButtonVisible = false;

    var worker = new BackgroundWorker();
    worker.DoWork += (s, e) =>
    {
        // Item.Search is sending a HTTP request, everything is responding
        foreach (Item item in Item.Search(_SearchText, _SearchLevelMin, _SearchLevelMax, _SearchRarity, _SearchType,_SearchRemoveUnavailable))
        {
            // after the response was parsed, the lock begins at this point when adding items by using the UI thread
            Execute(() => ItemCollection.Add(new ProfitItemViewModel(new ProfitItem(item))));
        }
     }
 }

Execute是一个小辅助函数,用于在单独的线程(BackgroundWorker)中访问UI线程,如下所示:

protected void Execute(System.Action action)
{
    if (Dispatcher.CheckAccess())
    {
        action.Invoke();
    }
    else
    {
        Dispatcher.BeginInvoke(DispatcherPriority.DataBind, action);
    }
}

我读过有关BeginInvokeInvoke的内容,并决定在执行操作时UI线程不得停止响应。所以我最终实现了BeginInvoke但是在更新时UI仍然冻结并锁定。

嗯,有没有解决方案在我的DataGrid被填满时没有锁定我的整个应用程序?


已更新正常工作代码:

var searchResults = Item.Search(_SearchText, _SearchLevelMin, _SearchLevelMax, _SearchRarity, _SearchType, _SearchRemoveUnavailable);
var compiledSearchResults = searchResults.Select(item => new ProfitItemViewModel(new ProfitItem(item)));
foreach (ProfitItemViewModel item in compiledSearchResults)
    Execute(() => ItemCollection.Add(item));

2 个答案:

答案 0 :(得分:3)

您正在单独添加项目,每个项目都作为单独的调用调用,这将极大地降低UI的速度。而是在单个UI调用中添加尽可能多的项目。它可能会导致暂时的故障,但会比一堆单个呼叫快得多。

此外,您正在UI调用中创建对象。如果它们不是依赖项对象,请在进行UI调用之前创建它们以保存一些UI功能。

经验法则:尽可能多地在UI线程之外做,并且只在必须在UI线程上完成的UI线程上工作(在您的情况下,更新ItemCollection)。

示例:

var searchResult = Item.Search(_SearchText, _SearchLevelMin, _SearchLevelMax, _SearchRarity, _SearchType,_SearchRemoveUnavailable));

var compiledList = searchResult.Select(item => new ProfitItemViewModel(new ProfitItem(item))).ToArray();

Execute(() => {
    foreach (Item item in compiledList)
    {
        ItemCollection.Add(item);
    }
});

还可以考虑使用DeferRefresh加速添加,如下所示:

Execute(() => {
    using(ItemCollection.DeferRefresh()) {
        foreach (Item item in compiledList)
        {
            ItemCollection.Add(item);
        }
    }
});

答案 1 :(得分:0)

我认为你应该在另一个线程中加载项目,一旦加载,然后更新网格。例如,我使用这样的方法:

public void LockAndDoInBackground(Action action, string text, Action beforeVisualAction = null, Action afterVisualAction = null)
{
    if (IsBusy)
        return;
    var currentSyncContext = SynchronizationContext.Current;
    ActiveThread = new Thread((_) =>
    {                
        currentSyncContext.Send(t =>
        {
            IsBusy = true;
            BusyText = string.IsNullOrEmpty(text) ? "Wait please..." : text;
            if (beforeVisualAction != null)
                beforeVisualAction();
        }, null);
        action();
        currentSyncContext.Send(t =>
        {
            IsBusy = false;
            BusyText = "";
            if (afterVisualAction != null)
                afterVisualAction();
        }, null);
    });
    ActiveThread.Start();
}

IsBusyBusyText是类中的可选变量(我在Base ViewModel类中使用此方法) action参数应该是加载逻辑,afterVisualAction参数应该是网格中的加载数据操作。例如:

<强> USAGE:

public void Test()
{
    var tcollection = new List<ProfitItemViewModel>();
    Action tloadAction = ()=> 
    {
        foreach (Item item in Item.Search(_SearchText, _SearchLevelMin, _SearchLevelMax, _SearchRarity, _SearchType,_SearchRemoveUnavailable))
        {                
            tcollection.Add(new ProfitItemViewModel(new ProfitItem(item))));
        }
    };
    Action tupdateGridAction = ()=> 
    {
        foreach (Item item in tcollection)
        {                
            ItemCollection.Add(item);
        }
    };
    LockAndDoInBackground(tloadAction, "Generating Information...", null,  tupdateGridAction);
}

这是我提出的想法。希望它有所帮助。