简体   繁体   English

带锁事件处理的最有效设计模式

[英]Most efficient design pattern for event handling with locking

I have an application that receives events asynchronously from an API and can call methods synchronously on this API. 我有一个应用程序,该应用程序从API异步接收事件,并且可以在此API上同步调用方法。

For thread-safety purposes, I need each synchronous function and each event handler in my application to be locked. 为了线程安全,我需要锁定应用程序中的每个同步函数和每个事件处理程序。

However, calling an API method synchronously may lead the API to raise events on a different thread and wait for them to be processed before returning. 但是,同步调用API方法可能会导致API在其他线程上引发事件,并在返回之前等待事件被处理。

This could therefore result in a deadlock as the API would wait for an event to be processed to continue, but in my class the synchronization object would be hit by two different threads and the program would hang. 因此,这可能会导致死锁,因为API将等待事件的继续处理,但是在我的类中,同步对象将被两个不同的线程击中,并且程序将挂起。

My current idea is, instead of locking event handlers, to try locking and if not possible (eg if the event results from a synchronous call on another thread) to buffer the event in a queue/message pump. 我当前的想法是尝试锁定而不是锁定事件处理程序,并且在不可能的情况下(例如,如果事件是由另一个线程上的同步调用导致的)将事件缓冲在队列/消息泵中。

Right before releasing the lock on the synchronous function call, I would then call a ProcessPendingEvents() function so that events could be processed without deadlock. 在释放对同步函数调用的锁定之前,然后我将调用ProcessPendingEvents()函数,以便可以处理事件而不会出现死锁。

Do you have any design pattern in mind you would recommend for this kind of situation? 对于这种情况,您有没有建议的设计模式? I am open to anything. 我对任何事情都开放。

Here is a simple example to illustrate my current tentative implementation. 这是一个简单的示例来说明我当前的暂定实现。 I really aim at having a class that would behave in a single-threaded way as much as possible: 我的真正目的是使类尽可能地以单线程方式运行:

class APIAdapter {
    readonly object AdapterLock = new object();
    private readonly ConcurrentQueue<Tuple<object, EventArgs>> PendingEvents = new ConcurrentQueue<Tuple<object, EventArgs>>();
    ExternalAPI API = new ExternalAPI();

    APIAdapter() {
        ExternalAPI.Data += ExternalAPI_Data;
    }

    public void RequestData() {
        lock (this.AdapterLock) {
            this.ExternalAPI.SynchronousDataRequest(); //Will cause the API to raise the Data event would therefore deadlock if I had a simple lock() in ExternalAPI_Data.
            this.ProcessPendingEvents();
        }
    }

    private void ExternalAPI_Data(object sender, EventArgs e) {
        if (!Monitor.TryEnter(this.AdapterLock)) {
            this.PendingEvents.Enqueue(Tuple.Create(sender, e));
            return;
        }
        Console.Write("Received event.");
        Monitor.Exit(this.AdapterLock);
    }

    private void ProcessPendingEvents() {
        Tuple<object, EventArgs> ev;
        while (this.PendingEvents.TryDequeue(out ev)) {
            ExternalAPI_Data(ev.Item1, ev.Item2);
        }
    }
}

My initial solution was not satisfying: after ProcessPendingEvents() was completed, but before the lock was released, other events could be bufferred and not raised until the next call to ProcessPendingEvents() . 我最初的解决方案并不令人满意: after ProcessPendingEvents()完成之后,但在释放锁之前,可以缓冲其他事件,直到下一次调用ProcessPendingEvents()时才引发其他事件。

Events could also be bufferred at any time if the API sent back two events on different threads, and I was crucially lacking a way to consume these events as soon as the lock were released. 如果API在不同的线程上发送回两个事件,则事件也可以在任何时候进行缓冲,而我非常缺乏一种在释放锁后立即消耗这些事件的方法。

I ended up implementing of much cleaner producer/consumer pattern to control when API events are to be processed, using BlockingCollection . 我最终使用BlockingCollection实现了更为简洁的生产者/消费者模式,以控制何时处理API事件。 Below is the corresponding code for those who are interested: 以下是有兴趣者的相应代码:

class APIAdapter {
    readonly object AdapterLock = new object();
    private readonly BlockingCollection<Tuple<object, EventArgs>> PendingEvents = new BlockingCollection<Tuple<object, EventArgs>>();
    ExternalAPI API = new ExternalAPI();

    APIAdapter() {
        ExternalAPI.Data += ExternalAPI_Data;
        Task.Factory.StartNew(Consume, TaskCreationOptions.LongRunning);
    }

    public void Consume() {
        foreach (var e in this.PendingEvents.GetConsumingEnumerable()) {
            if (this.PendingEvents.IsAddingCompleted) return;
            ProcessData(e.Item1, e.Item2);
        }
    }

    public void RequestData() {
        lock (this.AdapterLock) {
            this.ExternalAPI.SynchronousDataRequest(); 
        }
    }

    private void ExternalAPI_Data(object sender, EventArgs e) {
        this.PendingEvents.Add(Tuple.Create(sender, e));
    }

    private void ProcessData(object sender, EventArgs e) {
        lock (this.AdapterLock) {
            Console.Write("Received event.");
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM