为了账号安全,请及时绑定邮箱和手机立即绑定

如何从 ViewModel 正确执行异步方法?

如何从 ViewModel 正确执行异步方法?

C#
一只甜甜圈 2022-10-23 13:58:05
出于这个问题的目的,我有一个简单Window的以下 XAML:<StackPanel>    <TextBox Text="{Binding MyText}" />    <CheckBox IsChecked="{Binding IsChecked}">Check</CheckBox></StackPanel>每当用户在TextBox或中输入文本时CheckBox,我想执行一个缓慢的任务(例如将模型的状态保存到磁盘)。这是视图模型:public class ViewModel : ViewModelBase  // using GalaSoft.MvvmLight{    private string _myText;    public string MyText    {        get => _myText;        set        {            if (Set(ref _myText, value))                Save();        }    }    private bool _isChecked;    public bool IsChecked    {        get => _isChecked;        set        {            if (Set(ref _isChecked, value))                Save();        }    }    private async void Save()    {        var id = Guid.NewGuid();        Debug.WriteLine($"Starting save {id}");        await Task.Delay(100);  // Simulate slow task        Debug.WriteLine($"Finished save {id}");    }}该Save方法模拟一个缓慢的任务,例如保存到磁盘。出于调试目的,它会在执行此操作之前和之后输出一个唯一 ID。此外,该方法是异步的,因为我不希望 UI 在操作期间冻结。问题是,在用户在 中键入内容TextBox,然后检查 后CheckBox,属性会很快更新。这会导致以下调试示例输出:Starting save 6ea6c102-cbe7-472f-b8b8-249499ff7f64Starting save c77b4478-14ca-4243-a45b-7b35b5663d49Finished save 6ea6c102-cbe7-472f-b8b8-249499ff7f64Finished save c77b4478-14ca-4243-a45b-7b35b5663d49如您所见,第一个保存操作(来自)在第二个保存操作(来自)开始MyText之前没有完成。IsChecked这让我有点害怕,因为我想,数据可能会以错误的顺序保存并损坏。是否有处理此类问题的良好做法?我想了几个可能的解决方案。Delay=100首先是在TextBox绑定中使用类似的东西。这将导致在Save用户停止输入 100 毫秒后调用该方法。由于各种原因,这是一个丑陋的解决方案。第二种是使用SemaphoreSlim. 在Save方法内部,我可以用try/将代码括起来finally以使用此处所述的信号量。这实际上有效,但我不确定这是否是处理此问题的最佳方法。
查看完整描述

3 回答

?
慕桂英546537

TA贡献1848条经验 获得超10个赞

是否有处理此类问题的良好做法?

如果您希望两个保存都发生,那么使用锁(或SemaphoreSlim)对它们进行序列化是一种方法。如果您想阻止第二次保存开始,那么通常的方法是在保存过程中禁用这些控件,例如,通过IsBusy数据绑定到您的 UI 的属性。

注意事项:

  • IsBusy同步设置您的属性。这会立即禁用控件。

  • IsBusy在 a中取消finally设置,以确保它始终未设置,即使发生错误也是如此。


查看完整回答
反对 回复 2022-10-23
?
三国纷争

TA贡献1804条经验 获得超7个赞

正如您所说,我还认为锁是最好的方法。


我推荐使用 Stepehen Cleary straigthforward 解决方案:https ://github.com/StephenCleary/AsyncEx


因此,您的代码看起来像


private readonly AsyncLock _lock = new AsyncLock();

private async void Save()

{

    var id = Guid.NewGuid();


    using (await _lock.LockAsync())

        {

            // It's safe to await while the lock is held

            Debug.WriteLine($"Starting save {id}");


            await Task.Delay(100);  // Simulate slow task


            Debug.WriteLine($"Finished save {id}");

        }

}

这并不会真正影响代码的可读性,并且您最终会得到一个异步方法队列。


查看完整回答
反对 回复 2022-10-23
?
凤凰求蛊

TA贡献1825条经验 获得超4个赞

这是 Rx 的绝佳样本。如果你不想使用ReactiveUI(它可以很容易地与 MVVMLight 相邻),你所需要的只是一个属性改变的信号。


使用 RxUI:


this.WhenAnyValue(x => x.MyText, x => x.IsChecked) // this you will need to emulate if you don't want RxUI

.Throttle(TimeSpan.FromMilliseconds(150)) // wait for 150ms after last signal, if there isn't any, send your own further into pipeline

.Synchronize()

.Do(async _ => await Save()) // we have to await so that Synchronize can work

.Subscribe();

这将在最后一次 MyText 更改或 IsChecked 更改后等待 150 毫秒,然后执行一次保存。


此外,RxUI 有非常聪明的 ICommand 实现,它支持开箱即用的异步工作,包括在工作期间禁用命令。


查看完整回答
反对 回复 2022-10-23
  • 3 回答
  • 0 关注
  • 96 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信