/// <summary> /// The method to send a start message and wait for the end message. /// </summary> /// <param name="startMessage">The start message.</param> /// <param name="onMessage">The action to send the message.</param> /// <param name="timeout"> /// Optionally a timeout after which the method will return, without sending the result. /// By default the timeout is <see cref="NoTimout"/> /// </param> /// <param name="cancellationToken"> /// Optionally a cancellation token to cancel the wait operation. This is helpful, when for example the /// imitated service call is an async method. By default no CancellationToken will be used. /// </param> /// <returns>The <see cref="MessageGateResult{TEnd}"/> of the operation.</returns> /// <remarks> /// <para>For an example how to use this class see the type documentation.</para> /// <para> /// WARNING: Extensive use of this method will lead to time gaps in the execution. See .net issue on github: https://github.com/dotnet/runtime/issues/55562 /// Use this method only for the legacy service call. Of all other scenarios use <see cref="SendAndContinue(TStart,System.Action{Agents.Net.Message},System.Action{Agents.Net.MessageGateResult{TEnd}},int,System.Threading.CancellationToken)"/>. /// </para> /// </remarks> public MessageGateResult <TEnd> SendAndAwait(TStart startMessage, Action <Message> onMessage, int timeout = NoTimout, CancellationToken cancellationToken = default) { if (startMessage == null) { throw new ArgumentNullException(nameof(startMessage)); } if (onMessage == null) { throw new ArgumentNullException(nameof(onMessage)); } using (ManualResetEventSlim resetEvent = new ManualResetEventSlim()) { ManualResetEventSlim local = resetEvent; MessageGateResult <TEnd> result = null; SendAndContinue(startMessage, onMessage, r => { result = r; local.Set(); }, timeout, cancellationToken); //Do not use cancellation token here. The this is handled in the SendAndContinueMethod resetEvent.Wait(); return(result); } }
/// <summary> /// The method to send a start message and wait for the end message. /// </summary> /// <param name="startMessage">The start message.</param> /// <param name="onMessage">The action to send the message.</param> /// <param name="continueAction">The action to execute once a <see cref="MessageGateResult{TEnd}"/> was created.</param> /// <param name="timeout"> /// Optionally a timeout after which the method will return, without sending the result. /// By default the timeout is <see cref="NoTimout"/> /// </param> /// <param name="cancellationToken"> /// Optionally a cancellation token to cancel the continue operation. By default no CancellationToken will be used. /// </param> /// <remarks> /// <para>For an example how to use this class see the type documentation.</para> /// </remarks> public void SendAndContinue(TStart startMessage, Action <Message> onMessage, Action <MessageGateResult <TEnd> > continueAction, int timeout = NoTimout, CancellationToken cancellationToken = default) { if (startMessage == null) { throw new ArgumentNullException(nameof(startMessage)); } if (onMessage == null) { throw new ArgumentNullException(nameof(onMessage)); } if (continueAction == null) { throw new ArgumentNullException(nameof(continueAction)); } CancellationToken userCancelToken = cancellationToken; CancellationToken timeoutCancelToken = default; List <CancellationTokenSource> sources = new List <CancellationTokenSource>(); if (timeout != NoTimout) { CancellationTokenSource timeoutSource = new CancellationTokenSource(timeout); sources.Add(timeoutSource); timeoutCancelToken = timeoutSource.Token; } CancellationTokenSource combinedSource = CancellationTokenSource.CreateLinkedTokenSource(userCancelToken, timeoutCancelToken); cancellationToken = combinedSource.Token; sources.Add(combinedSource); MessageDomain.CreateNewDomainsFor(startMessage); CancellationTokenRegistration register = default; OneTimeAction action = new OneTimeAction(OnReceived); continueActions.TryAdd(startMessage.MessageDomain, action); register = cancellationToken.Register(OnCancel); onMessage(startMessage); void Dispose() { foreach (CancellationTokenSource tokenSource in sources) { tokenSource.Dispose(); } register.Dispose(); } void OnReceived(Message message) { if (!continueActions.TryRemove(startMessage.MessageDomain, out _)) { return; } MessageDomain.TerminateDomainsOf(startMessage); if (message.TryGet(out TEnd endMessage)) { continueAction(new MessageGateResult <TEnd>(MessageGateResultKind.Success, endMessage, Enumerable.Empty <ExceptionMessage>())); } else { ExceptionMessage exceptionMessage = message.Get <ExceptionMessage>(); continueAction( new MessageGateResult <TEnd>(MessageGateResultKind.Exception, null, new[] { exceptionMessage })); } Dispose(); } void OnCancel() { if (!continueActions.TryRemove(startMessage.MessageDomain, out _)) { return; } MessageDomain.TerminateDomainsOf(startMessage); MessageGateResultKind resultKind = MessageGateResultKind.Success; if (userCancelToken.IsCancellationRequested) { resultKind = MessageGateResultKind.Canceled; } else if (timeoutCancelToken.IsCancellationRequested) { resultKind = MessageGateResultKind.Timeout; } MessageGateResult <TEnd> result = new MessageGateResult <TEnd>(resultKind, null, Enumerable.Empty <ExceptionMessage>()); continueAction(result); Dispose(); } }