Search Unity

Execution order of main code vs. delegate invokes

Discussion in 'Scripting' started by waldgeist, Feb 3, 2021.

  1. waldgeist

    waldgeist

    Joined:
    May 6, 2017
    Posts:
    388
    I noticed a strange phenomenon I'd like to get a confirmation about: If I invoke a delegate, it seems as if the invocation is being delayed until the main thread is idle.

    So, if I have this code snippet:

    Code (CSharp):
    1. Debug.Log("before");
    2. myDelegate?.Invoke();
    3. Debug.Log("after");
    and the function bound to the delegate itself prints "delegate", I get this output:

    before
    after
    delegate

    instead of

    before
    delegate
    after

    Is this behavior intended? I always thought that event handlers were fired immediately. Is there any documentation about this? I only found infos that the execution order of multicast delegates is undefined, but that's not my question here.
     
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,911
    My understanding is also that delegates act much like normal function calls do - they run immediately, and to completion, before returning control to the caller. Your finding here is very strange to me! I'm going to go test it out myself.
     
  3. mopthrow

    mopthrow

    Joined:
    May 8, 2020
    Posts:
    348
    If delegates functioned as you say, it would break a lot of things. The Func delegate would cease to be useful for example, in the case where you wanted to work with some returned value after its invocation was handled elsewhere.

    I suspect that there's some other execution order issue going on in your code.


    This ^ seems correct. A quick test if you want:


    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. public class DelegateExecutionOrderTests : MonoBehaviour
    5. {
    6.     Action ActionTest;
    7.     Func<bool> FuncTest;
    8.     event EventHandler<EventArgs> EventTest;
    9.     delegate void DelegateTest();
    10.  
    11.     // Start is called before the first frame update
    12.     void Start()
    13.     {
    14.         DelegateTest del = new DelegateTest(DelegateTestHandler);
    15.         Debug.Log("first delegate");
    16.         del?.Invoke();
    17.         Debug.Log("last delegate");
    18.  
    19.         EventTest += EventTestHandler;
    20.         Debug.Log("first event");
    21.         EventTest?.Invoke(this, EventArgs.Empty);
    22.         Debug.Log("last event");
    23.  
    24.         ActionTest = ActionTestHandler;
    25.         Debug.Log("first action");
    26.         ActionTest?.Invoke();
    27.         Debug.Log("last action");
    28.  
    29.         FuncTest = FuncTestHandler;
    30.         Debug.Log("first func");
    31.         bool myBool = FuncTest.Invoke();
    32.         Debug.Log("last func");
    33.     }
    34.  
    35.     void DelegateTestHandler()
    36.     {
    37.         Debug.Log("delegate test");
    38.     }
    39.  
    40.     void EventTestHandler(object sender, EventArgs e)
    41.     {
    42.         Debug.Log("event test");
    43.     }
    44.  
    45.     void ActionTestHandler()
    46.     {
    47.         Debug.Log("action test");
    48.     }
    49.  
    50.     bool FuncTestHandler()
    51.     {
    52.         Debug.Log("func test");
    53.         return true;
    54.     }
    55. }
     
    Last edited: Feb 3, 2021
    Bunny83 likes this.
  4. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,911
    Seems to work fine for me:
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. public class DelegateTest : MonoBehaviour
    5. {
    6.     // Start is called before the first frame update
    7.     void Start()
    8.     {
    9.         Action a = () => { Debug.Log("delegate"); };
    10.         Debug.Log("before");
    11.         a?.Invoke();
    12.         Debug.Log("after");
    13.     }
    14. }
    upload_2021-2-3_13-27-24.png
     
    Last edited: Feb 3, 2021
  5. VolodymyrBS

    VolodymyrBS

    Joined:
    May 15, 2019
    Posts:
    150
    What code you invoke by
    myDelegate
    ?
    I could think about 2 cases how it could happen:
    1. Code that
    myDelegate
    execute make some delay (async await + some code that make Delay?);
    2. Using
    BeginInvoke
    instead of
    Invoke
    (very unlikely I think)
     
    Last edited: Feb 3, 2021
  6. waldgeist

    waldgeist

    Joined:
    May 6, 2017
    Posts:
    388
    I actually meant a multicast delegate that is added like this:

    Code (CSharp):
    1. myDelegate += MyMethod;
    Should have mentioned it. Maybe there's a difference between lambda delegates that are called directly and these kind of event-driven callbacks?
     
  7. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,006
    That makes no difference. A MultcastDelegate just runs all its targets in the order they have been added / are defined in the internal call list. A multicast delegate literally just has an array of normal delegates. When you add a method to a multicast delegate it just creates a new array, copies the old elements over and adds the new delegate to that array.

    Again, what are you doing inside that delegate? Is is actually a normal method? Keep in mind that starting coroutines or running something on a different thread of course breaks the normal control flow. Though a "normal" method / lambda expression / anonymous method will for sure complete before the calling code can continue.
     
  8. waldgeist

    waldgeist

    Joined:
    May 6, 2017
    Posts:
    388
    I am pretty sure it was a regular function call, no Coroutines involved.
     
  9. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    4,006
    Have you actually confirmed that your "delegate" log comes from this particular call of your delegate? Note that you use the conditional operator
    ?.
    which means the delegate may not even be called because there might not be any subscription to the delegate. So your "delegate" log you see may originate from a different part of your project. There are many possible cases. Though without knowing the exact code it seems pointless to discuss this further. Unity's scripting side runs in a single thread and therefore everything is executed in order. The only exceptions are when you start your own thread (implicitly or explicitly) or you start a coroutine inside your delegate and the log comes from inside that coroutine after it "waited".

    Note that some web communication frameworks provide you methods where you can specify a callback when its done. Those usually start a new thread or use a thread pool internally. So the passed in callback won't execute immediately as the actual web request is carried out on a seperate thread behind the scenes. Again we're stuck here without knowing what you're doing inside the method behind the delegate. All we can assure you is that a delegate is executed synchronously just like any other ordinary method.