【发布时间】:2018-05-23 12:31:42
【问题描述】:
我在那里发现的有趣(至少对我而言)错误。
我正在制作一个(原型)应用程序,它会执行一些 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 信息 20385 mvx android.view.ViewRootImpl$CalledFromWrongThreadException: 只有创建视图层次结构的原始线程才能触及其 意见。在 android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6898) 在 android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1048) 在 android.view.View.requestLayout(View.java:19785) 在 android.view.View.requestLayout(View.java:19785) 在 android.view.View.requestLayout(View.java:19785) 在 android.view.View.requestLayout(View.java:19785) 在 android.view.View.requestLayout(View.java:19785) 在 android.widget.AbsListView.requestLayout(AbsListView.java:1997) 在 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 的 notyfier 更改会创建另一个线程还是什么?
【问题讨论】:
-
@MatPag 我看到了,但我觉得
dispatcher.RequestMainThreadAction中的调用函数就够了,还是不行? -
使用dispatcher就够了。但是,您的 UI 更新在
Task.Run很多地方。 -
@Cheesebaron wellp 即使我不使用
Task.Run包装器,我也得到了相同的图片:( -
@Cheesebaron 但我只是想更新问题,所以我在那里发现了一件事
标签: c# android xamarin async-await mvvmcross