Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/286.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 带有异步viewmodelupdate的反应式ui绑定崩溃_C#_Android_Xamarin_Reactiveui - Fatal编程技术网

C# 带有异步viewmodelupdate的反应式ui绑定崩溃

C# 带有异步viewmodelupdate的反应式ui绑定崩溃,c#,android,xamarin,reactiveui,C#,Android,Xamarin,Reactiveui,我有一个关于反应式ui、它的绑定以及它如何处理ui更新的问题。我一直认为使用ReactiveUi会处理ui线程上的所有ui更新。但我最近发现情况并非总是如此 简言之,问题是:如何使用reactiveui来双向绑定viewmodel和view,并确保更新viewmodel在与ui线程不同的线程上运行时不会崩溃?无需手动订阅更改并在uiThread上进行明确更新,因为这违背了reactiveui的目的,也使得在PCL中封装所有逻辑变得更加困难 下面,我使用Xamaring和Reactiveui提供了

我有一个关于反应式ui、它的绑定以及它如何处理ui更新的问题。我一直认为使用ReactiveUi会处理ui线程上的所有ui更新。但我最近发现情况并非总是如此

简言之,问题是:如何使用reactiveui来双向绑定viewmodel和view,并确保更新viewmodel在与ui线程不同的线程上运行时不会崩溃?无需手动订阅更改并在uiThread上进行明确更新,因为这违背了reactiveui的目的,也使得在PCL中封装所有逻辑变得更加困难

下面,我使用Xamaring和Reactiveui提供了一个非常简单的(Android)项目,用于执行以下操作:

button.Click += delegate
    {
        this.ViewModel.Text += "a";                         // does not crash
        Task.Run(() => { this.ViewModel.Text += "a"; });    // crash
    };
  • 按钮,文本为“Hello World”
  • 点击按钮会在按钮文本中添加“a”
  • 我让活动实现IViewFor,并使用从ReactiveObject派生的ViewModel,其中包含我要更改的文本
  • 我将活动的button.Text绑定到ViewModel.Text,让reactiveui处理所有更改和ui更新
  • 最后,我在按钮的onclick中添加了一个函数,将“a”附加到ViewModel中
我的问题如下:

button.Click += delegate
    {
        this.ViewModel.Text += "a";                         // does not crash
        Task.Run(() => { this.ViewModel.Text += "a"; });    // crash
    };
直接附加“a”不是问题。但是,在不同的线程上添加“a”会导致众所周知的Java异常:异常:只有创建视图层次结构的原始线程才能接触其视图

我理解例外情况以及它的来源。事实上,如果我在另一个线程上附加'a',我已经让它简单地不绑定文本。而是订阅更改,并使用RunOnUiThread方法对ui进行更改。但是这种场景有点违背了使用ReactiveUi的目的。我非常喜欢简单语句“this.Bind(ViewModel,x=>x.Text,x=>x.button.Text);”的简洁编码方式,但是如果这个必须在uiThread上运行,我就看不出如何使它工作

当然,这是显示问题的最小值。我之所以提出这个问题,是因为我想使用来自akavache的“GetAndFetchLatest”方法。它异步获取数据并缓存数据,然后执行一个函数(正在更新ViewModel)。如果数据已经在缓存中,它将使用缓存的结果执行ViewModel更新,并在不同的线程中执行计算逻辑,然后在完成后再次调用相同的函数(导致崩溃,因为在不同的线程上更新ViewModel,从而导致崩溃)

请注意,尽管明确使用RunOnUiThread是可行的,但我真的不想(甚至不能)在ViewModel中调用它。因为我有一段更复杂的代码,其中一个按钮只是告诉ViewModel去获取数据并更新自己。如果我被要求在uiThread上执行此操作(即,在我获得数据后,我更新ViewModel),那么我就不能再将iOS绑定到同一个ViewModel

最后,这里是使它崩溃的全部代码。我已经看到Task.Run-part有时可以工作,但是如果您添加更多的任务并不断更新其中的ViewModel,它最终肯定会在UI线程上崩溃

public class MainActivity : Activity, IViewFor<MainActivity.RandomViewModel>
{
    public RandomViewModel ViewModel { get; set; }
    private Button button;

    protected override void OnCreate(Bundle bundle)
    {
        base.OnCreate(bundle);

        SetContentView(Resource.Layout.Main);

        this.button = FindViewById<Button>(Resource.Id.MyButton);

        this.ViewModel = new RandomViewModel { Text = "hello world" };
        this.Bind(ViewModel, x => x.Text, x => x.button.Text);

        button.Click += delegate
            {
                this.ViewModel.Text += "a";                         // does not crash
                Task.Run(() => { this.ViewModel.Text += "a"; });    // crash
            };
    }

    public class RandomViewModel : ReactiveObject
    {
        private string text;

        public string Text
        {
            get
            {
                return text;
            }
            set
            {
                this.RaiseAndSetIfChanged(ref text, value);
            }
        }
    }

    object IViewFor.ViewModel
    {
        get
        {
            return ViewModel;
        }
        set
        {
            ViewModel = value as RandomViewModel;
        }
    }
}
公共类主活动:活动,IViewFor
{
公共随机视图模型视图模型{get;set;}
私人按钮;
创建时受保护的覆盖无效(捆绑包)
{
base.OnCreate(bundle);
SetContentView(Resource.Layout.Main);
this.button=findviewbyd(Resource.Id.MyButton);
this.ViewModel=new RandomViewModel{Text=“hello world”};
this.Bind(ViewModel,x=>x.Text,x=>x.button.Text);
按钮。单击+=委派
{
this.ViewModel.Text+=“a”;//不会崩溃
Task.Run(()=>{this.ViewModel.Text+=“a”;});//崩溃
};
}
公共类RandomViewModel:反应对象
{
私有字符串文本;
公共字符串文本
{
得到
{
返回文本;
}
设置
{
此.RaiseAndSetIfChanged(参考文本,值);
}
}
}
对象IViewFor.ViewModel
{
得到
{
返回视图模型;
}
设置
{
ViewModel=作为随机ViewModel的值;
}
}
}

这一点已经讨论过了,简短的回答是“出于性能原因,按设计”

我个人并不完全相信后者(在设计API时,性能通常是一个糟糕的驱动因素),但我将尝试解释为什么我认为这种设计是正确的:

将对象绑定到视图时,您通常希望视图出现并在对象属性处达到峰值(读取),并且它是从UI线程执行的

一旦您承认了这一点,修改该对象(从UI线程将其峰值化为)的唯一明智的方法(如线程安全和保证工作)是也从UI线程进行修改

来自其他线程的修改可能会起作用,但只能在特定的条件下工作,开发人员通常不关心这些条件(直到他们获得UI工件,在这种情况下,他们…执行刷新…)

例如,如果您正在使用,并且您的属性值是不可变的(例如字符串),并且您的视图在收到值更改通知之前不会因为观察到值更改而感到不安(简单控件可能可以,具有过滤/排序功能的网格可能不可以,除非它们完全深度复制其源)

您应该在设计ViewModel时考虑到它存在于UI上下文中这一事实

对于Rx,这意味着在ViewModel修改代码之前有
.OnserverOn(/*ui调度程序*/)