我得到了一个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);
}
}
我读过有关BeginInvoke
和Invoke
的内容,并决定在执行操作时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));
答案 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();
}
IsBusy
和BusyText
是类中的可选变量(我在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);
}
这是我提出的想法。希望它有所帮助。