具有IDisposable的无限状态机 [英] Infinite state machine with an IDisposable

查看:98
本文介绍了具有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 returnyield 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