C# 线程安全的扩展方法

3

我可能完全错了,也可能非常接近。无论如何,我现在很无助。:)

我想使用扩展方法来设置一个类的属性,但这个类可能(也可能不)在非UI线程上更新,并且继承自一个强制更新在UI线程上的类(该类实现了INotifyPropertyChanged等)。

我定义了一个类,类似于以下内容:

public class ClassToUpdate : UIObservableItem
{
    private readonly Dispatcher mDispatcher = Dispatcher.CurrentDispatcher;
    private Boolean mPropertyToUpdate = false;

    public ClassToUpdate() : base()
    {
    }

    public Dispatcher Dispatcher
    {
        get { return mDispatcher; }
    }

    public Boolean PropertyToUpdate
    {
        get { return mPropertyToUpdate; }
        set { SetValue("PropertyToUpdate", ref mPropertyToUpdate, value; }
    }
}

我有一个扩展方法类,类定义如下:

static class ExtensionMethods
{
    public static IEnumerable<T> SetMyProperty<T>(this IEnumerable<T> sourceList,
                                                  Boolean newValue)
    {
       ClassToUpdate firstClass = sourceList.FirstOrDefault() as ClassToUpdate;

       if (firstClass.Dispatcher.Thread.ManagedThreadId != 
           System.Threading.Thread.CurrentThread.ManagedThreadId)
        {
            // WHAT GOES HERE?
        }
        else
        {
            foreach (var classToUpdate in sourceList)
            {
               (classToUpdate as ClassToUpdate ).PropertyToUpdate = newValue;
               yield return classToUpdate;
            }
        }
    }
}

显然,我正在寻找扩展方法中的“WHAT GOES HERE”。

谢谢, wTs


1
你有一个名为'SetMyProperty'的枚举器吗?也许你应该先决定这个函数实际上应该做什么? - H H
@Henk:其实,我认为这是一种流畅的 API:sourceList.SetMyProperty(true).SetMyOtherProperty("hello").SetFoo(42)... - Thomas Levesque
扩展方法本质上只是静态方法,它们知道如何“装饰”值(此操作完全在编译时完成)。它们的线程安全性与它们所做的事情以及上下文有关。但是,除了线程处理方面,它们并没有什么特别之处。我通常喜欢使用SynchronizationContext和dispatchers。 - user166390
@Thomas:你说得对。这实际上是一个简化的方法,用作代表更复杂方法的示例。 - Wonko the Sane
2个回答

1

// 这里应该放什么?

mDispatcher.Invoke(new Action(() => sourceList.SetMyProperty(newValue)));

作为旁注,如果您需要检查当前线程是否有访问UI的权限,您不需要比较线程ID。您只需调用CheckAccess方法即可:
if (firstClass.Dispatcher.CheckAccess())
{
    ...
}

由于某些原因,这个方法在Intellisense中被隐藏了...不知道为什么


更新

好的,我的回答并不完全准确...你仍然需要yield return集合中的每个项,而Invoke不会这样做。这是你的方法的另一个版本:

public static IEnumerable<T> SetMyProperty<T>(this IEnumerable<T> sourceList, bool newValue)
    where T : ClassToUpdate
{
    Action<T> setProperty = t => t.PropertyToUpdate = newValue;

    foreach(var t in sourceList)
    {
        if (t.Dispatcher.CheckAccess())
        {
            action(t);
        }
        else
        {
            t.Dispatcher.Invoke(action, new object[] { t });
        }
    }
}

请注意,我在泛型类型参数上添加了一个约束,并且删除了强制转换(以你之前的方式,泛型没有带来任何好处)。

我不确定这是否有效。由于调用Invoke后,断点和跟踪点没有命中函数,我期望在调用Invoke后的第一行函数处命中断点。 - Wonko the Sane
另外,Invoke方法是否与通过yield return创建IEnumerable的其他循环具有相同的效果?从Invoke返回的对象为null。 - Wonko the Sane
谢谢!我能够使用你的示例解决了问题。哦,我还有一个限制是我的真实类 - 我只是没有在上面的快速示例中放入它。 - Wonko the Sane

0

仅仅是为了修正上面例子中出现的一些小错误(并且希望不会增加我的错误),这里提供一个最终的解决方案。

public static IEnumerable<T> SetMyProperty<T>(this IEnumerable<T> sourceList, 
    bool newValue) where T : ClassToUpdate
{
    Action<T> setProperty = t => t.PropertyToUpdate = newValue;

    foreach(var t in sourceList)
    {
        if (t.Dispatcher.CheckAccess())
        {
            setProperty(t);
        }
        else
        {
            t.Dispatcher.Invoke(setProperty, new object[] { t });
        }

        yield return t;
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接