C# 4.0:是否有现成的、线程安全的自动实现属性?

发布于 2024-10-05 04:09:36 字数 500 浏览 0 评论 0原文

我希望对自动实现的属性进行线程安全的读写访问。我缺少 C#/.NET 框架中的此功能,即使在最新版本中也是如此。 充其量,我希望得到类似的结果

[Threadsafe]
public int? MyProperty { get; set; }

:我知道有各种代码示例可以实现此目的,但我只是想在自己实现某些内容之前确保仅使用 .NET 框架方法仍然无法实现此目的。我错了吗?

编辑:由于一些答案详细阐述了原子性,我想说,据我所知,我只是想拥有它:只要(且不超过)一个线程正在读取属性的值,不允许其他线程更改该值。因此,多线程不会引入无效值。我选择了int?类型,因为这是我目前关心的。

编辑2:我在这里找到了 Nullable 示例的具体答案,作者:Eric利珀特

I would like to have thread-safe read and write access to an auto-implemented property. I am missing this functionality from the C#/.NET framework, even in it's latest version.
At best, I would expect something like

[Threadsafe]
public int? MyProperty { get; set; }

I am aware that there are various code examples to achieve this, but I just wanted to be sure that this is still not possible using .NET framework methods only, before implementing something myself. Am I wrong?

EDIT: As some answers elaborate on atomicity, I want to state that I just want to have that, as far as I understand it: As long as (and not longer than) one thread is reading the value of the property, no other thread is allowed to change the value. So, multi-threading would not introduce invalid values. I chose the int? type because that is the on I am currently concerned about.

EDIT2: I have found the specific answer to the example with Nullable here, by Eric Lippert

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

溺ぐ爱和你が 2024-10-12 04:09:36

正确的;没有这样的设备。想必您正试图防止在另一个线程更改了该字段的一半(原子性)时读取该字段?请注意,许多(小)原语本质上是安全的,不会出现这种类型的线程问题:

5.5 变量引用的原子性

读取和写入以下数据
类型是原子的:boolcharbyte
sbyteshortushortuintint
浮点引用类型。在
枚举的加法、读写
具有基础类型的类型
之前的列表也是原子的。

但老实说,这只是线程冰山一角; 本身通常仅仅拥有线程安全属性是不够的;大多数情况下,同步块的范围必须大于一次读/写。

