UI 在异步方法完成之前不会更新
UI doesn't update until async method finishes
在我的项目中,我想显示进度指示器,从 web 服务检索数据并隐藏进度指示器。如果我这样做,我目前必须等到我从 web 服务检索数据,然后 UI 立即更新结果。但是进度指示器永远不会出现。看来,我正在 UI 线程上工作或阻塞。但是在哪里呢?
这是我的代码的简单版本:
SomePage
private async void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
{
await ShowDetailView();
}
private async Task ShowDetailView()
{
var view = new MyView();
this.detailView = view;
await view.LoadContent();
}
SomeView
public async Task LoadContent()
{
var success = await LoadData();
if (success)
{
ShowInformation();
}
}
public async Task<bool> LoadData()
{
try
{
ShowLoadingProcess();
await Task.Delay(5000);
this.itemList = await WebService.Instance.GetData();
return true;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
return false;
}
finally
{
HideLoadingProcess();
}
}
private void ShowInformation()
{
Device.BeginInvokeOnMainThread(() =>
{
this.grid.Clear();
foreach(var item in this.itemList)
{
GridItem contentItem = new GridItem(item);
this.grid.Children.Add(contentItem);
}
}
}
private void ShowLoadingProcess()
{
Device.BeginInvokeOnMainThread(() => BringProgressIndicatorToFront());
}
private void HideLoadingProcess()
{
Device.BeginInvokeOnMainThread(() => BringProgressIndicatorToBack());
}
我尝试了不同的方法,我在 UI 和后台线程之间不同步。例如。 ShowInformation()
在 LoadData()
完成之前被调用。
有人可以提示我这里出了什么问题吗?
我不完全确定为什么您的代码会按照它的方式运行,但您应该考虑到 Device.BeginInvokeOnMainThread()
不会对何时执行操作做出任何承诺。它在内部执行以下操作:
// Simplified
s_handler = new Handler(Looper.MainLooper);
s_handler.Post(action);
它将您的操作传递给消息队列,然后在未来未知的时间点执行该操作。
但是,我建议您重新考虑应用程序的体系结构。通过利用 MVVM 模式和数据绑定,您将能够非常轻松地控制进度指示器的可见性。
在 ViewModel 中,您将拥有以下内容:
private bool isBusy;
public bool IsBusy() {
get { return isBusy; }
set { SetProperty(ref isBusy, value); }
}
在将上述 ViewModel 作为其 BindingContext 的视图中,您将拥有如下内容:
<ActivityIndicator IsRunning="{Binding IsBusy}" IsVisible="{Binding IsBusy}" />
现在,无论何时开始长时间的 运行 操作,您只需将 isBusy
更改为 true 并且视图会自动显示 运行 ActivityIndicator 由于绑定.
我不熟悉xamarin.forms,所以以下内容可能完全是垃圾。如果是,请告诉我,我会删除答案。
对我来说,您似乎有一些带有事件处理程序 OnItemSelected 的表单,它应该显示 DetailView,它是其他表单。创建 DetailView 对象后,您要加载详细视图。此加载需要一些时间,同时您要通知操作员表单正在加载。
我不确定您想要视觉反馈的位置:在 SomePage 或 SomeView 上。答案并不重要,除了显示和隐藏视觉反馈的命令最好写在显示反馈的 class 中。
在 SomeView 上显示反馈
在这种情况下,SomeView 可以显示和隐藏视觉反馈,例如进度条或 ajax 加载器。代码如下:
因为我对Form比较熟悉,所以就写成Form吧。您的代码将非常相似
class SomeView : Form
{
void ShowVisualFeedBack()
{
...
}
void HideVisualFeedBack()
{
...
}
public async Task LoadDataAsync()
{
bool result = false;
this.ShowVisualFeedBack()
try
{
this.itemList = await WebService.Instance.GetData();
result = true;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
this.HideVisualFeedBack();
return result;
}
}
或者如果您确实需要等待 5 秒才能开始获取数据:
try
{
await Task.Delay(TimeSpan.FromSeconds(5));
this.itemList = await WebService.Instance.GetData();
result = true;
}
在 SomePage 上显示反馈
class SomePage : Form
{
void ShowVisualFeedBack()
{
...
}
void HideVisualFeedBack()
{
...
}
private async Task ShowDetailView()
{
this.ShowVisualFeedBack();
this.DetailView = new MyView();
await view.LoadContent();
this.HideVisualFeedBack();
}
}
顺便说一下,当您的线程正在等待 GetData() 时,它无法更新您的视觉反馈。一些像 GIF 这样的视觉反馈方法不需要你的线程更新,但是如果你有进度条之类的东西,你的线程需要更新它。您可以通过等待最长时间直到 GetData 完成、更新进度并再次等待来完成此操作
var taskWaitASec = Task.Delay(TimeSpan.FromSeconds(1));
var taskGetData = WebService.Instance.GetData();
// note: you are not awaiting yet, so you program continues:
while (!taskGetData.IsCompleted)
{
var myTasks = new Task[] {taskWaitASec, taskGetData}
var completedTask = await Task.WhenAny(myTasks);
if (completedTask == taskWaitASec)
{
UpdateProgress();
taskWaitASec = Task.Delay(TimeSpan.FromSeconds(1));
}
}
非常愚蠢的错误。我有这样的事情:
private async Task ShowDetailView()
{
var view = new MyView();
this.detailView = view;
await view.LoadContent();
this.mainGrid.Children.Add(this.detailView);
}
当然要等到操作完成,然后显示结果。这是正确的方法:
private async Task ShowDetailView()
{
var view = new MyView();
this.detailView = view;
this.mainGrid.Children.Add(this.detailView);
await view.LoadContent();
}
我应该删除我的问题吗?也许吧,因为有时候只见树木不见森林。
在我的项目中,我想显示进度指示器,从 web 服务检索数据并隐藏进度指示器。如果我这样做,我目前必须等到我从 web 服务检索数据,然后 UI 立即更新结果。但是进度指示器永远不会出现。看来,我正在 UI 线程上工作或阻塞。但是在哪里呢?
这是我的代码的简单版本:
SomePage
private async void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
{
await ShowDetailView();
}
private async Task ShowDetailView()
{
var view = new MyView();
this.detailView = view;
await view.LoadContent();
}
SomeView
public async Task LoadContent()
{
var success = await LoadData();
if (success)
{
ShowInformation();
}
}
public async Task<bool> LoadData()
{
try
{
ShowLoadingProcess();
await Task.Delay(5000);
this.itemList = await WebService.Instance.GetData();
return true;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
return false;
}
finally
{
HideLoadingProcess();
}
}
private void ShowInformation()
{
Device.BeginInvokeOnMainThread(() =>
{
this.grid.Clear();
foreach(var item in this.itemList)
{
GridItem contentItem = new GridItem(item);
this.grid.Children.Add(contentItem);
}
}
}
private void ShowLoadingProcess()
{
Device.BeginInvokeOnMainThread(() => BringProgressIndicatorToFront());
}
private void HideLoadingProcess()
{
Device.BeginInvokeOnMainThread(() => BringProgressIndicatorToBack());
}
我尝试了不同的方法,我在 UI 和后台线程之间不同步。例如。 ShowInformation()
在 LoadData()
完成之前被调用。
有人可以提示我这里出了什么问题吗?
我不完全确定为什么您的代码会按照它的方式运行,但您应该考虑到 Device.BeginInvokeOnMainThread()
不会对何时执行操作做出任何承诺。它在内部执行以下操作:
// Simplified
s_handler = new Handler(Looper.MainLooper);
s_handler.Post(action);
它将您的操作传递给消息队列,然后在未来未知的时间点执行该操作。
但是,我建议您重新考虑应用程序的体系结构。通过利用 MVVM 模式和数据绑定,您将能够非常轻松地控制进度指示器的可见性。
在 ViewModel 中,您将拥有以下内容:
private bool isBusy;
public bool IsBusy() {
get { return isBusy; }
set { SetProperty(ref isBusy, value); }
}
在将上述 ViewModel 作为其 BindingContext 的视图中,您将拥有如下内容:
<ActivityIndicator IsRunning="{Binding IsBusy}" IsVisible="{Binding IsBusy}" />
现在,无论何时开始长时间的 运行 操作,您只需将 isBusy
更改为 true 并且视图会自动显示 运行 ActivityIndicator 由于绑定.
我不熟悉xamarin.forms,所以以下内容可能完全是垃圾。如果是,请告诉我,我会删除答案。
对我来说,您似乎有一些带有事件处理程序 OnItemSelected 的表单,它应该显示 DetailView,它是其他表单。创建 DetailView 对象后,您要加载详细视图。此加载需要一些时间,同时您要通知操作员表单正在加载。
我不确定您想要视觉反馈的位置:在 SomePage 或 SomeView 上。答案并不重要,除了显示和隐藏视觉反馈的命令最好写在显示反馈的 class 中。
在 SomeView 上显示反馈
在这种情况下,SomeView 可以显示和隐藏视觉反馈,例如进度条或 ajax 加载器。代码如下:
因为我对Form比较熟悉,所以就写成Form吧。您的代码将非常相似
class SomeView : Form
{
void ShowVisualFeedBack()
{
...
}
void HideVisualFeedBack()
{
...
}
public async Task LoadDataAsync()
{
bool result = false;
this.ShowVisualFeedBack()
try
{
this.itemList = await WebService.Instance.GetData();
result = true;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
}
this.HideVisualFeedBack();
return result;
}
}
或者如果您确实需要等待 5 秒才能开始获取数据:
try
{
await Task.Delay(TimeSpan.FromSeconds(5));
this.itemList = await WebService.Instance.GetData();
result = true;
}
在 SomePage 上显示反馈
class SomePage : Form
{
void ShowVisualFeedBack()
{
...
}
void HideVisualFeedBack()
{
...
}
private async Task ShowDetailView()
{
this.ShowVisualFeedBack();
this.DetailView = new MyView();
await view.LoadContent();
this.HideVisualFeedBack();
}
}
顺便说一下,当您的线程正在等待 GetData() 时,它无法更新您的视觉反馈。一些像 GIF 这样的视觉反馈方法不需要你的线程更新,但是如果你有进度条之类的东西,你的线程需要更新它。您可以通过等待最长时间直到 GetData 完成、更新进度并再次等待来完成此操作
var taskWaitASec = Task.Delay(TimeSpan.FromSeconds(1));
var taskGetData = WebService.Instance.GetData();
// note: you are not awaiting yet, so you program continues:
while (!taskGetData.IsCompleted)
{
var myTasks = new Task[] {taskWaitASec, taskGetData}
var completedTask = await Task.WhenAny(myTasks);
if (completedTask == taskWaitASec)
{
UpdateProgress();
taskWaitASec = Task.Delay(TimeSpan.FromSeconds(1));
}
}
非常愚蠢的错误。我有这样的事情:
private async Task ShowDetailView()
{
var view = new MyView();
this.detailView = view;
await view.LoadContent();
this.mainGrid.Children.Add(this.detailView);
}
当然要等到操作完成,然后显示结果。这是正确的方法:
private async Task ShowDetailView()
{
var view = new MyView();
this.detailView = view;
this.mainGrid.Children.Add(this.detailView);
await view.LoadContent();
}
我应该删除我的问题吗?也许吧,因为有时候只见树木不见森林。