转换器的竞争条件?

时间:2014-08-18 15:03:36

标签: c# wpf converter race-condition

我有一个wpf表单,它有很多动态创建的控件,例如ComboBox ES。

在每个ComboBox上,我有一些转换器来处理业务逻辑。其中一个转换器自动填充N/A并根据表单上的其他输入禁用它。但是,这应该只允许自动填充(用户不应该选择此项)。

为了实现这一点,我在ComboBox的ItemsSource上有一个转换器,如果N/A已启用,则会过滤掉ComboBox选项,否则我会将其包含在内以便填充如所须。我还有一个用于SelectedItem的转换器来自动填充N/A答案。

然而,似乎存在一种竞争条件,即转换器不会以相同的顺序一致地触发,导致空白答案(在ItemsSource转换器运行之前,自动填充selecteditem的转换器正在运行以添加答案)。

无论如何确保转换器以相同的方式执行?我在代码隐藏中创建绑定/转换器(因为控件是动态创建的),如果这会产生影响。

编辑:根据请求添加我的代码(但我的问题更多是一般性问题)。这里是我定义绑定/转换器的代码(我的xaml中有每个控件的模板,我也在这里克隆它):

            //Filter out AnswerOptions (N/A, Yes, No) based on other questions
        MultiBinding itemsSourceBinding = new MultiBinding();
        itemsSourceBinding.Bindings.Add(new Binding("ParentForm.BaselineQuestionsProperty.ImageAdequacyProperty.SingleAnswer") { Source = this });
        itemsSourceBinding.Bindings.Add(new Binding(".") { Source = containerBase });
        itemsSourceBinding.Bindings.Add(new Binding("ParentForm.BaselineQuestionsProperty.MissingTOptionProperty.IsSelected") { Source = this });
        itemsSourceBinding.Bindings.Add(new Binding("ParentForm.BaselineQuestionsProperty.MissingLOptionProperty.IsSelected") { Source = this });
        itemsSourceBinding.Bindings.Add(new Binding("AnswerOptions") { Source = containerBase });
        itemsSourceBinding.Converter = new EndplateAnswerFilterConverter();
        BindingOperations.SetBinding(comboBox, ComboBox.ItemsSourceProperty, itemsSourceBinding);

        //Binding to auto-populate Answers based on Image Adequacy Answer
        MultiBinding singleAnswerBinding = new MultiBinding();
        singleAnswerBinding.Bindings.Add(new Binding("ParentForm.BaselineQuestionsProperty.ImageAdequacyProperty.SingleAnswer") { Source = this });
        singleAnswerBinding.Bindings.Add(new Binding(".") { Source = containerBase });
        singleAnswerBinding.Bindings.Add(new Binding("ParentForm.BaselineQuestionsProperty.MissingTcOptionProperty.IsSelected") { Source = this });
        singleAnswerBinding.Bindings.Add(new Binding("ParentForm.BaselineQuestionsProperty.MissingLOptionProperty.IsSelected") { Source = this });
        singleAnswerBinding.Converter = new EndplateNotReadabletoAnswerConverter();
        BindingOperations.SetBinding(containerBase, ContainerBase.SingleAnswerProperty, singleAnswerBinding);

以下是我的转换器:

