【发布时间】: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)); }我给了nameoftrue 属性作为调用者成员名称。 -
不确定这是否可以做得很漂亮。是否所有属性都具有
int类型?可以通过ref传递数组条目,如Interlocked.Increment(ref _backingArrayAllProperties[_indexForTextProgress]);。对于支持Dictionary<,>或类似的东西,同样不能做到这一点。但是你可以有一个永远不变的Dictionary<,>来从属性名称映射到索引号到数组。或者类似public static readonly IList<string> _names = Array.AsReadOnly(new[] { nameof(TextProgress), nameof(Xxx), ... });,然后是var indexForPropName = _names.IndexOf(propName);(搜索 O(n))。
标签: c# wpf thread-safety