From 91f5a5b3cf72053b1b683bfd6cd5bc8cf6d02afb Mon Sep 17 00:00:00 2001 From: Andrew Yuan Date: Thu, 12 Mar 2026 19:02:11 -0700 Subject: [PATCH 1/6] Standalone Activity sample --- README.md | 1 + TemporalioSamples.sln | 18 ++++ src/StandaloneActivity/MyActivities.cs | 12 +++ src/StandaloneActivity/Program.cs | 102 ++++++++++++++++++ src/StandaloneActivity/README.md | 36 +++++++ ...emporalioSamples.StandaloneActivity.csproj | 7 ++ tests/StandaloneActivity/MyActivityTests.cs | 36 +++++++ 7 files changed, 212 insertions(+) create mode 100644 src/StandaloneActivity/MyActivities.cs create mode 100644 src/StandaloneActivity/Program.cs create mode 100644 src/StandaloneActivity/README.md create mode 100644 src/StandaloneActivity/TemporalioSamples.StandaloneActivity.csproj create mode 100644 tests/StandaloneActivity/MyActivityTests.cs diff --git a/README.md b/README.md index e26e8e7..1507d68 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ Prerequisites: * [Saga](src/Saga) - Demonstrates how to implement a saga pattern. * [Schedules](src/Schedules) - How to schedule workflows to be run at specific times in the future. * [SignalsQueries](src/SignalsQueries) - A loyalty program using Signals and Queries. +* [StandaloneActivity](src/StandaloneActivity) - Execute activities directly from a client, without a workflow. * [SleepForDays](src/SleepForDays/) - Use a timer to send an email every 30 days. * [Timer](src/Timer) - Use a timer to implement a monthly subscription; handle workflow cancellation. * [UpdatableTimer](src/UpdatableTimer) - A timer that can be updated while sleeping. diff --git a/TemporalioSamples.sln b/TemporalioSamples.sln index 0189a4b..eae12aa 100644 --- a/TemporalioSamples.sln +++ b/TemporalioSamples.sln @@ -107,6 +107,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemporalioSamples.NexusCanc EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "NexusCancellation", "NexusCancellation", "{7123C63D-3158-4C9A-8EAD-6D4F1295BC04}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "StandaloneActivity", "StandaloneActivity", "{EAB0C45A-7620-D2D2-2901-5E7FCBFFDA77}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TemporalioSamples.StandaloneActivity", "src\StandaloneActivity\TemporalioSamples.StandaloneActivity.csproj", "{240517A1-13B5-4A67-8519-BFCF2C4591B9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -633,6 +637,18 @@ Global {6D0BE4C4-9C4F-4A3D-78F1-B0B761568559}.Release|x64.Build.0 = Release|Any CPU {6D0BE4C4-9C4F-4A3D-78F1-B0B761568559}.Release|x86.ActiveCfg = Release|Any CPU {6D0BE4C4-9C4F-4A3D-78F1-B0B761568559}.Release|x86.Build.0 = Release|Any CPU + {240517A1-13B5-4A67-8519-BFCF2C4591B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {240517A1-13B5-4A67-8519-BFCF2C4591B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {240517A1-13B5-4A67-8519-BFCF2C4591B9}.Debug|x64.ActiveCfg = Debug|Any CPU + {240517A1-13B5-4A67-8519-BFCF2C4591B9}.Debug|x64.Build.0 = Debug|Any CPU + {240517A1-13B5-4A67-8519-BFCF2C4591B9}.Debug|x86.ActiveCfg = Debug|Any CPU + {240517A1-13B5-4A67-8519-BFCF2C4591B9}.Debug|x86.Build.0 = Debug|Any CPU + {240517A1-13B5-4A67-8519-BFCF2C4591B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {240517A1-13B5-4A67-8519-BFCF2C4591B9}.Release|Any CPU.Build.0 = Release|Any CPU + {240517A1-13B5-4A67-8519-BFCF2C4591B9}.Release|x64.ActiveCfg = Release|Any CPU + {240517A1-13B5-4A67-8519-BFCF2C4591B9}.Release|x64.Build.0 = Release|Any CPU + {240517A1-13B5-4A67-8519-BFCF2C4591B9}.Release|x86.ActiveCfg = Release|Any CPU + {240517A1-13B5-4A67-8519-BFCF2C4591B9}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -686,5 +702,7 @@ Global {AF077751-E4B9-4696-93CB-74653F0BB6C4} = {1A647B41-53D0-4638-AE5A-6630BAAE45FC} {6D0BE4C4-9C4F-4A3D-78F1-B0B761568559} = {7123C63D-3158-4C9A-8EAD-6D4F1295BC04} {7123C63D-3158-4C9A-8EAD-6D4F1295BC04} = {1A647B41-53D0-4638-AE5A-6630BAAE45FC} + {EAB0C45A-7620-D2D2-2901-5E7FCBFFDA77} = {1A647B41-53D0-4638-AE5A-6630BAAE45FC} + {240517A1-13B5-4A67-8519-BFCF2C4591B9} = {EAB0C45A-7620-D2D2-2901-5E7FCBFFDA77} EndGlobalSection EndGlobal diff --git a/src/StandaloneActivity/MyActivities.cs b/src/StandaloneActivity/MyActivities.cs new file mode 100644 index 0000000..28ed0a0 --- /dev/null +++ b/src/StandaloneActivity/MyActivities.cs @@ -0,0 +1,12 @@ +namespace TemporalioSamples.StandaloneActivity; + +using Temporalio.Activities; + +public static class MyActivities +{ + [Activity] + public static Task ComposeGreetingAsync(ComposeGreetingInput input) => + Task.FromResult($"{input.Greeting}, {input.Name}!"); +} + +public record ComposeGreetingInput(string Greeting, string Name); diff --git a/src/StandaloneActivity/Program.cs b/src/StandaloneActivity/Program.cs new file mode 100644 index 0000000..def8250 --- /dev/null +++ b/src/StandaloneActivity/Program.cs @@ -0,0 +1,102 @@ +using Microsoft.Extensions.Logging; +using Temporalio.Client; +using Temporalio.Common.EnvConfig; +using Temporalio.Worker; +using TemporalioSamples.StandaloneActivity; + +var connectOptions = ClientEnvConfig.LoadClientConnectOptions(); +connectOptions.TargetHost ??= "localhost:7233"; +connectOptions.LoggerFactory = LoggerFactory.Create(builder => + builder. + AddSimpleConsole(options => options.TimestampFormat = "[HH:mm:ss] "). + SetMinimumLevel(LogLevel.Information)); +var client = await TemporalClient.ConnectAsync(connectOptions); + +const string taskQueue = "standalone-activity-sample"; + +async Task RunWorkerAsync() +{ + using var tokenSource = new CancellationTokenSource(); + Console.CancelKeyPress += (_, eventArgs) => + { + tokenSource.Cancel(); + eventArgs.Cancel = true; + }; + + Console.WriteLine("Running worker"); + using var worker = new TemporalWorker( + client, + new TemporalWorkerOptions(taskQueue). + AddActivity(MyActivities.ComposeGreetingAsync)); + try + { + await worker.ExecuteAsync(tokenSource.Token); + } + catch (OperationCanceledException) + { + Console.WriteLine("Worker cancelled"); + } +} + +async Task ExecuteActivityAsync() +{ + var result = await client.ExecuteActivityAsync( + () => MyActivities.ComposeGreetingAsync(new ComposeGreetingInput("Hello", "World")), + new("standalone-activity-id", taskQueue) + { + ScheduleToCloseTimeout = TimeSpan.FromSeconds(10), + }); + Console.WriteLine($"Activity result: {result}"); +} + +async Task StartActivityAsync() +{ + var handle = await client.StartActivityAsync( + () => MyActivities.ComposeGreetingAsync(new ComposeGreetingInput("Hello", "World")), + new("standalone-activity-id", taskQueue) + { + ScheduleToCloseTimeout = TimeSpan.FromSeconds(10), + }); + Console.WriteLine($"Started activity: {handle.Id}"); + + var result = await handle.GetResultAsync(); + Console.WriteLine($"Activity result: {result}"); +} + +async Task ListActivitiesAsync() +{ + await foreach (var info in client.ListActivitiesAsync( + $"TaskQueue = '{taskQueue}'")) + { + Console.WriteLine($"ActivityID: {info.ActivityId}, Type: {info.ActivityType}, Status: {info.Status}"); + } +} + +async Task CountActivitiesAsync() +{ + var resp = await client.CountActivitiesAsync( + $"TaskQueue = '{taskQueue}'"); + Console.WriteLine($"Total activities: {resp.Count}"); +} + +switch (args.ElementAtOrDefault(0)) +{ + case "worker": + await RunWorkerAsync(); + break; + case "execute-activity": + await ExecuteActivityAsync(); + break; + case "start-activity": + await StartActivityAsync(); + break; + case "list-activities": + await ListActivitiesAsync(); + break; + case "count-activities": + await CountActivitiesAsync(); + break; + default: + throw new ArgumentException( + "Must pass 'worker', 'execute-activity', 'start-activity', 'list-activities', or 'count-activities' as the single argument"); +} diff --git a/src/StandaloneActivity/README.md b/src/StandaloneActivity/README.md new file mode 100644 index 0000000..02add39 --- /dev/null +++ b/src/StandaloneActivity/README.md @@ -0,0 +1,36 @@ +# Standalone Activity + +This sample shows how to execute Activities directly from a Temporal Client, without a Workflow. + +For full documentation, see [Standalone Activities](https://docs.temporal.io/develop/dotnet/standalone-activities). + +### Sample directory structure + +- [MyActivities.cs](MyActivities.cs) - Activity definition with `[Activity]` attribute +- [Program.cs](Program.cs) - Worker, execute, start, list, and count commands + +### Steps to run this sample + +To run, first see [README.md](../../README.md) for prerequisites. Then, run the following from this directory +in a separate terminal to start the worker: + + dotnet run worker + +Then in another terminal, execute a standalone activity and wait for the result: + + dotnet run execute-activity + +Or start a standalone activity, get a handle, then wait for the result: + + dotnet run start-activity + +List standalone activity executions: + + dotnet run list-activities + +Count standalone activity executions: + + dotnet run count-activities + +Note: `list-activities` and `count-activities` are only available in the +[Standalone Activity prerelease CLI](https://github.com/temporalio/cli/releases/tag/v1.6.2-standalone-activity). diff --git a/src/StandaloneActivity/TemporalioSamples.StandaloneActivity.csproj b/src/StandaloneActivity/TemporalioSamples.StandaloneActivity.csproj new file mode 100644 index 0000000..52e6553 --- /dev/null +++ b/src/StandaloneActivity/TemporalioSamples.StandaloneActivity.csproj @@ -0,0 +1,7 @@ + + + + Exe + + + diff --git a/tests/StandaloneActivity/MyActivityTests.cs b/tests/StandaloneActivity/MyActivityTests.cs new file mode 100644 index 0000000..70bb662 --- /dev/null +++ b/tests/StandaloneActivity/MyActivityTests.cs @@ -0,0 +1,36 @@ +namespace TemporalioSamples.Tests.StandaloneActivity; + +using Temporalio.Client; +using Temporalio.Testing; +using Temporalio.Worker; +using TemporalioSamples.StandaloneActivity; +using Xunit; +using Xunit.Abstractions; + +public class MyActivityTests : TestBase +{ + public MyActivityTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact(Skip = "Standalone Activity is not yet supported by WorkflowEnvironment.StartTimeSkippingAsync")] + public async Task ExecuteActivityAsync_SimpleRun_Succeeds() + { + await using var env = await WorkflowEnvironment.StartTimeSkippingAsync(); + using var worker = new TemporalWorker( + env.Client, + new TemporalWorkerOptions("my-task-queue"). + AddActivity(MyActivities.ComposeGreetingAsync)); + await worker.ExecuteAsync(async () => + { + var result = await env.Client.ExecuteActivityAsync( + () => MyActivities.ComposeGreetingAsync(new ComposeGreetingInput("Hello", "World")), + new($"act-{Guid.NewGuid()}", worker.Options.TaskQueue!) + { + ScheduleToCloseTimeout = TimeSpan.FromSeconds(10), + }); + Assert.Equal("Hello, World!", result); + }); + } +} From 211c16c933b4d8f1d9d4d093d4210272430a9985 Mon Sep 17 00:00:00 2001 From: Andrew Yuan Date: Wed, 18 Mar 2026 09:13:44 -0700 Subject: [PATCH 2/6] SDK released, clean up deps --- Directory.Build.props | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index c007235..48f3761 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -19,15 +19,10 @@ - - - - - - + + + + From c5ce34d3a38b9824fb106c055b37dcc0495d5973 Mon Sep 17 00:00:00 2001 From: Andrew Yuan Date: Wed, 18 Mar 2026 09:15:27 -0700 Subject: [PATCH 3/6] don't remove existing comment --- Directory.Build.props | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 48f3761..937d9b0 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -23,11 +23,14 @@ - + - \ No newline at end of file + From 5a0a8b25198a3c5441cf09a9f96dfd4e00ec8785 Mon Sep 17 00:00:00 2001 From: Andrew Yuan Date: Wed, 18 Mar 2026 09:23:06 -0700 Subject: [PATCH 4/6] fix nexus changes, test csproj --- src/CounterInterceptor/MyCounterInterceptor.cs | 4 ++-- src/Mutex/Impl/MutexActivities.cs | 2 +- src/NexusCancellation/Caller/HelloCallerWorkflow.workflow.cs | 4 ++-- .../Caller/HelloCallerWorkflow.workflow.cs | 2 +- .../NexusContextPropagationInterceptor.cs | 2 +- src/NexusMultiArg/Caller/HelloCallerWorkflow.workflow.cs | 2 +- src/NexusSimple/Caller/EchoCallerWorkflow.workflow.cs | 2 +- src/NexusSimple/Caller/HelloCallerWorkflow.workflow.cs | 2 +- tests/TemporalioSamples.Tests.csproj | 1 + 9 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/CounterInterceptor/MyCounterInterceptor.cs b/src/CounterInterceptor/MyCounterInterceptor.cs index 22fb9fd..59704d5 100644 --- a/src/CounterInterceptor/MyCounterInterceptor.cs +++ b/src/CounterInterceptor/MyCounterInterceptor.cs @@ -136,8 +136,8 @@ internal ActivityInbound(MyCounterInterceptor root, ActivityInboundInterceptor n public override Task ExecuteActivityAsync(ExecuteActivityInput input) { - var id = ActivityExecutionContext.Current.Info.WorkflowId; - root.Increment(id, c => Interlocked.Increment(ref root.Counts[id].WorkflowActivityExecutions)); + var id = ActivityExecutionContext.Current.Info.WorkflowId!; + root.Increment(id, c => Interlocked.Increment(ref root.Counts[id!].WorkflowActivityExecutions)); return base.ExecuteActivityAsync(input); } } diff --git a/src/Mutex/Impl/MutexActivities.cs b/src/Mutex/Impl/MutexActivities.cs index eb13c8c..da40b01 100644 --- a/src/Mutex/Impl/MutexActivities.cs +++ b/src/Mutex/Impl/MutexActivities.cs @@ -31,7 +31,7 @@ await this.client.StartWorkflowAsync( new WorkflowOptions(input.MutexWorkflowId, activityInfo.TaskQueue) { StartSignal = RequestLockSignalName, - StartSignalArgs = new object[] { new LockRequest(activityInfo.WorkflowId, input.AcquireLockSignalName, input.LockTimeout), }, + StartSignalArgs = new object[] { new LockRequest(activityInfo.WorkflowId!, input.AcquireLockSignalName, input.LockTimeout), }, }); } } diff --git a/src/NexusCancellation/Caller/HelloCallerWorkflow.workflow.cs b/src/NexusCancellation/Caller/HelloCallerWorkflow.workflow.cs index c0ae571..5288362 100644 --- a/src/NexusCancellation/Caller/HelloCallerWorkflow.workflow.cs +++ b/src/NexusCancellation/Caller/HelloCallerWorkflow.workflow.cs @@ -15,13 +15,13 @@ public class HelloCallerWorkflow public async Task RunAsync(string name) { using var cts = CancellationTokenSource.CreateLinkedTokenSource(Workflow.CancellationToken); - var client = Workflow.CreateNexusClient(IHelloService.EndpointName); + var client = Workflow.CreateNexusWorkflowClient(IHelloService.EndpointName); // Concurrently execute an operation per language. var tasks = Languages.Select(lang => client.ExecuteNexusOperationAsync( svc => svc.SayHello(new IHelloService.HelloInput(name, lang)), - new NexusOperationOptions + new NexusWorkflowOperationOptions { // We set the CancellationType to WaitCancellationRequested, which means the caller waits // for the request to be received by the handler before proceeding with the cancellation. diff --git a/src/NexusContextPropagation/Caller/HelloCallerWorkflow.workflow.cs b/src/NexusContextPropagation/Caller/HelloCallerWorkflow.workflow.cs index afb484b..43f7e3e 100644 --- a/src/NexusContextPropagation/Caller/HelloCallerWorkflow.workflow.cs +++ b/src/NexusContextPropagation/Caller/HelloCallerWorkflow.workflow.cs @@ -11,7 +11,7 @@ public class HelloCallerWorkflow public async Task RunAsync(string name, IHelloService.HelloLanguage language) { Workflow.Logger.LogInformation("Caller workflow called by user {UserId}", MyContext.UserId); - var output = await Workflow.CreateNexusClient(IHelloService.EndpointName). + var output = await Workflow.CreateNexusWorkflowClient(IHelloService.EndpointName). ExecuteNexusOperationAsync(svc => svc.SayHello(new(name, language))); return output.Message; } diff --git a/src/NexusContextPropagation/NexusContextPropagationInterceptor.cs b/src/NexusContextPropagation/NexusContextPropagationInterceptor.cs index 9992a4b..62d5425 100644 --- a/src/NexusContextPropagation/NexusContextPropagationInterceptor.cs +++ b/src/NexusContextPropagation/NexusContextPropagationInterceptor.cs @@ -48,7 +48,7 @@ private class WorkflowOutbound( string headerKey, WorkflowOutboundInterceptor next) : WorkflowOutboundInterceptor(next) { - public override Task> StartNexusOperationAsync( + public override Task> StartNexusOperationAsync( StartNexusOperationInput input) { if (context.Value is { } value) diff --git a/src/NexusMultiArg/Caller/HelloCallerWorkflow.workflow.cs b/src/NexusMultiArg/Caller/HelloCallerWorkflow.workflow.cs index b9ed770..1a1d01c 100644 --- a/src/NexusMultiArg/Caller/HelloCallerWorkflow.workflow.cs +++ b/src/NexusMultiArg/Caller/HelloCallerWorkflow.workflow.cs @@ -8,7 +8,7 @@ public class HelloCallerWorkflow [WorkflowRun] public async Task RunAsync(string name, IHelloService.HelloLanguage language) { - var output = await Workflow.CreateNexusClient(IHelloService.EndpointName). + var output = await Workflow.CreateNexusWorkflowClient(IHelloService.EndpointName). ExecuteNexusOperationAsync(svc => svc.SayHello(new(name, language))); return output.Message; } diff --git a/src/NexusSimple/Caller/EchoCallerWorkflow.workflow.cs b/src/NexusSimple/Caller/EchoCallerWorkflow.workflow.cs index ff58029..d51d81c 100644 --- a/src/NexusSimple/Caller/EchoCallerWorkflow.workflow.cs +++ b/src/NexusSimple/Caller/EchoCallerWorkflow.workflow.cs @@ -8,7 +8,7 @@ public class EchoCallerWorkflow [WorkflowRun] public async Task RunAsync(string message) { - var output = await Workflow.CreateNexusClient(IHelloService.EndpointName). + var output = await Workflow.CreateNexusWorkflowClient(IHelloService.EndpointName). ExecuteNexusOperationAsync(svc => svc.Echo(new(message))); return output.Message; } diff --git a/src/NexusSimple/Caller/HelloCallerWorkflow.workflow.cs b/src/NexusSimple/Caller/HelloCallerWorkflow.workflow.cs index d99f38a..58b377e 100644 --- a/src/NexusSimple/Caller/HelloCallerWorkflow.workflow.cs +++ b/src/NexusSimple/Caller/HelloCallerWorkflow.workflow.cs @@ -8,7 +8,7 @@ public class HelloCallerWorkflow [WorkflowRun] public async Task RunAsync(string name, IHelloService.HelloLanguage language) { - var output = await Workflow.CreateNexusClient(IHelloService.EndpointName). + var output = await Workflow.CreateNexusWorkflowClient(IHelloService.EndpointName). ExecuteNexusOperationAsync(svc => svc.SayHello(new(name, language))); return output.Message; } diff --git a/tests/TemporalioSamples.Tests.csproj b/tests/TemporalioSamples.Tests.csproj index 9490d8a..5b0533e 100644 --- a/tests/TemporalioSamples.Tests.csproj +++ b/tests/TemporalioSamples.Tests.csproj @@ -40,6 +40,7 @@ + From 0bbf6d6cc527cf55fbad558b29b43a7a3afc0465 Mon Sep 17 00:00:00 2001 From: Andrew Yuan Date: Wed, 18 Mar 2026 09:24:08 -0700 Subject: [PATCH 5/6] small fix --- Directory.Build.props | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 937d9b0..7845df8 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -22,11 +22,13 @@ - + + + From 8d26b373a3130b1b4e79ba9c2d69fa4735e149c1 Mon Sep 17 00:00:00 2001 From: Andrew Yuan Date: Wed, 18 Mar 2026 10:35:06 -0700 Subject: [PATCH 6/6] make it explicit that this must exist for workflow activities --- src/CounterInterceptor/MyCounterInterceptor.cs | 5 +++-- src/Mutex/Impl/MutexActivities.cs | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/CounterInterceptor/MyCounterInterceptor.cs b/src/CounterInterceptor/MyCounterInterceptor.cs index 59704d5..a005be0 100644 --- a/src/CounterInterceptor/MyCounterInterceptor.cs +++ b/src/CounterInterceptor/MyCounterInterceptor.cs @@ -136,8 +136,9 @@ internal ActivityInbound(MyCounterInterceptor root, ActivityInboundInterceptor n public override Task ExecuteActivityAsync(ExecuteActivityInput input) { - var id = ActivityExecutionContext.Current.Info.WorkflowId!; - root.Increment(id, c => Interlocked.Increment(ref root.Counts[id!].WorkflowActivityExecutions)); + var info = ActivityExecutionContext.Current.Info; + var workflowId = info.IsWorkflowActivity ? info.WorkflowId! : throw new InvalidOperationException("Activity must be invoked from a workflow"); + root.Increment(workflowId, c => Interlocked.Increment(ref root.Counts[workflowId].WorkflowActivityExecutions)); return base.ExecuteActivityAsync(input); } } diff --git a/src/Mutex/Impl/MutexActivities.cs b/src/Mutex/Impl/MutexActivities.cs index da40b01..89ea11b 100644 --- a/src/Mutex/Impl/MutexActivities.cs +++ b/src/Mutex/Impl/MutexActivities.cs @@ -25,13 +25,14 @@ public MutexActivities(ITemporalClient client) public async Task SignalWithStartMutexWorkflowAsync(SignalWithStartMutexWorkflowInput input) { var activityInfo = ActivityExecutionContext.Current.Info; + var workflowId = activityInfo.IsWorkflowActivity ? activityInfo.WorkflowId! : throw new InvalidOperationException("Activity must be invoked from a workflow"); await this.client.StartWorkflowAsync( (MutexWorkflow mw) => mw.RunAsync(MutexWorkflowInput.Empty), new WorkflowOptions(input.MutexWorkflowId, activityInfo.TaskQueue) { StartSignal = RequestLockSignalName, - StartSignalArgs = new object[] { new LockRequest(activityInfo.WorkflowId!, input.AcquireLockSignalName, input.LockTimeout), }, + StartSignalArgs = new object[] { new LockRequest(workflowId, input.AcquireLockSignalName, input.LockTimeout), }, }); } }