ItemsSource转换器来过滤掉答案:

    public class EndplateAnswerFilterConverter : IMultiValueConverter
{
    public object Convert(object[] values, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (values[0] is Answer && values[1] is ContainerBase && values[2] is bool && values[3] is bool && values[4] is ObservableCollection<Answer> /*targetType == typeof(Visibility)*/)
        {
            ObservableCollection<Answer> itemsSource = new ObservableCollection<Answer>();

            var answerOptions = (ObservableCollection<Answer>)values[4];
            var imageAdequacyAnswer = (Answer)values[0];
            var containerBase = (ContainerBase)values[1];
            var missingTOptionSelected = (bool)values[2];
            var missingLOptionSelected = (bool)values[3];

            //Loop through AnswerOptions
            foreach (var ans in answerOptions)
            {
                //Add N/A option if NR/Missing Images
                if (ans.Value == ((int)YesNoAnswers.NotApplicable).ToString() &&
                    (imageAdequacyAnswer.Value == ((int)ImageAdequacyAnswers.NotReadable).ToString() ||
                    (missingTOptionSelected && containerBase.Name.Contains("_T")) ||
                    (missingLOptionSelected && containerBase.Name.Contains("_L"))))
                {
                    itemsSource.Add(ans);
                }
                //Add Yes/No otherwise
                else if (ans.Value != ((int)YesNoAnswers.NotApplicable).ToString() &&
                    !(imageAdequacyAnswer.Value == ((int)ImageAdequacyAnswers.NotReadable).ToString() ||
                    (missingTOptionSelected && containerBase.Name.Contains("_T")) ||
                    (missingLOptionSelected && containerBase.Name.Contains("_L"))))
                {
                    itemsSource.Add(ans);
                }
            }

            return itemsSource;
        }
        else
        {
            return null;
        }
    }

转换器自动填充组合框(ContainerBase是一个自定义用户控件,它将ComboBox与其他属性封装在一起):

    public class EndplateNotReadabletoAnswerConverter : IMultiValueConverter
{
    public object Convert(object[] values, System.Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (values[0] is Answer && values[1] is ContainerBase && values[2] is bool && values[3] is bool /*targetType == typeof(Visibility)*/)
        {
            var imageAdequacyAnswer = (Answer)values[0];
            var containerBase = (ContainerBase)values[1];
            var missingTOptionSelected = (bool)values[2];
            var missingLOptionSelected = (bool)values[3];

            if (imageAdequacyAnswer.Value == ((int)ImageAdequacyAnswers.NotReadable).ToString() ||
                (missingTOptionSelected && containerBase.Name.Contains("_T")) ||
                (missingLOptionSelected && containerBase.Name.Contains("_L")))
            {
                return containerBase.GetAnswerOptionByValue(((int)YesNoAnswers.NotApplicable).ToString());
            }

            return null;
        }
        else
        {
            return null;
        }
    }

1 个答案:

答案 0 :(得分:0)

我在想有可能会遇到两种不同的转换器;因此创建了两个不同的数据集和明显的竞争条件。

为了解决这种竞争条件的任何可能问题,我建议完成这两个步骤;它们都可以独立完成,但建议两者一起完成。

  1. 放入lock部分的转换器内并锁定同步对象以停止任何双重访问/创建项目以最终进入组合。
  2. 创建一个基于单件的转换器,其中转换器负责自己的创建,并且只有一个转换器,无论转换器需要哪个页面完全相同的
  3. 要创建转换器,我建议创建一个通用基类来处理转换器的单例创建和转换器的服务器。我将以下代码基于我的博客文章Xaml: Call Binding Converter Without Defining StaticResource in Xaml Thanks to Markup Derived Base Class in C#

    在那篇文章中,我从未在xaml中创建静态资源转换器,但会直接在Xaml中调用转换器,如:

    <Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:converters="clr-namespace:Omega.Operation.Converters"
            ...
            >
    
    <Combo ItemsSource="{Binding ComboData, 
                           Converter={ converters:EndplateAnswerFilterConverter } 
                          }">
    

    要在单例中实现转换器的直接绑定和 one 转换器的创建,需要实现以下基类:

    /// <summary>
    /// This creates a Xaml markup which can allow converters (which inheirit form this class) to be called directly
    /// without specify a static resource in the xaml markup.
    /// </summary>
    public  class CoverterBase<T> : MarkupExtension where T : class, new()
     {
        private static T _converter = null;
    
        public CoverterBase() { }
    
        /// <summary>Create and return the static implementation of the derived converter for usage in Xaml.</summary>
        /// <returns>The static derived converter</returns>
        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return _converter ?? (_converter = (T) Activator.CreateInstance(typeof (T), null));
        }
    }
    

    最后转换器本身需要指定其基数:

      namespace Omega.Operation.Converters
        {
    
         public class EndplateAnswerFilterConverter : CoverterBase<EndplateAnswerFilterConverter>, 
                                                           System.Windows.Data.IValueConverter
        {
            public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
               lock (syncObject)
               {}
            }
    
            public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
            {
              lock (syncObject)
                {}       
            }
        }
        }