Пример #1
0
        /// <summary>
        /// Registers a new callback to be called when an event occurs.
        /// </summary>
        /// <param name="callbackAsync">Callback method to register.</param>
        /// <param name="addFirst"><c>true</c> to insert the new callback as the first callback to be called,
        /// <c>false</c> to add it as the last one.</param>
        /// <exception cref="ArgumentException">Thrown if <paramref name="callbackAsync"/> has already been registered.</exception>
        /// <remarks>
        /// It is allowed that a callback method registers another callback.
        /// </remarks>
        public void Register(AsyncExecutionEventCallback <TSender, TArg> callbackAsync, bool addFirst = false)
        {
            // We only lock if we are outside of the execution context of ExecuteCallbacksAsync.
            IDisposable lockReleaser = !this.callbackExecutionInProgress.Value ? this.asyncLock.Lock(this.cancellationSource.Token) : null;

            try
            {
                if (this.callbackToListNodeMapping.ContainsKey(callbackAsync))
                {
                    throw new ArgumentException("Callback already registered.");
                }

                var node = new LinkedListNode <AsyncExecutionEventCallback <TSender, TArg> >(callbackAsync);

                if (addFirst)
                {
                    this.callbackList.AddFirst(node);
                }
                else
                {
                    this.callbackList.AddLast(node);
                }

                this.callbackToListNodeMapping.Add(callbackAsync, node);
            }
            finally
            {
                lockReleaser?.Dispose();
            }
        }
Пример #2
0
        public async Task AsyncExecutionEvent_OnlyRegisteredCallbackIsCalled_Async()
        {
            using (var executionEvent = new AsyncExecutionEvent <int, int>())
            {
                int  value       = 1;
                bool called      = false;
                int  senderValue = 2;

                AsyncExecutionEventCallback <int, int> callbackAsync = (sender, arg) =>
                {
                    called = true;
                    Assert.Equal(value, arg);
                    Assert.Equal(senderValue, sender);
                    return(Task.CompletedTask);
                };
                executionEvent.Register(callbackAsync);

                await executionEvent.ExecuteCallbacksAsync(senderValue, value);

                Assert.True(called);

                // Now unregister callback and check that it won't be called anymore.
                called = false;
                executionEvent.Unregister(callbackAsync);
                await executionEvent.ExecuteCallbacksAsync(senderValue + 1, value + 1);

                Assert.False(called);
            }
        }
Пример #3
0
        public void AsyncExecutionEvent_CantRegisterTwice()
        {
            using (var executionEvent = new AsyncExecutionEvent <object, bool>())
            {
                AsyncExecutionEventCallback <object, bool> callback1Async = (sender, arg) =>
                {
                    return(Task.CompletedTask);
                };

                AsyncExecutionEventCallback <object, bool> callback2Async = (sender, arg) =>
                {
                    return(Task.CompletedTask);
                };

                executionEvent.Register(callback1Async);

                // We can register a second callback with same body, but it is different function.
                executionEvent.Register(callback2Async);

                // But we can't register a same function twice.
                Assert.Throws <ArgumentException>(() => executionEvent.Register(callback1Async));

                executionEvent.Unregister(callback1Async);
                executionEvent.Unregister(callback2Async);
            }
        }
Пример #4
0
        public async Task AsyncExecutionEvent_MultipleCallbacksExecutedInCorrectOrder_Async()
        {
            using (var executionEvent = new AsyncExecutionEvent <object, object>())
            {
                int orderIndex = 0;
                var orderList  = new List <int>();

                AsyncExecutionEventCallback <object, object> callback1Async = (sender, arg) =>
                {
                    orderIndex++;
                    orderList.Add(orderIndex);
                    return(Task.CompletedTask);
                };

                AsyncExecutionEventCallback <object, object> callback2Async = (sender, arg) =>
                {
                    orderIndex *= 3;
                    orderList.Add(orderIndex);
                    return(Task.CompletedTask);
                };

                AsyncExecutionEventCallback <object, object> callback3Async = (sender, arg) =>
                {
                    orderIndex = (orderIndex + 2) * 2;
                    orderList.Add(orderIndex);
                    return(Task.CompletedTask);
                };

                // We register the callbacks so that they should execute in order 1, 2, 3.
                executionEvent.Register(callback2Async);
                executionEvent.Register(callback1Async, true);
                executionEvent.Register(callback3Async);

                // We execute the callbacks and remove some of them and execute again.
                await executionEvent.ExecuteCallbacksAsync(null, null);

                executionEvent.Unregister(callback1Async);
                executionEvent.Unregister(callback3Async);

                await executionEvent.ExecuteCallbacksAsync(null, null);

                // So the final execution sequence should have been 1, 2, 3, 2, we check that.
                Assert.Equal(1, orderList[0]);  // 0 + 1 = 1
                Assert.Equal(3, orderList[1]);  // 1 * 3 = 3
                Assert.Equal(10, orderList[2]); // (3 + 2) * 2 = 10
                Assert.Equal(30, orderList[3]); // 10 * 3 = 30
                Assert.Equal(30, orderIndex);

                // After we unregister all callbacks, execution should not change the value of orderIndex.
                executionEvent.Unregister(callback2Async);
                await executionEvent.ExecuteCallbacksAsync(null, null);

                Assert.Equal(30, orderIndex);
            }
        }
