C# 中的线程安全可破坏事件触发类

发布于 2024-09-30 00:42:21 字数 2620 浏览 1 评论 0原文

最近,我被要求实施一个课程作为选拔过程的一部分。我按照要求做了程序。然而,我考试失败了。我真的很想知道我的解决方案出了什么问题。非常感谢任何帮助。问题和我的解决方案如下

实现一个线程安全类,该类从构造开始每秒触发一个事件。需要有一个函数来查找经过的秒数。此类必须实现 IDisposable,并且在调用 dispose 后对秒数经过函数的任何调用都应该失败。

我的解决方案:

namespace TimeCounter
{
public delegate void SecondsElapsedHandler(object o, EventArgs e);
/// <summary>
/// Summary description for SecondCounter
/// </summary>
public class SecondCounter : IDisposable
{
    private volatile int nSecondsElapsed;
    Timer myTimer;
    private readonly object EventLock = new object();
    private SecondsElapsedHandler secondsHandler;
    public SecondCounter()
    {
        nSecondsElapsed = 0;
        myTimer = new Timer();
        myTimer.Elapsed += new ElapsedEventHandler(OneSecondElapsed);
        myTimer.Interval = 1000;
        myTimer.AutoReset = false;
        myTimer.Start();
    }

    public void OneSecondElapsed(object source, ElapsedEventArgs e)
    {
        try
        {
            SecondsElapsedHandler handlerCopy;
            lock (EventLock)
            {
                handlerCopy = secondsHandler;
                nSecondsElapsed++;

            }
            if (secondsHandler != null)
            {
                secondsHandler(this, e);
            }
        }
        catch (Exception exp)
        {
            Console.WriteLine("Exception thrown from SecondCounter OneSecondElapsed " + exp.Message);
        }
        finally
        {
            if (myTimer != null)
            {
                myTimer.Enabled = true;
            }
        }
    }

    public event SecondsElapsedHandler AnotherSecondElapsed
    {
        add
        {
            lock (EventLock)
            {
                secondsHandler += value;
            }
        }
        remove
        {
            lock (EventLock)
            {
                secondsHandler -= value;
            }

        }
    }

    public int SecondsElapsed()
    {
        if (this.IsDisposed)
        {
            throw new ObjectDisposedException("SecondCounter");
        }
        return nSecondsElapsed;

    }

    private bool IsDisposed = false;
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    private void Dispose(bool Disposing)
    {
        if (!IsDisposed)
        {
            if (Disposing)
            {

            }
            if (myTimer != null)
            {
                myTimer.Dispose();
            }

        }
        secondsHandler = null;
        IsDisposed = true;

    }
    ~SecondCounter()
    {
        Dispose(false);
    }
}
}

Recently, I was asked to implement a class as part of a selection process. I did the program as requested. However, I failed in the test. I am really curious to know what is wrong in my solution. Any help is much appreciated. The question and my solution are given below

Question:

Implement a thread safe class which fires an event every second from construction. There need to be a function for finding the seconds elapsed. This class has to implement IDisposable and any calls to seconds elapsed function after calling dispose should fail.

My solution:

namespace TimeCounter
{
public delegate void SecondsElapsedHandler(object o, EventArgs e);
/// <summary>
/// Summary description for SecondCounter
/// </summary>
public class SecondCounter : IDisposable
{
    private volatile int nSecondsElapsed;
    Timer myTimer;
    private readonly object EventLock = new object();
    private SecondsElapsedHandler secondsHandler;
    public SecondCounter()
    {
        nSecondsElapsed = 0;
        myTimer = new Timer();
        myTimer.Elapsed += new ElapsedEventHandler(OneSecondElapsed);
        myTimer.Interval = 1000;
        myTimer.AutoReset = false;
        myTimer.Start();
    }

    public void OneSecondElapsed(object source, ElapsedEventArgs e)
    {
        try
        {
            SecondsElapsedHandler handlerCopy;
            lock (EventLock)
            {
                handlerCopy = secondsHandler;
                nSecondsElapsed++;

            }
            if (secondsHandler != null)
            {
                secondsHandler(this, e);
            }
        }
        catch (Exception exp)
        {
            Console.WriteLine("Exception thrown from SecondCounter OneSecondElapsed " + exp.Message);
        }
        finally
        {
            if (myTimer != null)
            {
                myTimer.Enabled = true;
            }
        }
    }

    public event SecondsElapsedHandler AnotherSecondElapsed
    {
        add
        {
            lock (EventLock)
            {
                secondsHandler += value;
            }
        }
        remove
        {
            lock (EventLock)
            {
                secondsHandler -= value;
            }

        }
    }

    public int SecondsElapsed()
    {
        if (this.IsDisposed)
        {
            throw new ObjectDisposedException("SecondCounter");
        }
        return nSecondsElapsed;

    }

