我在那里找到了有趣的(至少对我来说)错误。
我正在创建一个(原型)应用程序,它执行一些Web请求并返回简单数据。
动态更新ObservableCollection<DownloadbleEntity>
(因为DownloadbleEntity
包含我们通过其他请求获得的图像,输出带有图像的列表元素)。
这是布局部分:
<MvvmCross.Binding.Droid.Views.MvxListView
android:id="@+id/searchlist"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
local:MvxBind="ItemsSource FoundItems; ItemClick OnItemClickCommand; OnScrollToBottom GetNewAsyncCommand"
local:MvxItemTemplate="@layout/listitem" />
这是ViewModel代码,用于显示更新的方式:
private ObservableCollection<DownloadableEntity> _foundItems;
public ObservableCollection<DownloadableEntity> FoundItems
{
get { return _foundItems; }
set
{
if (_currentPage > 0)
{
_foundItems = new ObservableCollection<DownloadableEntity>(_foundItems.Concat(value));
}
else
{
_foundItems = value;
}
RaisePropertyChanged(() => FoundItems);
}
}
private async Task PrepareDataForOutput(SearchResult searchResult)
{
_currentListLoaded = false;
IMvxMainThreadDispatcher dispatcher = Mvx.Resolve<IMvxMainThreadDispatcher>();
List<Task<DownloadableEntity>> data = searchResult.Tracks.Items.ToList().Select(async (x) => await PrepareDataOutputAsync(x).ConfigureAwait(false)).ToList();
Android.Util.Log.Verbose("ACP", "PrepareDataForOutput");
try
{
var result = new ObservableCollection<DownloadableEntity>();
while (data.Count > 0)
{
var entityToAdd = await Task.Run(async () =>
{
Task<DownloadableEntity> taskComplete = await Task.WhenAny(data).ConfigureAwait(false);
data.Remove(taskComplete);
DownloadableEntity taskCompleteData = await taskComplete.ConfigureAwait(false);
await Task.Delay(500);
return taskCompleteData;
}).ConfigureAwait(false);
result.Add(entityToAdd);
// as it recommended by mvvmcross providers
dispatcher.RequestMainThreadAction(async () =>
await Task.Run(() =>
{
Android.Util.Log.Verbose("ACP", $"RequestMainThreadAction update {result.Last().Title}");
_toastService.ShowToastMessage($"Got {result.Last().Title}");
FoundItems = result;
}).ConfigureAwait(false)
);
}
await Task.WhenAll(data).ContinueWith((x) =>
{
Android.Util.Log.Verbose("ACP", "Output is Done");
_currentListLoaded = true;
});
}
catch (Exception e)
{
Android.Util.Log.Verbose("ACP", e.Message);
}
}
private async Task<DownloadableEntity> PrepareDataOutputAsync(PurpleItem x)
{
return new DownloadableEntity
{
Title = x.Title,
ArtistName = x.Artists.Select(y => y.Name).Aggregate((cur, next) => cur + ", " + next),
Image = await Task.Run(() => _remoteMusicDataService.DownloadCoverByUri(x.Albums.FirstOrDefault().CoverUri)).ConfigureAwait(false),
AlbumName = x.Albums.First().Title ?? "",
AlbumId = x.Albums.First().Id,
TrackId = x.Id
};
}
嗯,问题是 - 在设备上,数据输出开始后输出一个列表元素,同时输出:
时间设备名称类型PID标记消息12-09 13:45:22.012 Wileyfox 斯威夫特2 X Info 20385 mvx android.view.ViewRootImpl $ CalledFromWrongThreadException: 只有创建视图层次结构的原始线程才能触及它 观点。在 android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6898)at at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1048)at android.view.View.requestLayout(View.java:19785)at android.view.View.requestLayout(View.java:19785)at android.view.View.requestLayout(View.java:19785)at android.view.View.requestLayout(View.java:19785)at android.view.View.requestLayout(View.java:19785)at android.widget.AbsListView.requestLayout(AbsListView.java:1997)at android.widget.AdapterView $ AdapterDataSetObserver.onChanged(AdapterView.java:840) 在 android.widget.AbsListView $ AdapterDataSetObserver.onChanged(AbsListView.java:6380) 在 android.database.DataSetObservable.notifyChanged(DataSetObservable.java:37) 在 android.widget.BaseAdapter.notifyDataSetChanged(BaseAdapter.java:50)
但它继续更新我的收藏集,因此Android.Util.Log.Verbose("ACP", $"RequestMainThreadAction update {result.Last().Title}");
切换,我可以在设备日志窗口中看到它。
但只有在我做某事后才能在我的设备屏幕上继续渲染 - 触摸屏或触摸我的SearchView
输入或旋转它。
有点奇怪我不知道是什么原因造成的。 是因为我对收藏更新做错了吗?
我录制了最新动态的视频,所以here it is(对不起我的英语和口音:()
UPD(关于上次评论):
如果在dispatcher.RequestMainThreadAction
内发生更新,那么集合是否真的从后台线程更新?
UPD2
我添加了线程号检测,因此,看起来数字总是相同的
代码:
// as it recommended
dispatcher.RequestMainThreadAction(async () =>
await Task.Run(() =>
{
var poolId = TaskScheduler.Current.Id;
Android.Util.Log.Verbose("ACP THREAD INFO", $"RequestMainThreadAction update {result.Last().Title} THREAD NUMBER {poolId}");
_toastService.ShowToastMessage($"Got {result.Last().Title} by {result.Last().ArtistName}");
FoundItems = result;
}).ConfigureAwait(false)
);
同样在View
我添加了:
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
_searchView = FindViewById<SearchView>(Resource.Id.search10);
ViewModel.OnSearchStartEvent += ViewModel_OnSearchStartEvent;
var poolId = TaskScheduler.Current.Id;
Android.Util.Log.Verbose("ACP THREAD INFO", $"VIEW CREATED FROM THREAD NUMBER {poolId}");
}
输出:
此外,试过这种方法但没有成功:
Mvx.Resolve<IMvxAndroidCurrentTopActivity>().Activity.RunOnUiThread(async () =>
await Task.Run(() =>
{
var poolId = TaskScheduler.Current.Id;
Android.Util.Log.Verbose("ACP THREAD INFO", $"RequestMainThreadAction update {result.Last().Title} THREAD NUMBER {poolId}");
_toastService.ShowToastMessage($"Got {result.Last().Title} by {result.Last().ArtistName}");
FoundItems = result;
}).ConfigureAwait(false)
);
UPD3
我找到了一种解决方法(但仍然没有解决方案) - 如果我使用MvxObservableCollection
代替ObservableCollection
而不是FoundItems
- 一切正常运作!
如果我们看一下这个类(MvxObservableCollection.cs),我们会看到它有那些触发更新的函数,看起来它在那里做同样的事情:
protected virtual void InvokeOnMainThread(Action action)
{
var dispatcher = MvxSingleton<IMvxMainThreadDispatcher>.Instance;
dispatcher?.RequestMainThreadAction(action);
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
InvokeOnMainThread(() => base.OnPropertyChanged(e));
}
但是我不明白为什么在我的情况下它不像它想象的那样工作,我的意思是只是定期ObservableCollection
?
ObservableCollection
更改是否更为创建另一个线程或什么?
答案 0 :(得分:0)
不要窃取@jazzmasterkc的信用,但是认为这需要作为答案发布。使用MvxObservableCollection而非ObservableCollection进行列表值绑定可解决此问题,因为MvxObservableCollection会在主线程上调用所有PropertyChanged和CollectionChanged事件。