Пример #5
0
        public void AsyncExecutionEvent_CantUnregisterNotRegistered()
        {
            using (var executionEvent = new AsyncExecutionEvent <object, bool>())
            {
                AsyncExecutionEventCallback <object, bool> callbackAsync = (sender, arg) =>
                {
                    return(Task.CompletedTask);
                };

                Assert.Throws <ArgumentException>(() => executionEvent.Unregister(callbackAsync));
            }
        }
Пример #6
0
        public void AsyncExecutionEvent_UnregisterGuarantee()
        {
            var rnd = new Random();

            using (var executionEvent = new AsyncExecutionEvent <object, bool>())
            {
                for (int i = 0; i < 100; i++)
                {
                    bool unregistered = false;
                    bool badCall      = false;
                    AsyncExecutionEventCallback <object, bool> callbackAsync = (sender, arg) =>
                    {
                        // badCall should never be set to true here
                        // because unregistered is only set to true
                        // after Unregister has been called.
                        badCall = arg;
                        return(Task.CompletedTask);
                    };

                    executionEvent.Register(callbackAsync);

                    Task unregisterTask = Task.Run(async() =>
                    {
                        // In about half of the cases, we yield execution here.
                        // So that we randomize the order of the two tasks.
                        if (rnd.Next(100) >= 50)
                        {
                            await Task.Yield();
                        }
                        executionEvent.Unregister(callbackAsync);
                        unregistered = true;
                    });

                    Task callTask = Task.Run(async() =>
                    {
                        // Random delay to help randomize the order of two tasks.
                        await Task.Delay(rnd.Next(10));
                        await executionEvent.ExecuteCallbacksAsync(null, unregistered);
                    });

                    Task.WaitAll(unregisterTask, callTask);
                    Assert.False(badCall);
                }
            }
        }
Пример #7
0
        /// <summary>
        /// Unregisters an existing callback.
        /// </summary>
        /// <param name="callbackAsync">Callback method to unregister.</param>
        /// <exception cref="ArgumentException">Thrown if <paramref name="callbackAsync"/> was not found among registered callbacks.</exception>
        /// <remarks>
        /// The caller is guaranteed that once this method completes, <paramref name="callbackAsync"/> will not be called by this executor.
        /// <para>It is allowed that a callback method unregisters itself (or another callback).</para>
        /// </remarks>
        public void Unregister(AsyncExecutionEventCallback <TSender, TArg> callbackAsync)
        {
            // We only lock if we are outside of the execution context of ExecuteCallbacksAsync.
            IDisposable lockReleaser = !this.callbackExecutionInProgress.Value ? this.asyncLock.Lock(this.cancellationSource.Token) : null;

            try
            {
                LinkedListNode <AsyncExecutionEventCallback <TSender, TArg> > node;
                if (!this.callbackToListNodeMapping.TryGetValue(callbackAsync, out node))
                {
                    throw new ArgumentException("Trying to unregistered callback that is not registered.");
                }

                this.callbackList.Remove(node);
                this.callbackToListNodeMapping.Remove(callbackAsync);
            }
            finally
            {
                lockReleaser?.Dispose();
            }
        }
Пример #8
0
        /// <summary>
        /// Calls all registered callbacks with the given arguments.
        /// </summary>
        /// <param name="sender">Source of the event.</param>
        /// <param name="arg">Argument to pass to the callbacks.</param>
        /// <remarks>
        /// It is necessary to hold the lock while calling the callbacks to provide guarantees described in <see cref="Unregister(AsyncExecutionEventCallback{TSender, TArg})"/>.
        /// However, we do support new callbacks to be registered or unregistered while callbacks are being executed,
        /// but this is only possible from the same execution context - i.e. another task or thread is unable to register or unregister callbacks
        /// while callbacks execution is in progress.
        /// </remarks>
        public async Task ExecuteCallbacksAsync(TSender sender, TArg arg)
        {
            this.callbackExecutionInProgress.Value = true;
            try
            {
                using (await this.asyncLock.LockAsync(this.cancellationSource.Token).ConfigureAwait(false))
                {
                    // We need to make a copy of the list because callbacks may call Register or Unregister,
                    // which modifies the list.
                    var listCopy = new AsyncExecutionEventCallback <TSender, TArg> [this.callbackList.Count];
                    this.callbackList.CopyTo(listCopy, 0);

                    foreach (AsyncExecutionEventCallback <TSender, TArg> callbackAsync in listCopy)
                    {
                        await callbackAsync(sender, arg).ConfigureAwait(false);
                    }
                }
            }
            finally
            {
                this.callbackExecutionInProgress.Value = false;
            }
        }