    private bool IsDisposed = false;
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
    private void Dispose(bool Disposing)
    {
        if (!IsDisposed)
        {
            if (Disposing)
            {

            }
            if (myTimer != null)
            {
                myTimer.Dispose();
            }

        }
        secondsHandler = null;
        IsDisposed = true;

    }
    ~SecondCounter()
    {
        Dispose(false);
    }
}
}

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

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

发布评论

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

评论(2

小糖芽 2024-10-07 00:42:21

存在一些问题:

  1. 您可能会因一般的异常吞咽而受到惩罚,尽管这与线程问题没有具体相关。

  2. 您的计时器上存在竞争条件。处置,因为您可以在再次设置为启用之前处置计时器,从而导致异常。

  3. 您从未在 Dispose 中将 myTimer 设置为 null。

  4. 您正在从终结器 (disusing=false) 访问托管类 myTimer,这是一个坏主意。

  5. 没有必要显式地实现带有锁定的事件。委托是不可变的,添加/删除事件永远不会导致无效的委托状态,但如果在回调触发的同时添加/删除委托,则可能会出现竞争条件。如果您使用标准的“公共事件”声明,而没有显式支持私有委托,则将自动处理同步。

  6. (小点)如果您要实现完整的 Dispose 模式,通常会将 Dispose(bool dispose) 方法标记为 protected virtual,以便派生类可以挂钩到处置机制。更好的是,将您的类标记为sealed,这样您就可以完全消除终结器。

There are a few problems:

  1. You might have been penalized for general Exception swallowing though that's not specifically related to threading issues.

  2. There's a race condition on your timer.Dispose, as you could Dispose the timer before it is set Enabled again, resulting in an Exception.

  3. You never set myTimer to null in Dispose.

  4. You're accessing the managed class myTimer from the finalizer (disposing=false), which is a bad idea.

  5. The explicit implementation of the event with locking is unnecessary. Delegates are immutable and adding/removing an event will never result in an invalid delegate state, though there can be race conditions if delegates are added/removed around the same time that the callback is fired. If you use the standard 'public event' declaration without an explicit backing private delegate, the synchronization will be handled automatically.

  6. (minor point) If you're implementing the full Dispose pattern, it's customary to mark the Dispose(bool disposing) method as protected virtual, so that deriving classes can hook into the disposal mechanism. Better yet, mark your class sealed and you can eliminate the finalizer entirely.

不知在何时 2024-10-07 00:42:21

您的终结器可能已损坏。它正确地将 false 作为 Dispose 参数传递。这应该告诉 Dispose(bool) 避免尝试处置其他托管对象。但在该方法中,您输入:

if (Disposing)
{

}
if (myTimer != null)
{
    myTimer.Dispose();
}

因此,您忽略了 Diswriting 的值。这意味着当该对象可能已经被终结时(如果它有终结器,它可能会这样做),您可以从终结器线程调用计时器的 Dispose 方法。终结器以不可预测的顺序运行。通常建议不要从终结器调用其他 GC 管理的对象。

事实上,现在通常建议您不要编写终结器。问题没有要求你写一个!不幸的是,大多数关于 IDisposable 的教程也讨论了终结器。他们是不同的主题。

您还可以捕获通用异常基类Exception。这意味着您会捕获诸如 NullReferenceException 之类的内容。通常不是一个好主意。您还可以登录控制台,这在 GUI 或基于服务器的应用程序中没有多大价值。

您可以将: 替换

myTimer.Elapsed += new ElapsedEventHandler(OneSecondElapsed);

为:

myTimer.Elapsed += OneSecondElapsed;

您的变量命名不一致。请参阅Microsoft 指南

Your finalizer is probably broken. It correctly passes false as the Disposing parameter. This should tell Dispose(bool) to avoid attempting to dispose other managed objects. But in that method you put:

if (Disposing)
{

}
if (myTimer != null)
{
    myTimer.Dispose();
}

So you ignore the value of Disposing. This means that you call the timer's Dispose method from the finalizer thread, when that object may already have been finalized (if it has a finalizer, which it probably does). Finalizers run in an unpredictable order. It's generally recommended to not make calls to other GC-managed objects from a finalizer.

In fact, it's usually recommended that you don't write finalizers at all these days. The question didn't ask you to write one! It's unfortunate that most tutorials about IDisposable also talk about finalizers. They're different subjects.

You also catch Exception, the universal exception base class. This means you catch things like NullReferenceException. Not usually a good idea. You also log to the console, which is not worth much in a GUI or server-based application.

You can replace:

myTimer.Elapsed += new ElapsedEventHandler(OneSecondElapsed);

with:

myTimer.Elapsed += OneSecondElapsed;

Your variable naming is inconsistent. Refer to the Microsoft guidelines.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文