【问题标题】:Suspended event handling with C#使用 C# 处理暂停事件
【发布时间】:2015-08-15 08:01:31
【问题描述】:

我有 2 节课:

public class A
{
    private const int MAXCOUNTER = 100500;
    private Thread m_thrd;
    public event Action<string> ItemStarted;
    public event Action<string> ItemFinished;

    private void OnItemStarted(string name)
    {
        if (ItemStarted != null) ItemStarted(name);
    }

    private void OnItemFinished(string name)
    {
        if (ItemFinished != null) ItemFinished(name);
    }

    public A()
    {
        m_thrd = new Thread(this.Run);
        m_thrd.Start();
    }

    private void Run()
    {
        for (int i = 0; i < MAXCOUNTER; i++)
        {
            OnItemStarted(i.ToString());
            // some long term operations
            OnItemFinished(i.ToString());
        }
    }
}

public class B
{
    private Thread m_thrd;
    private Queue<string> m_data;
    public B()
    {
        m_thrd = new Thread(this.ProcessData);
        m_thrd.Start();
    }

    public void ItemStartedHandler(string str)
    {
        m_data.Enqueue(str);
    }

    public void ItemFinishedHandler(string str)
    {
        if (m_data.Dequeue() != str)
            throw new Exception("dequeued element is not the same as finish one!");
    }

    private void ProcessData()
    {
        lock (m_data)
        {
            while (m_data.Count != 0)
            {
                var item = m_data.Peek();
                //make some long term operations on the item
            }
        }
    }
}

我们在代码中还有其他地方

A a = new A();
B b = new B();
a.ItemStarted += b.ItemStartedHandler;
a.ItemFinished += b.ItemFinishedHandler;
  • 那么,如果在 ProcessData() 仍在工作时引发了 ItemFinished,会发生什么?
  • 我应该使用AutoResetEvent 之类的东西来让A 等课程B 完成ProcessData
  • lock必须在ProcessData中使用吗?
  • 可以用m_thrd = new Thread(this.ProcessData); 调用B 类的线程吗?这件事让我感到困惑 - 在引发任何 ItemStarted 事件之前,ProcessData 不会完成(当ItemStarted 第一次生成时,B 中的线程已经完成,这不会导致这种情况吗)?

【问题讨论】:

    标签: c# multithreading locking semaphore autoresetevent


    【解决方案1】:

    你的代码有点乱。

    如果在ProcessData() 完成之前引发ItemFinished,您的第一个问题是竞争条件。

    不用担心使用AutoResetEvent。简单的事情是锁定对m_data 的所有访问。所以这就引出了下一个问题——是的,锁是必要的,而且在任何地方都是必要的。

    但你的最后一点是最重要的。您需要将每个构造函数更改为 .Start() 方法,以便在开始之前有时间进行连接。

    但是,即便如此,你也有一个巨大的竞争条件。

    要完成这项工作,您应该执行以下操作。 NuGet "Rx-Main" 将 Microsoft 的响应式框架添加到您的代码中。然后这样做:

    var scheduler1 = new EventLoopScheduler();
    var scheduler2 = new EventLoopScheduler();
    
    Observable
        .Range(0, 100500)
        .ObserveOn(scheduler1)
        .Do(x => { /* some long term operations */ })
        .ObserveOn(scheduler2)
        .Subscribe(x =>
        {
            //make some long term operations on the item
        });
    

    工作完成。

    【讨论】:

    • 感谢您的回复。但我必须在这里明确使用线程。
    • 这确实使用线程。每个事件循环调度程序使用单个线程进行调度。
    【解决方案2】:

    您需要纠正代码中的几个线程问题:

    1. 您正在启动线程后连接事件。您应该在启动线程之前执行此操作。因此,对于这两个类,将构造函数中的任何内容放入 Public 实例函数中。现在创建类的实例,连接事件,然后先调用 B 类的实例方法,然后再调用 A 类来启动线程。
    2. 您正在从两个不同的线程访问 B 类中的队列。队列不是线程安全的,因此您应该使用 ConcurrentQueue (https://msdn.microsoft.com/en-us/library/dd267265(v=vs.110).aspx) 和相应的 Try... 函数。您不需要使用 ConcurrentQueue 进行任何类型的显式锁定。
    3. 您使用 B 类 ProcessData 函数的意图不是很清楚。您想为队列中的每个项目调用一次 ProcessData 还是要继续调用 ProcessData 直到项目未出列?如果是前者,那么您应该使用 BlockingCollection 和 GetConsumingEnumerable,而不是使用 Queue。详情请参阅:https://msdn.microsoft.com/en-us/library/dd287186(v=vs.110).aspx。如果这是你想要的,我可以发布一个样本。如果您使用 BlockingCollection,则不会出现 ProcessData 比 A 类线程更早完成的情况。

    【讨论】:

    • 我必须在这里更具体一点:我有一个类来选择有关目录和其中文件的信息(递归地遍历整个树)和 2 个类来将数据存储在文件中并在 UI 中显示。一般的想法是将文件夹名称放在堆栈中(它在 cl.A 的同一线程中处理)和队列中的文件信息(cl.B)。当目录中的所有文件都在队列中时,A 挂起,直到 B 在单独的线程中处理它们。我正在使用 CountdownEvent(因为有 2 个额外的线程 - 比如说 B 的实例)和 Thread.Join() 出于初始要求的同步目的 - 不使用 TPL、并发 coll 等。
    猜你喜欢
    • 2011-08-30
    • 1970-01-01
    • 1970-01-01
    • 2021-06-15
    • 1970-01-01
    • 1970-01-01
    • 2021-03-08
    • 2014-09-29
    • 1970-01-01
    相关资源
    最近更新 更多