转换器的竞态条件

本文关键字:条件 转换器 | 更新日期: 2023-09-27 18:18:25

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

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

要实现这一点,我在ComboBox的ItemsSource上有一个转换器,如果启用了ComboBox,它将过滤掉N/A选项,否则我将包含它,以便可以根据需要填充它。我还有一个用于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. 在转换器中放入lock部分并锁定同步对象,以阻止任何双重访问/创建项目,最终进入连击。
  2. 创建一个基于单例的转换器,其中转换器负责自己的创建,并且无论哪个页面需要转换器,只有一个转换器获得完全相同的

要让转换器创建自己,我建议创建一个泛型基类,它处理转换器的单例创建并为转换器提供服务。下面的代码来自我的博客文章《Xaml:调用绑定转换器而不用在Xaml中定义StaticResource》,这要感谢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 } 
                      }">

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

/// <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)
            {}       
        }
    }
    }