An Azure service that provides an event-driven serverless compute platform.
Root Cause
In your OrchestratorSemaphore entity, you inject DurableTaskClient via DI and call client.RaiseEventAsync(instanceId, eventName) from within the entity to send events back to orchestrations. This is not a supported communication pattern.
TaskEntityContext intentionally exposes only two outbound operations:
-
SignalEntity— send one-way messages to other entities -
ScheduleNewOrchestration— start new orchestrations
There is no RaiseEvent capability on the entity context by design. Calling DurableTaskClient.RaiseEventAsync from inside an entity bypasses the entity framework — it's an external gRPC call that goes outside the entity's transactional execution model. This means:
- The call is not transactional — if the entity operation fails and its state rolls back, the
RaiseEventAsynchas already been sent and cannot be reverted. - The NotFound error you observed (
No instance with ID '...' was found) comes from the backend when trying to deliver the event through the external API. While the orchestration does exist, the external gRPCRaiseEventAPI path may have different visibility constraints than internal framework messaging, particularly for sub-orchestrations.
Recommended Fix
Replace the SignalEntity + WaitForExternalEvent + RaiseEventAsync pattern with CallEntityAsync<T>, which is the built-in two-way (request-response) communication from orchestrations to entities:
// In the orchestration — replaces SignalEntity + WaitForExternalEvent
// In the entity — much simpler, no DurableTaskClient needed
With this approach:
- The return value from
CallEntityAsync<bool>is the communication back to the orchestration — no external events needed - Entity operations run serially (single-threaded by design), so no race conditions on the counter
- No need for
PendingAcks,ResendIfNoAck,LeaseVersions, or any of the other compensating mechanisms - No
DurableTaskClientinjection in the entity
References
-
CallEntityAsyncAPI:TaskOrchestrationEntityFeature.cs - Working sample using
CallEntityAsyncin a standalone .NET app (not Azure Functions):LargePayloadConsoleApp/Program.cslines 75-78 — demonstratesctx.Entities.CallEntityAsync<int>(...)withEnableEntitySupport = true - Entity access patterns doc: Orchestration signals and calls an entity
- Entity developer guide (.NET isolated): Orchestration first signals then calls entity
Key quote from the docs:
Only orchestrations can call entities and get a response, which can be a return value or an exception. Client functions that use the client binding can only signal entities.
Root Cause
In your
OrchestratorSemaphoreentity, you injectDurableTaskClientvia DI and callclient.RaiseEventAsync(instanceId, eventName)from within the entity to send events back to orchestrations. This is not a supported communication pattern.TaskEntityContextintentionally exposes only two outbound operations:
SignalEntity— send one-way messages to other entitiesScheduleNewOrchestration— start new orchestrations There is noRaiseEventcapability on the entity context by design. CallingDurableTaskClient.RaiseEventAsyncfrom inside an entity bypasses the entity framework — it's an external gRPC call that goes outside the entity's transactional execution model. This means:
- The call is not transactional — if the entity operation fails and its state rolls back, the
RaiseEventAsynchas already been sent and cannot be reverted.- The NotFound error you observed (
No instance with ID '...' was found) comes from the backend when trying to deliver the event through the external API. While the orchestration does exist, the external gRPCRaiseEventAPI path may have different visibility constraints than internal framework messaging, particularly for sub-orchestrations.Recommended Fix
Replace the
SignalEntity+WaitForExternalEvent+RaiseEventAsyncpattern withCallEntityAsync<T>, which is the built-in two-way (request-response) communication from orchestrations to entities:// In the orchestration — replaces SignalEntity + WaitForExternalEvent var entityId = new EntityInstanceId("LlmConcurrencyLimiter", "global-llm-limiter"); // Poll until a slot is available while (true) { bool acquired = await context.Entities.CallEntityAsync<bool>(entityId, "TryAcquire"); if (acquired) break; await context.CreateTimer(context.CurrentUtcDateTime.AddSeconds(2), CancellationToken.None); } try { return await context.CallActivityAsync<T>(activityName, input, options); } finally { await context.Entities.SignalEntityAsync(entityId, "Release"); }// In the entity — much simpler, no DurableTaskClient needed public class LlmConcurrencyLimiter : TaskEntity<SemaphoreState> { public bool TryAcquire() { if (this.State.Current < this.State.MaxConcurrent) { this.State.Current++; return true; // returned directly to the calling orchestration } return false; } public void Release() { if (this.State.Current > 0) this.State.Current--; } }With this approach:
- The return value from
CallEntityAsync<bool>is the communication back to the orchestration — no external events needed- Entity operations run serially (single-threaded by design), so no race conditions on the counter
- No need for
PendingAcks,ResendIfNoAck,LeaseVersions, or any of the other compensating mechanisms- No
DurableTaskClientinjection in the entityReferences
CallEntityAsyncAPI:TaskOrchestrationEntityFeature.cs- Working sample using
CallEntityAsyncin a standalone .NET app (not Azure Functions):LargePayloadConsoleApp/Program.cslines 75-78 — demonstratesctx.Entities.CallEntityAsync<int>(...)withEnableEntitySupport = true- Entity access patterns doc: Orchestration signals and calls an entity
- Entity developer guide (.NET isolated): Orchestration first signals then calls entity Key quote from the docs:
Only orchestrations can call entities and get a response, which can be a return value or an exception. Client functions that use the client binding can only signal entities.