/// <summary> /// Signals the workflow. /// </summary> /// <param name="signalName">The signal name.</param> /// <param name="args">The signal arguments.</param> /// <returns>The tracking <see cref="Task"/>.</returns> /// <exception cref="InvalidOperationException">Thrown if the child workflow has not been started.</exception> /// <remarks> /// <note> /// <b>IMPORTANT:</b> You need to take care to ensure that the parameters passed /// are compatible with the target workflow signal arguments. /// </note> /// </remarks> public async Task SignalAsync(string signalName, params object[] args) { await SyncContext.Clear; Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(signalName), nameof(signalName)); Covenant.Requires <ArgumentNullException>(args != null, nameof(args)); if (Execution == null) { throw new InvalidOperationException("The stub must be started first."); } var reply = await parentWorkflow.ExecuteNonParallel( async() => { return((WorkflowSignalChildReply)await client.CallProxyAsync( new WorkflowSignalChildRequest() { ContextId = parentWorkflow.ContextId, ChildId = childExecution.ChildId, SignalName = signalName, SignalArgs = TemporalHelper.ArgsToBytes(client.DataConverter, args) })); }); reply.ThrowOnError(); }
/// <summary> /// Queries the workflow. /// </summary> /// <typeparam name="TQueryResult">The query result type.</typeparam> /// <param name="queryName">Identifies the query.</param> /// <param name="args">The query arguments.</param> /// <returns>The query result.</returns> /// <remarks> /// <note> /// <b>IMPORTANT:</b> You need to take care to ensure that the parameters and /// result type passed are compatible with the target workflow query arguments. /// </note> /// </remarks> public async Task <TQueryResult> QueryAsync <TQueryResult>(string queryName, params object[] args) { await SyncContext.Clear; Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(queryName), nameof(queryName)); Covenant.Requires <ArgumentNullException>(args != null, nameof(args)); if (execution == null) { throw new InvalidOperationException("The stub must be started first."); } var reply = (WorkflowQueryReply)await client.CallProxyAsync( new WorkflowQueryRequest() { WorkflowId = execution.WorkflowId, RunId = execution.RunId, Namespace = options.Namespace, QueryName = queryName, QueryArgs = TemporalHelper.ArgsToBytes(client.DataConverter, args) }); reply.ThrowOnError(); return(client.DataConverter.FromData <TQueryResult>(reply.Result)); }
/// <summary> /// Starts the target activity that returns <c>void</c>, passing the specified arguments. /// </summary> /// <param name="args">The arguments to be passed to the activity.</param> /// <returns>The <see cref="IAsyncFuture{T}"/> with the <see cref="IAsyncFuture{T}.GetAsync"/> that can be used to retrieve the workfow result.</returns> /// <exception cref="InvalidOperationException">Thrown when attempting to start a stub more than once.</exception> /// <remarks> /// <para> /// You must take care to pass parameters that are compatible with the target activity parameters. /// These are checked at runtime but not while compiling. /// </para> /// <note> /// Any given <see cref="ActivityFutureStub{TActivityInterface}"/> may only be executed once. /// </note> /// </remarks> public async Task <IAsyncFuture> StartAsync(params object[] args) { await SyncContext.Clear; Covenant.Requires <ArgumentNullException>(parentWorkflow != null, nameof(parentWorkflow)); parentWorkflow.SetStackTrace(); if (hasStarted) { throw new InvalidOperationException("Cannot start a future stub more than once."); } var parameters = targetMethod.GetParameters(); if (parameters.Length != args.Length) { throw new ArgumentException($"Invalid number of parameters: [{parameters.Length}] expected but [{args.Length}] were passed.", nameof(parameters)); } hasStarted = true; // Cast the input parameters to the target types so that developers won't need to expicitly // cast things like integers into longs, floats into doubles, etc. for (int i = 0; i < args.Length; i++) { args[i] = TemporalHelper.ConvertArg(parameters[i].ParameterType, args[i]); } // Start the activity. var client = parentWorkflow.Client; var dataConverter = client.DataConverter; var activityConstructor = typeof(TActivityImplementation).GetConstructor(Type.EmptyTypes); var activityId = parentWorkflow.GetNextActivityId(); var activityActionId = parentWorkflow.RegisterActivityAction(typeof(TActivityImplementation), activityConstructor, targetMethod); var reply = await parentWorkflow.ExecuteNonParallel( async() => { return((ActivityStartLocalReply)await client.CallProxyAsync( new ActivityStartLocalRequest() { ContextId = parentWorkflow.ContextId, WorkerId = parentWorkflow.Worker.WorkerId, ActivityId = activityId, ActivityTypeId = activityActionId, Args = TemporalHelper.ArgsToBytes(dataConverter, args), Options = options })); }); reply.ThrowOnError(); parentWorkflow.UpdateReplay(reply); // Create and return the future. return(new AsyncFuture(parentWorkflow, activityId)); }
public async Task SignalAsync(WorkflowExecution execution, string signalName, params object[] args) { await SyncContext.Clear; var dataConverter = Activity.Client.DataConverter; await Activity.Client.SignalWorkflowAsync(execution, signalName, TemporalHelper.ArgsToBytes(dataConverter, args)); }
/// <summary> /// Executes the associated workflow and waits for it to complete, /// returning the workflow result. /// </summary> /// <typeparam name="TResult">The workflow result type.</typeparam> /// <param name="args">The workflow arguments.</param> /// <returns>The tracking <see cref="Task"/>.</returns> public async Task <TResult> ExecuteAsync <TResult>(params object[] args) { await SyncContext.Clear; Covenant.Requires <ArgumentNullException>(args != null, nameof(args)); EnsureNotStarted(); var argBytes = TemporalHelper.ArgsToBytes(client.DataConverter, args); Execution = await client.StartWorkflowAsync(WorkflowTypeName, argBytes, Options); return(await GetResultAsync <TResult>()); }
/// <summary> /// Signals the associated workflow, starting it if it hasn't already been started. /// </summary> /// <param name="signalName">Specifies the signal name.</param> /// <param name="signalArgs">Specifies the signal arguments.</param> /// <param name="startArgs">Specifies the workflow start arguments.</param> /// <returns>The tracking <see cref="Task"/>.</returns> public async Task <WorkflowExecution> SignalWithStartAsync(string signalName, object[] signalArgs, object[] startArgs) { await SyncContext.Clear; Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(signalName), nameof(signalName)); Covenant.Requires <ArgumentNullException>(signalArgs != null, nameof(signalArgs)); Covenant.Requires <ArgumentNullException>(startArgs != null, nameof(startArgs)); var signalArgBytes = TemporalHelper.ArgsToBytes(client.DataConverter, signalArgs); var startArgBytes = TemporalHelper.ArgsToBytes(client.DataConverter, startArgs); return(await client.SignalWorkflowWithStartAsync(this.WorkflowTypeName, signalName, signalArgBytes, startArgBytes, this.Options)); }
/// <summary> /// Signals the associated workflow. /// </summary> /// <param name="signalName">Specifies the signal name.</param> /// <param name="args">Specifies the signal arguments.</param> /// <returns>The tracking <see cref="Task"/>.</returns> public async Task SignalAsync(string signalName, params object[] args) { await SyncContext.Clear; Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(signalName), nameof(signalName)); Covenant.Requires <ArgumentNullException>(args != null, nameof(args)); EnsureStarted(); if (Execution == null) { throw new InvalidOperationException("Signal cannot be sent because the stub doesn't have the workflow execution."); } var argBytes = TemporalHelper.ArgsToBytes(client.DataConverter, args); await client.SignalWorkflowAsync(Execution, signalName, argBytes, client.ResolveNamespace(Options?.Namespace)); }
/// <summary> /// Queries the associated workflow specifying the expected result type as /// a parameter. /// </summary> /// <param name="resultType">Specifies the query result type.</param> /// <param name="queryType">Specifies the query type.</param> /// <param name="args">Specifies the query arguments.</param> /// <returns>The query result as a <c>dynamic</c>.</returns> public async Task <object> QueryAsync(Type resultType, string queryType, params object[] args) { await SyncContext.Clear; Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(queryType), nameof(queryType)); Covenant.Requires <ArgumentNullException>(args != null, nameof(args)); EnsureStarted(); if (Execution == null) { throw new InvalidOperationException("Query cannot be sent because the stub doesn't have the workflow execution."); } var argBytes = TemporalHelper.ArgsToBytes(client.DataConverter, args); return(client.DataConverter.FromData(resultType, await client.QueryWorkflowAsync(Execution, queryType, argBytes, client.ResolveNamespace(Options?.Namespace)))); }
/// <summary> /// Starts the workflow, returning an <see cref="IAsyncFuture"/> that can be used /// to wait for the the workflow to complete and obtain its result. /// </summary> /// <typeparam name="TResult">The workflow result type.</typeparam> /// <param name="args">The workflow arguments.</param> /// <returns>An <see cref="ExternalWorkflowFuture{TResult}"/> that can be used to retrieve the workflow result as an <c>object</c>.</returns> /// <exception cref="InvalidOperationException">Thrown if the workflow has already been started.</exception> /// <remarks> /// <note> /// <b>IMPORTANT:</b> You need to take care to ensure that the parameters passed /// and the result type are compatible with the target workflow method. /// </note> /// </remarks> public async Task <ExternalWorkflowFuture <TResult> > StartAsync <TResult>(params object[] args) { await SyncContext.Clear; Covenant.Requires <ArgumentNullException>(args != null, nameof(args)); if (execution != null) { throw new InvalidOperationException("Cannot start a future stub more than once."); } execution = await client.StartWorkflowAsync(workflowTypeName, TemporalHelper.ArgsToBytes(client.DataConverter, args), options); // Create and return the future. return(new ExternalWorkflowFuture <TResult>(client, execution, options.Namespace)); }
/// <summary> /// Signals the workflow. /// </summary> /// <param name="signalName">Specifies the signal name.</param> /// <param name="args">Specifies the signal arguments.</param> /// <returns>The tracking <see cref="Task"/>.</returns> public async Task SignalAsync(string signalName, params object[] args) { await SyncContext.Clear; Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(signalName), nameof(signalName)); Covenant.Requires <ArgumentNullException>(args != null, nameof(args)); if (parentWorkflow != null) { var stub = parentWorkflow.NewLocalActivityStub <ILocalOperations, LocalOperations>(); await stub.SignalAsync(Execution, signalName, args); } else { await client.SignalWorkflowAsync(Execution, signalName, TemporalHelper.ArgsToBytes(client.DataConverter, args)); } }
/// <summary> /// <b>EXPERIMENTAL:</b> This method synchronously signals the workflow and returns /// only after the workflow has processed received and processed the signal as opposed /// to <see cref="SignalAsync"/> which is fire-and-forget and does not wait for the /// signal to be processed. /// </summary> /// <param name="signalName"> /// The signal name as defined by the <see cref="SignalMethodAttribute"/> /// decorating the workflow signal method. /// </param> /// <param name="args">The signal arguments.</param> /// <remarks> /// <note> /// <b>IMPORTANT:</b> You need to take care to ensure that the parameters passed /// are compatible with the target workflow arguments. No compile-time type checking /// is performed for this method. /// </note> /// </remarks> public async Task SyncSignalAsync(string signalName, params object[] args) { await SyncContext.Clear; Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(signalName), nameof(signalName)); Covenant.Requires <ArgumentNullException>(args != null, nameof(args)); if (execution == null) { throw new InvalidOperationException("The stub must be started first."); } var signalId = Guid.NewGuid().ToString("d"); var argBytes = TemporalHelper.ArgsToBytes(client.DataConverter, args); var signalCall = new SyncSignalCall(signalName, signalId, argBytes); var signalCallBytes = TemporalHelper.ArgsToBytes(client.DataConverter, new object[] { signalCall }); await client.SyncSignalWorkflowAsync(execution, signalName, signalId, signalCallBytes, options.Namespace); }
/// <summary> /// Starts the target activity that returns <c>void</c>, passing the specified arguments. /// </summary> /// <param name="args">The arguments to be passed to the activity.</param> /// <returns>The <see cref="IAsyncFuture{T}"/> with the <see cref="IAsyncFuture{T}.GetAsync"/> that can be used to retrieve the workfow result.</returns> /// <exception cref="InvalidOperationException">Thrown when attempting to start a future stub more than once.</exception> /// <remarks> /// <para> /// You must take care to pass parameters that are compatible with the target activity parameters. /// These are checked at runtime but not while compiling. /// </para> /// <note> /// Any given <see cref="ActivityFutureStub{TActivityInterface}"/> may only be executed once. /// </note> /// </remarks> public async Task <IAsyncFuture> StartAsync(params object[] args) { await SyncContext.Clear; Covenant.Requires <ArgumentNullException>(parentWorkflow != null, nameof(parentWorkflow)); parentWorkflow.SetStackTrace(); if (hasStarted) { throw new InvalidOperationException("Cannot start a future stub more than once."); } hasStarted = true; // Start the activity. var client = parentWorkflow.Client; var dataConverter = client.DataConverter; var activityId = parentWorkflow.GetNextActivityId(); var reply = await parentWorkflow.ExecuteNonParallel( async() => { return((ActivityStartReply)await client.CallProxyAsync( new ActivityStartRequest() { ContextId = parentWorkflow.ContextId, ActivityId = activityId, Activity = activityTypeName, Args = TemporalHelper.ArgsToBytes(dataConverter, args), Options = options })); }); reply.ThrowOnError(); parentWorkflow.UpdateReplay(reply); // Create and return the future. return(new AsyncFuture(parentWorkflow, activityId)); }
/// <summary> /// Starts the target workflow that returns <typeparamref name="TResult"/>, passing any specified arguments. /// </summary> /// <typeparam name="TResult">The workflow result type.</typeparam> /// <param name="args">The arguments to be passed to the workflow.</param> /// <returns>The <see cref="ChildWorkflowFuture{T}"/> with the <see cref="ChildWorkflowFuture{T}.GetAsync"/> that can be used to retrieve the workfow result.</returns> /// <exception cref="InvalidOperationException">Thrown when attempting to start a future stub more than once.</exception> /// <remarks> /// <para> /// You must take care to pass parameters that are compatible with the target workflow parameters. /// These are checked at runtime but not while compiling. /// </para> /// <note> /// Any given <see cref="ChildWorkflowStub{TWorkflowInterface}"/> may only be executed once. /// </note> /// </remarks> public async Task <ChildWorkflowFuture <TResult> > StartAsync <TResult>(params object[] args) { await SyncContext.Clear; Covenant.Requires <ArgumentNullException>(parentWorkflow != null, nameof(parentWorkflow)); parentWorkflow.SetStackTrace(); if (hasStarted) { throw new InvalidOperationException("Cannot start a future stub more than once."); } var parameters = targetMethod.GetParameters(); if (parameters.Length != args.Length) { throw new ArgumentException($"Invalid number of parameters: [{parameters.Length}] expected but [{args.Length}] were passed.", nameof(parameters)); } hasStarted = true; // Cast the input parameters to the target types so that developers won't need to expicitly // cast things like integers into longs, floats into doubles, etc. for (int i = 0; i < args.Length; i++) { args[i] = TemporalHelper.ConvertArg(parameters[i].ParameterType, args[i]); } // Start the child workflow and then construct and return the future. var client = parentWorkflow.Client; var execution = await client.StartChildWorkflowAsync(parentWorkflow, workflowTypeName, TemporalHelper.ArgsToBytes(client.DataConverter, args), options); // Initialize the type-safe stub property such that developers can call // any query or signal methods. Stub = StubManager.NewChildWorkflowStub <TWorkflowInterface>(client, parentWorkflow, workflowTypeName, execution); // Create and return the future. var resultType = targetMethod.ReturnType; if (resultType == typeof(Task)) { throw new ArgumentException($"Workflow method [{nameof(TWorkflowInterface)}.{targetMethod.Name}()] does not return [void].", nameof(TWorkflowInterface)); } resultType = resultType.GenericTypeArguments.First(); if (!resultType.IsAssignableFrom(typeof(TResult))) { throw new ArgumentException($"Workflow method [{nameof(TWorkflowInterface)}.{targetMethod.Name}()] returns [{resultType.FullName}] which is not compatible with [{nameof(TResult)}].", nameof(TWorkflowInterface)); } return(new ChildWorkflowFuture <TResult>(parentWorkflow, execution)); }
/// <summary> /// Starts the child workflow, returning an <see cref="IAsyncFuture"/> that can be used /// to wait for the workflow to complete. This version doesn't return a workflow result. /// </summary> /// <param name="args">The workflow arguments.</param> /// <returns>An <see cref="ChildWorkflowFuture"/> that can be used to retrieve the workflow result as an <c>object</c>.</returns> /// <exception cref="InvalidOperationException">Thrown if the child workflow has already been started.</exception> /// <remarks> /// <note> /// <b>IMPORTANT:</b> You need to take care to ensure that the parameters passed /// are compatible with the target workflow arguments. /// </note> /// </remarks> public async Task <ChildWorkflowFuture> StartAsync(params object[] args) { await SyncContext.Clear; Covenant.Requires <ArgumentNullException>(args != null, nameof(args)); if (childExecution != null) { throw new InvalidOperationException("Cannot start a future stub more than once."); } childExecution = await client.StartChildWorkflowAsync(parentWorkflow, WorkflowTypeName, TemporalHelper.ArgsToBytes(client.DataConverter, args), Options); // Create and return the future. return(new ChildWorkflowFuture(parentWorkflow, childExecution)); }
/// <summary> /// Starts the target activity that returns <typeparamref name="TResult"/>, passing the specified arguments. /// </summary> /// <typeparam name="TResult">The activity result type.</typeparam> /// <param name="args">The arguments to be passed to the activity.</param> /// <returns>The <see cref="IAsyncFuture{T}"/> with the <see cref="IAsyncFuture{T}.GetAsync"/> that can be used to retrieve the workfow result.</returns> /// <exception cref="InvalidOperationException">Thrown when attempting to start a future stub more than once.</exception> /// <remarks> /// <para> /// You must take care to pass parameters that are compatible with the target activity parameters. /// These are checked at runtime but not while compiling. /// </para> /// <note> /// Any given <see cref="ActivityFutureStub{TActivityInterface}"/> may only be executed once. /// </note> /// </remarks> public async Task <IAsyncFuture <TResult> > StartAsync <TResult>(params object[] args) { await SyncContext.Clear; Covenant.Requires <ArgumentNullException>(parentWorkflow != null, nameof(parentWorkflow)); parentWorkflow.SetStackTrace(); if (hasStarted) { throw new InvalidOperationException("Cannot start a future stub more than once."); } var parameters = targetMethod.GetParameters(); if (parameters.Length != args.Length) { throw new ArgumentException($"Invalid number of parameters: [{parameters.Length}] expected but [{args.Length}] were passed.", nameof(parameters)); } hasStarted = true; // Cast the input parameters to the target types so that developers won't need to expicitly // cast things like integers into longs, floats into doubles, etc. for (int i = 0; i < args.Length; i++) { args[i] = TemporalHelper.ConvertArg(parameters[i].ParameterType, args[i]); } // Validate the return type. var resultType = targetMethod.ReturnType; if (resultType == typeof(Task)) { throw new ArgumentException($"Activity method [{nameof(TActivityInterface)}.{targetMethod.Name}()] does not return [void].", nameof(TActivityInterface)); } resultType = resultType.GenericTypeArguments.First(); if (!resultType.IsAssignableFrom(typeof(TResult))) { throw new ArgumentException($"Activity method [{nameof(TActivityInterface)}.{targetMethod.Name}()] returns [{resultType.FullName}] which is not compatible with [{nameof(TResult)}].", nameof(TActivityInterface)); } // Start the activity. var client = parentWorkflow.Client; var dataConverter = client.DataConverter; var activityId = parentWorkflow.GetNextActivityId(); var reply = await parentWorkflow.ExecuteNonParallel( async() => { return((ActivityStartReply)await client.CallProxyAsync( new ActivityStartRequest() { ContextId = parentWorkflow.ContextId, ActivityId = activityId, Activity = activityTypeName, Args = TemporalHelper.ArgsToBytes(dataConverter, args), Options = options })); }); reply.ThrowOnError(); parentWorkflow.UpdateReplay(reply); // Create and return the future. return(new AsyncFuture <TResult>(parentWorkflow, activityId)); }