AsyncEx icon indicating copy to clipboard operation
AsyncEx copied to clipboard

Feature request: AwaitableEvent

Open cactuaroid opened this issue 7 years ago • 6 comments

I have made AwaitableEvent because I need to wake up all awaiters by one event. I considered AsyncAutoResetEvent etc. but did not fit. AwaitableEvent is simple but I think is useful if you just want to await a event. Definitions: AwaitableEvent.cs, AwaitableEventInvoked.cs User: ChatLogRepository.cs, ChatService.cs

My motivation in my case here using gRPC is that one RPC (Write) cannot write to another RPC (Subscribe) response stream of IAsyncEnumerable. When a object is written to server, the server should push the object to all subscription stream. But Invoking normal event does not work to trigger that because it is actually just calling methods. It have to be triggered via different mechanism.

I googled and found this post inspires me. https://stackoverflow.com/questions/12858501/is-it-possible-to-await-an-event-instead-of-another-async-method

How do you think about this? I can make PR to your repository if you think it's useful. Or, if you know the other better ways, I would be glad to know. Thank you.

cactuaroid avatar Sep 02 '18 07:09 cactuaroid

Hmm, I have been thinking about this. Maybe awaitable event is same as IAsyncEnumerable. Event is usually a sequence, not one time. What I'm thinking about is how to generate IAsyncEnumerable sequence. This problem is out of AsyncEx, isn't it? (Perhaps it will be from C# 8 though)

cactuaroid avatar Sep 03 '18 15:09 cactuaroid

Finally I removed AwaitableEvent and replaced it with new AsyncEnumerableEvent.cs. I feel this represents awaitable event well, and this is Ix-Async topic. I'm closing this feature request. Thank you.

cactuaroid avatar Sep 04 '18 08:09 cactuaroid

Actually, I am planning something like an awaitable event, but I just realized I didn't have an issue for it. So I'm going to reopen this one.

Conceptually, events map best to IObservable, but there are some scenarios where awaiting events is useful.

StephenCleary avatar Sep 04 '18 14:09 StephenCleary

Thank you for the comment. Finally (again), I removed AsyncEnumerableEvent and replace it with IObservable for the event sequence side. On the consumer side, IObservable.ToAsyncEnumerable() so that gRPC method can pull the sequence. I feel now they represents the concept well.

I have posted related question here, and concluded by myself with your comment. https://stackoverflow.com/questions/52164953/generate-iasyncenumerable-representing-asynchronous-event

there are some scenarios where awaiting events is useful.

I'm interested in the scenarios.

cactuaroid avatar Sep 05 '18 08:09 cactuaroid

@cactuaroid Writing TAP wrappers for EAP components is one that immediately comes to mind.

StephenCleary avatar Sep 05 '18 19:09 StephenCleary

Some code I had lying around, not exactly the same as what you're talking about here, I think, but thought I would share anyways. I suspect might have come to me long ago by modifying some stephen cleary code I found in stackoverflow.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Apex.TaskUtilities {

    public class AsyncEvent {

        public delegate Task Handler(object sender);

        readonly object Sync = new object();
        readonly List<Handler> CallbackList = new List<Handler>();

        public void AddHandler(Handler callback) {
            if (null == callback) throw new NullReferenceException("callback is null");
            lock (Sync) CallbackList.Add(callback);
        }

        public void RemoveHandler(Handler callback) {
            if (null == callback) throw new NullReferenceException("callback is null");
            lock (Sync) CallbackList.Remove(callback);
        }

        public async Task InvokeSerialAsync(object sender) {
            var list = CopyInvocationListThreadSafe();
            foreach (var callback in list)
                await callback(sender).ConfigureAwait(false);
        }

        public async Task InvokeParallelAsync(Object sender) {
            var list = CopyInvocationListThreadSafe();
            var tasks = list.Select(c => c(sender));
            await Task.WhenAll(tasks).ConfigureAwait(false);
        }

        List<Handler> CopyInvocationListThreadSafe() {
            lock (Sync) {
                return CallbackList.ToList();
            }
        }
    }

    public class AsyncEvent<TEventArgs> {

        public delegate Task Handler(object sender, TEventArgs eventArgs);

        readonly object Sync = new object();
        readonly List<Handler> CallbackList = new List<Handler>();

        public void AddHandler(Handler callback) {
            if (null == callback) throw new NullReferenceException("callback is null");
            lock (Sync) CallbackList.Add(callback);
        }

        public void RemoveHandler(Handler callback) {
            if (null == callback) throw new NullReferenceException("callback is null");
            lock (Sync) CallbackList.Remove(callback);
        }

        public async Task InvokeSerialAsync(object sender, TEventArgs eventArgs) {
            var list = CopyInvocationListThreadSafe();
            foreach (var callback in list)
                await callback(sender, eventArgs).ConfigureAwait(false);
        }

        public async Task InvokeParallelAsync(Object sender, TEventArgs eventArgs) {
            var list = CopyInvocationListThreadSafe();
            var tasks = list.Select(c => c(sender, eventArgs));
            await Task.WhenAll(tasks).ConfigureAwait(false);
        }

        List<Handler> CopyInvocationListThreadSafe() {
            lock (Sync) {
                return CallbackList.ToList();
            }
        }
    }
}

bboyle1234 avatar Oct 12 '18 01:10 bboyle1234