Deferred Engine Events

*Questo contenuto sarà presto disponibile nella lingua selezionata.

The Workspace.SignalBehavior property controls whether event handlers are fired immediately or deferred. We recommend the Enum.SignalBehavior.Deferred option, which helps improve the performance and correctness of the engine. The event handlers for deferred events are resumed at the next resumption point, along with any newly triggered event handlers.

The following diagram compares the Immediate event behavior and the Deferred event behavior.

  • With the Immediate behavior, if an event triggers another event, the second event handler fires immediately.
  • With the Deferred behavior, the second event is added to the back of a queue and run later.

The total time taken does not change, but the ordering is different.

A comparison of three event handlers firing with Immediate and Deferred behavior

"Re-entrancy" prevents events from continuously firing one another when they reach a certain depth. The current limit for this is 10.

Deferred Event Benefits

The Immediate behavior has some disadvantages. For every instance added to your game, property that changes, or some other trigger that is invoked, the engine needs to run Lua code before anything else happens.

  • To change 1,000 properties, 1,000 snippets of code potentially need to run after each change.
  • Strange, hard-to-diagnose bugs can occur, such as a removing event firing before something was even added.
  • Performance-critical systems can fire events requiring them to yield back and forth to Lua.
  • Event handlers can make changes to the place or trigger other events any time an event is fired.
  • An event can fire multiple times despite being redundant, such as a property changing twice.

By having specific portions of the engine life cycle in which Lua can run, the engine can gain improved performance by using a number of assumptions:

  • Performance-critical systems don't need to yield to Lua, which leads to performance gains.
  • Unless the engine itself changes it, the place never changes outside of a resumption point.

Resumption Points

After being deferred, an event handler is resumed at the next resumption point. Currently, the set of resumption points includes:

Common Affected Code Patterns

With remote events, the following examples either stop working correctly or have subtly different behavior; they rely on events being resumed immediately.

Triggering and Catching Events Mid-Execution

In this example, false is always returned when deferred events are enabled because the callback has not run. To work correctly, the thread must yield until at least when the event should have fired.


local success = false
event:Connect(function ()
success = true
end)
doSomethingToTriggerEvent() -- Causes `event` to fire
return success

Listening for the First Occurrence of an Event


connection = event:Connect(function ()
connection:Disconnect()
-- do something
end)

With deferred events enabled, multiple event handler invocations can be queued before you disconnect from the event. Calling Disconnect() drops all pending event handler invocations—the same behavior that exists for immediate events.

Alternatively, use Once() as a more convenient method for connecting to an event that you only need the first invocation of.

Events That Change Ancestry or Properties

Deferred events cause events that handle a change in ancestry or a property to fire after the ancestry or property is changed:


local part = Instance.new("Part", workspace)
local function onPartDestroying()
print("In signal:", part:GetFullName(), #part:GetChildren())
end
part.Destroying:Connect(onPartDestroying)
part:Destroy()

Because Destroy() works immediately after the script that called it yields, the instance has already been destroyed by the time onPartDestroying() is called. For more examples, see Instance.Destroying.