Skip to content

8.0 Discussion about public events #973

Open
@bollhals

Description

@bollhals

Originating from #970 the discussion is about how we deal with the currently public events.

Starter was the post from @stebet

I'm wondering if a better implementation than having the events is to have a "handler" interface that can be registered in the channels, so instead of events like FlowControlChanged etc. there would be an interface called IChannelHandler, that could be registered like consumers. These handlers would exposes the standard methods:

public interface IChannelHandler
{
  ValueTask OnFlowControl(IChannel channel, bool active);
  ValueTask OnPublicAckReceived(IChannel channel);
  ...
}

That way it's easy to override whatever logic to take care of in a class (logging, waiting for publish confirms etc.) and then register it with the channel interface channel.RegisterHandler(myCustomHandler);.

The channel can maintain those handlers in a list and execute them (in parallel if needed) as events happen. That way those implementing the handlers can also do things asynchronously with minimal effort and the channel code would be free of all the async event workarounds we currently have. Logging and all sorts of instrumentation or special logic can then be added to augment the channel interface, especially if the handlers can have a reference to the Channel objects themselves and access other things.

I've done some measurements today regarding performance of various ways of invoking actions To have also an idea about what this means.

Method action Mean Error StdDev Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
GetInvocationList Syste(...)nt32] [29] 14.7064 ns 1.0487 ns 0.0575 ns 1.00 0.00 0.0068 - - 32 B
GetInvocationList Syste(...)nt32] [29] 31.4116 ns 12.2659 ns 0.6723 ns 2.14 0.05 0.0085 - - 40 B
Direct Syste(...)nt32] [29] 0.6689 ns 0.1508 ns 0.0083 ns 0.05 0.00 - - - -
Direct Syste(...)nt32] [29] 7.2192 ns 0.2554 ns 0.0140 ns 0.49 0.00 - - - -
Reflection.Emit Syste(...)nt32] [29] 14.8313 ns 0.2667 ns 0.0146 ns 1.01 0.00 - - - -
Reflection.Emit Syste(...)nt32] [29] 25.1454 ns 0.8545 ns 0.0468 ns 1.71 0.01 - - - -
Interface Syste(...)Test] [66] 6.7287 ns 0.8310 ns 0.0455 ns ? ? - - - -
Interface Syste(...)Test] [66] 11.7185 ns 1.1444 ns 0.0627 ns ? ? - - - -
List_Action Syste(...)t32]] [64] 6.7017 ns 0.1550 ns 0.0085 ns ? ? - - - -
List_Action Syste(...)t32]] [64] 11.5592 ns 0.1531 ns 0.0084 ns ? ? - - - -

Where

  • GetInvocationList is the "current way" of getting the individual delegates
  • Direct is just calling delegate.Invoke() (no individual control over invocation)
  • Reflection.Emit is retrieving the _invocationList field of a delegate via Reflection.Emit (+- what is returned by GetInvocationList)
  • Interface is a single method interface stored in a list
  • List_Action is all induvidual delegates stored within a list and iterated over

And the first entry always being for one attached event handler and the 2nd one for two handlers (action += handler) (Or entries in the list)

I think the relevant notes are:

  • GetInvocationList is kind of slow and allocates
  • Direct is obviously unbeatable, but won't allow us to do what we want
  • Emit is nice, a but not fast enough while also having additional dependency
  • Interface / List_Action are reasonably fast while not allocating. (Downside though is the increased complexity to store them in the list + the allocation will take place there)

This issue should be taken as the place to dicuss where we want to move to with the events in our public API.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions