具有IDisposable的无限状态机 [英] Infinite state machine with an IDisposable
问题描述
让我们说我有一个无限状态机来生成随机的md5散列:
Lets say I have an infinite state machine to generate random md5 hashes:
public static IEnumerable<string> GetHashes()
{
using (var hash = System.Security.Cryptography.MD5.Create())
{
while (true)
yield return hash.ComputeHash(Guid.NewGuid().ToByteArray());
}
}
在上面的示例中,我使用了using
语句.是否会调用.Dispose()
方法? CQ,非托管资源会被释放吗?
In the above example I use an using
statement. Will the .Dispose()
method ever be called? CQ, will the unmanaged resources ever be freed?
例如,如果我按如下方式使用机器:
For example, if I use the machine as follows:
public static void Test()
{
int counter = 0;
var hashes = GetHashes();
foreach(var md5 in hashes)
{
Console.WriteLine(md5);
counter++;
if (counter > 10)
break;
}
}
由于hashes
变量将超出范围(并且我认为是垃圾回收),将调用dispose方法以释放System.Security.Cryptography.MD5
所使用的资源,或者这是内存泄漏吗?
Since the hashes
variable will go out of scope (and I presume garbage collected) will the dispose method be called to free the resources used by System.Security.Cryptography.MD5
or is this a memory leak?
推荐答案
让我们稍微更改一下原始代码块,将其简化为基本要点,同时还要保持足够的趣味性以进行分析.这与您发布的内容不完全相同,但是我们仍在使用迭代器的值.
Let's change your original code blocks a bit to boil it down to the essentials, while still keeping it interesting enough to analyze. This is not exactly equivalent to what you posted, but we're still using the value of the iterator.
class Disposable : IDisposable {
public void Dispose() {
Console.WriteLine("Disposed!");
}
}
IEnumerable<int> CreateEnumerable() {
int i = 0;
using (var d = new Disposable()) {
while (true) yield return ++i;
}
}
void UseEnumerable() {
foreach (int i in CreateEnumerable()) {
Console.WriteLine(i);
if (i == 10) break;
}
}
这将在打印Disposed!
幕后到底发生了什么?还有很多.让我们首先解决外层UseEnumerable
. foreach
是以下语法糖:
What actually happens under the covers? A whole lot more. Let's tackle the outer layer first, UseEnumerable
. The foreach
is syntactic sugar for the following:
var e = CreateEnumerable().GetEnumerator();
try {
while (e.MoveNext()) {
int i = e.Current;
Console.WriteLine(i);
if (i == 10) break;
}
} finally {
e.Dispose();
}
For the exact details (because even this is simplified, a little) I refer you to the C# language specification, section 8.8.4. The important bit here is that a foreach
entails an implicit call to the Dispose
of the enumerator.
接下来,CreateEnumerable
中的using
语句也是语法糖.实际上,让我们在原始语句中写出整个内容,以便稍后再进行翻译:
Next, the using
statement in CreateEnumerable
is syntactic sugar as well. In fact, let's write out the whole thing in primitive statements so we can make more sense of the translation later:
IEnumerable<int> CreateEnumerable() {
int i = 0;
Disposable d = new Disposable();
try {
repeat:
i = i + 1;
yield return i;
goto repeat;
} finally {
d.Dispose();
}
}
语言规范的第10.14节详细介绍了实现迭代器块的确切规则.它们是根据抽象操作而不是代码给出的.在 C#中,可以很好地讨论C#编译器生成什么样的代码以及每个部分做什么.在深度中,但我将给出一个仍然符合规范的简单翻译.重申一下,这并不是编译器实际上会产生的结果,但是它足以近似地说明正在发生的事情,并且省去了处理线程和优化的更多毛病.
The exact rules for implementation of iterator blocks are detailed in section 10.14 of the language specification. They're given in terms of abstract operations, not code. A good discussion on what kind of code is generated by the C# compiler and what each part does is given in C# in Depth, but I'm going to give a simple translation instead that still complies with the specification. To reiterate, this is not what the compiler will actually produce, but it's a good enough approximation to illustrate what's happening and leaves out the more hairy bits that deal with threading and optimization.
class CreateEnumerable_Enumerator : IEnumerator<int> {
// local variables are promoted to instance fields
private int i;
private Disposable d;
// implementation of Current
private int current;
public int Current => current;
object IEnumerator.Current => current;
// State machine
enum State { Before, Running, Suspended, After };
private State state = State.Before;
// Section 10.14.4.1
public bool MoveNext() {
switch (state) {
case State.Before: {
state = State.Running;
// begin iterator block
i = 0;
d = new Disposable();
i = i + 1;
// yield return occurs here
current = i;
state = State.Suspended;
return true;
}
case State.Running: return false; // can't happen
case State.Suspended: {
state = State.Running;
// goto repeat
i = i + 1;
// yield return occurs here
current = i;
state = State.Suspended;
return true;
}
case State.After: return false;
default: return false; // can't happen
}
}
// Section 10.14.4.3
public void Dispose() {
switch (state) {
case State.Before: state = State.After; break;
case State.Running: break; // unspecified
case State.Suspended: {
state = State.Running;
// finally occurs here
d.Dispose();
state = State.After;
}
break;
case State.After: return;
default: return; // can't happen
}
}
public void Reset() { throw new NotImplementedException(); }
}
class CreateEnumerable_Enumerable : IEnumerable<int> {
public IEnumerator<int> GetEnumerator() {
return new CreateEnumerable_Enumerator();
}
IEnumerator IEnumerable.GetEnumerator() {
return GetEnumerator();
}
}
IEnumerable<int> CreateEnumerable() {
return new CreateEnumerable_Enumerable();
}
这里最重要的一点是,代码块在yield return
或yield break
语句出现时被拆分,而迭代器负责在中断时记住我们在哪里".主体中的所有finally
块都将延迟到Dispose
为止.您的代码中的无限循环实际上不再是无限循环了,因为它被定期的yield return
语句打断了.请注意,因为 finally
块实际上不再是finally
块,所以在与迭代器打交道时,执行它的不确定性就降低了.这就是为什么必须使用foreach
(或确保在finally
块中调用迭代器的Dispose
方法的任何其他方式)的原因.
The essential bit here is that the code block is split up at the occurrences of a yield return
or yield break
statement, with the iterator responsible for remembering "where we were" at the time of the interruption. Any finally
blocks in the body are deferred until the Dispose
. The infinite loop in your code is really not an infinite loop anymore, because it's interrupted by periodic yield return
statements. Note that, because the finally
block isn't actually a finally
block anymore, it getting executed is a little less certain when you're dealing with iterators. This is why using foreach
(or any other way that ensures the Dispose
method of the iterator is called in a finally
block) is essential.
这是一个简化的示例;当使循环更复杂,引入异常等时,事情会变得更加有趣. 只需完成这项工作"的负担就在编译器上.
This is a simplified example; things get much more interesting when you make the loop more complex, introduce exceptions, etcetera. The burden of "just making this work" is on the compiler.
这篇关于具有IDisposable的无限状态机的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!