I have an application that receives events asynchronously from an API and can call methods synchronously on this 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.
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.
My current idea is, instead of locking event handlers, to try locking and if not possible (e.g. 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.
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);
}
}
}