还有很多不同的方法可以使某些东西成为线程安全的,具体取决于访问配置文件;

  • 锁定
  • ReaderWriterLockSlim ?
  • 引用交换到某个类(本质上是一个 Box,因此在本例中是一个 Box
  • Interlocked(在所有的伪装)
  • 易失性(在某些情况下;它不是魔杖......)

(更不用说使其不可变(通过代码,或者只是选择不改变它,这通常是使其成为线程安全的最简单方法)

Correct; there is no such device. Presumably you are trying to protect against reading the field while another thread has changed half of it (atomicity)? Note that many (small) primitives are inherently safe from this type of threading issue:

5.5 Atomicity of variable references

Reads and writes of the following data
types are atomic: bool, char, byte,
sbyte, short, ushort, uint, int,
float, and reference types. In
addition, reads and writes of enum
types with an underlying type in the
previous list are also atomic.

But in all honesty this is just the tip of the threading ice-berg; by itself it usually isn't enough to just have a thread-safe property; most times the scope of a synchronized block must be more than just one read/write.

There are also so many different ways of making something thread-safe, depending on the access profile;

  • lock ?
  • ReaderWriterLockSlim ?
  • reference-swapping to some class (essentially a Box<T>, so a Box<int?> in this case)
  • Interlocked (in all the guises)
  • volatile (in some scenarios; it isn't a magic wand...)
  • etc

(not to mention making it immutable (either through code, or by just choosing not to mutate it, which is often the simplest way of making it thread-safe)

过度放纵 2024-10-12 04:09:36

我在这里回答是为了补充 Marc 的答案,他说“还有很多不同的方法可以使某些东西成为线程安全的,具体取决于访问配置文件”。

我只是想补充一点,部分原因是有很多方式线程安全,当我们说某件事是线程安全的时,我们必须明确取决于所提供的安全性。

对于几乎所有可变对象,都会有一些非线程安全的方法来处理它(注意几乎任何,都会出现异常)。考虑一个具有以下(线程安全)成员的线程安全队列;入队操作、出队操作和计数属性。通过在每个成员上进行内部锁定,或者甚至使用无锁技术,构建其中一个相对容易。

但是,假设我们像这样使用该对象:

if(queue.Count != 0)
    return queue.Dequeue();

上面的代码不是线程安全的,因为不能保证(线程安全的)Count 返回 1 之后,另一个线程不会出队并且从而导致第二次操作失败。

在很多方面它仍然是一个线程安全的对象,特别是即使在这种失败的情况下,失败的出队操作也不会将对象置于无效状态。

为了使对象在面对任何给定的操作组合时作为一个整体线程安全,我们必须使其在逻辑上不可变(可以通过线程安全操作更新内部状态作为优化来实现内部可变性 - 例如通过记忆或根据需要从数据源加载,但对于外部它必须显得不可变)或者严重减少可能的外部操作的数量(我们可以创建一个只有 Enqueue的线程安全队列TryDequeue 始终是线程安全的,但既减少了可能的操作,也强制将失败的出队重新定义为不是失败,并强制更改我们之前版本的调用代码的逻辑) 。

其他任何事情都是部分保证。我们免费获得了一些部分保证(正如 Marc 指出的那样,就单个原子而言,作用于某些自动属性已经是线程安全的了——在某些情况下,这就是我们所需要的所有线程安全性,但在其他情况下却无济于事)近得足够远)。

让我们考虑一个属性,该属性将这种部分保证添加到我们尚未获得的情况下。它对我们到底有多大价值?嗯,在某些情况下它会是完美的,但在其他情况下却不会。回到出队前的测试案例,对 Count 进行这样的保证并没有多大用处 - 我们有这样的保证,但代码在多线程条件下仍然会失败,而它不会以某种方式失败在单线程条件下。

更重要的是,将这种保证添加到尚不具备这种保证的情况下至少需要一定程度的开销。一直担心开销可能是不成熟的优化,但增加开销却没有收益是过早的悲观情绪,所以我们不要这样做!更重要的是,如果我们确实提供更广泛的并发控制来使一组操作真正线程安全,那么我们将使更窄的并发控制变得无关紧要,并且它们变成纯粹的开销 - 所以我们甚至无法从我们的操作中获得价值某些情况下的开销;这几乎总是纯粹的浪费。

还不清楚并发问题的范围有多大。我们是否需要仅锁定(或类似)该属性,还是需要锁定所有属性?我们是否还需要锁定非自动操作,这可能吗?

这里没有好的单一答案(在滚动您自己的解决方案时,它们可能是棘手的问题,更不用说尝试在其他人使用此 [Threadsafe] 属性时生成此类代码的代码中回答它)。

此外,任何给定的方法都会有一组不同的条件,在这些条件下可能会发生死锁、活锁和类似问题,因此我们实际上可以通过将线程安全视为我们可以盲目应用于属性的东西来降低线程安全性。

如果无法找到这些问题的单一通用答案,就没有提供单一通用实现的好方法,并且任何此类 [Threadsafe] 属性充其量也将具有非常有限的价值。最后,在使用它的程序员的心理层面上,很可能会导致一种错误的安全感,即他们创建了一个线程安全的类,而实际上他们没有创建这个类;这实际上会比无用更糟糕。

I'm answering here to add to Marc's answer, where he says "there are also so many different ways of making something thread-safe, depending on the access profile".

I just want to add, that part of the reason for this, is that there are so many ways of not being thread-safe, that when we say something is thread-safe, we have to be clear on just what safety is provided.

With almost any mutable object, there will be ways to deal with it that are not thread-safe (note almost any, an exception is coming up). Consider a thread-safe queue that has the following (thread-safe) members; an enqueue operation, a dequeue operation and a count property. It's relatively easy to construct one of these either through locking internally on each member, or even with lock-free techniques.

However, say we used the object like so:

if(queue.Count != 0)
    return queue.Dequeue();

The above code is not thread-safe, because there is no guarantee that after the (thread-safe) Count returning 1, another thread won't dequeue and hence cause the second operation to fail.

It is still a thread-safe object in many ways, particularly as even in this case of failure, the failing dequeue operation will not put the object into an invalid state.

To make an object as a whole thread-safe in the face of any given combination of operations we have to either make it logically immutable (it's possible to have internal mutability with thread-safe operations updating internal state as an optimisation - e.g. through memoisation or loading from a datasource as needed, but to the outside it must appear immutable) or to severely reduce the number of external operations possible (we could create a thread-safe queue that only had Enqueue and TryDequeue which is always thread-safe but that both reduces the operations possible, and also forces a failed dequeue to be redefined as not being a failure, and forces a change in logic on calling code from the version we had earlier).

Anything else is a partial guarantee. We get some partial guarantees for free (as Marc notes, acting on some automatic properties are already thread-safe in regards to being individually atomic - which in some cases is all the thread safety we need, but in other cases doesn't go anywhere near far enough).

Let's consider an attribute that adds this partial guarantee to those cases where we don't already get it. Just how much value is it to us? Well, in some cases it will be perfect, but in others it won't. Going back to our case of testing before dequeue, having such a guarantee on Count isn't much use - we had that guarantee and the code still failed in multi-threaded conditions in a way it wouldn't in single-threaded conditions.

What's more, adding this guarantee to the cases that don't already have it requires at least a degree of overhead. It may be premature optimisation to worry about overhead all the time, but adding overhead for no gain is premature pessimisation, so lets not do that! What's more, if we do provide the wider concurrency control to make a set of operations truly thread-safe, then we will have rendered the narrower concurrency controls irrelevant, and they become pure overhead - so we don't even get value out of our overhead in some cases; it's almost always purely waste.

It's also not clear how wide or narrow the concurrency concerns are. Do we need to lock (or similar) only on that property, or do we need to lock on all properties? Do we need to lock also on non-automatic operations, and is that even possible?

There is no good single answer here (they can be tricky questions to answer in rolling your own solution, never mind in trying to answer it in the code that would produce such code when someone else has used this [Threadsafe] attribute).

Also, any given approach will have a different set of conditions in which deadlock, livelock, and similar problems can occur, so we can actually reduce thread-safety by treating thread-safety as something we can just blindly apply to a property.

Without being able to find a single universal answer to those questions, there is no good way of providing a single universal implementation, and any such [Threadsafe] attribute would be of very limited value at best. Finally, at the psychological level of the programmer using it, it is very likely to lead to a false sense of security that they have created a thread-safe class when in fact they have not; which would make it actually worse than useless.

只有一腔孤勇 2024-10-12 04:09:36

不,不可能。这里没有免费的午餐。当您的自动属性需要更多提示(线程安全,INotifyPropertyChanged)时,您就需要手动完成 - 没有自动属性魔法。

No, not possible. No free lunch here. The moment your autoproperties need even a tip more (thread safety, INotifyPropertyChanged) it is down to do yourself manually - no automatic properties magic.

我喜欢麦丽素 2024-10-12 04:09:36

根据 C# 4.0 规范,此行为保持不变:

第 10.7.3 节 自动实现的属性

当属性被指定为自动实现的属性时,隐藏的支持字段将自动可供该属性使用,并且访问器被实现来读取和写入该支持字段。

以下示例:

 public class Point {
    public int X { get; set; } // automatically implemented
    public int Y { get; set; } // automatically implemented
}

相当于以下声明:

public class Point {
    private int x;
    private int y;
    public int X { get { return x; } set { x = value; } }
    public int Y { get { return y; } set { y = value; } }
}

According to the C# 4.0 spec this behavior is unchanged:

Section 10.7.3 Automatically implemented properties

When a property is specified as an automatically implemented property, a hidden backing field is automatically available for the property, and the accessors are implemented to read from and write to that backing field.

The following example:

 public class Point {
    public int X { get; set; } // automatically implemented
    public int Y { get; set; } // automatically implemented
}

is equivalent to the following declaration:

public class Point {
    private int x;
    private int y;
    public int X { get { return x; } set { x = value; } }
    public int Y { get { return y; } set { y = value; } }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文