【问题标题】:Properties and Thread Safety属性和线程安全
【发布时间】:2018-01-31 15:43:14
【问题描述】:

由于线程安全,在下面的代码中,TextProgress 最终不等于TextMax。如果我为“_ViewModel.TextProgress++”锁定_ViewModel,这将纠正这种行为。

using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;

namespace Testing
{
    public partial class MainWindow : Window
    {
        ResultsItemViewModel _ViewModel = new ResultsItemViewModel();
        public MainWindow()
        {
            InitializeComponent();
            DataContext = _ViewModel;
            _ViewModel.TextMax += 10000;
            _ViewModel.TextMax += 10000;
        }

        private int MAXVAL = 10000;
        private void Method1(
            Action<int> reportProgress = null)
        {
            var progress = 0;

            for (int i = 0; i < 10000; i++)
            {
                if (reportProgress != null)
                    reportProgress.Invoke(++progress);
                else
                {
                    _ViewModel.TextProgress++;
                }
            }
        }
        private void Method2(
            Action<int> reportProgress = null)
        {
            var progress = 0;

            for (int i = 0; i < 10000; i++)
            {
                if(reportProgress != null)
                    reportProgress.Invoke(++progress);
                else
                {
                    _ViewModel.TextProgress++;
                }
            }
        }
        private async Task TextProcessing()
        {
            await Task.WhenAll(
                Task.Run(() => Method1()),
                Task.Run(() => Method2()));
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            _ViewModel.TextProgress = 0;
            await TextProcessing();
            lblResult.Content = _ViewModel.TextProgress + "/" + _ViewModel.TextMax;
        }
    }
    public class ResultsItemViewModel : INotifyPropertyChanged
    {
        int _textProgress, _textMax;
        public int TextProgress
        {
            get => _textProgress;
            set
            {
                _textProgress = value;
                NotifyPropertyChanged();
            }
        }

        public int TextMax
        {
            get => _textMax;
            set
            {
                _textMax = value;
                NotifyPropertyChanged();
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var handler = PropertyChanged;
            handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

这行得通;

lock (_ViewModel)
{
    _ViewModel.TextProgress++;
}

但是,我有很多需要以这种方式更新的属性,所以我不想锁定整个_ViewModel,但同时我不想为每个属性创建一个类财产。理想情况下我可以做到;

lock (_ViewModel.TextProgress)
{
    _ViewModel.TextProgress++;
}

但这显然是不可能的。

【问题讨论】:

  • 不确定我是否理解最后一句话。你不想做什么?在ResultsItemViewModel 类中,您可以创建一个方法IncrementTextProgress,它可以调用Interlocked.Increment(ref _textProgress),但我不确定这是否是您所追求的。
  • @JeppeStigNielsen 道歉,更正了措辞并添加了更清晰的细节。希望这是有道理的。我现在将尝试增量的想法。
  • @JeppeStigNielsen 所以这个解决方案似乎可以正确更新值,但不会触发我的NotifyPropertyChanged()。我需要这个,因为我有一个 ProgressBar 绑定到 TextProgress 属性。
  • 好的。在视图模型类中可以有一个public static readonly object TextProgressLock = new object();,人们可以使用lock。或者,根据我第一条评论的想法,只需在此处致电NotifyPropertyChanged。例如:public void IncrementTextProgress() { Interlocked.Increment(ref _textProgress); NotifyPropertyChanged(nameof(TextProgress)); } 我给了nameof true 属性作为调用者成员名称。
  • 不确定这是否可以做得很漂亮。是否所有属性都具有int 类型?可以通过ref 传递数组条目,如Interlocked.Increment(ref _backingArrayAllProperties[_indexForTextProgress]);。对于支持Dictionary&lt;,&gt; 或类似的东西,同样不能做到这一点。但是你可以有一个永远不变的Dictionary&lt;,&gt; 来从属性名称映射到索引号到数组。或者类似public static readonly IList&lt;string&gt; _names = Array.AsReadOnly(new[] { nameof(TextProgress), nameof(Xxx), ... });,然后是var indexForPropName = _names.IndexOf(propName);(搜索 O(n))。

标签: c# wpf thread-safety


【解决方案1】:

如果您想要线程安全,您需要同步对共享资源 (TextProgress) 的访问,例如使用锁,或者您需要确保共享资源只能从单个访问线。您无能为力。

作为使用lock 语句的替代方法,您可以使用Interlocked.Increment 方法来增加值并将结果存储为原子操作。

但恐怕这里没有“我确实想要多线程和线程安全但我不想同步”选项。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-17
    • 1970-01-01
    相关资源
    最近更新 更多