简体   繁体   中英

What is the order of execution for Dispatcher operations(Invoke/BeginInvoke)

Here is the test code in WPF:

            Action ac0 = delegate
            {
                Console.WriteLine("action-beginInvoke-enter");
                Application.Current.Dispatcher.BeginInvoke(new Action(delegate
                {
                    Console.WriteLine("dispatcher begin invoke code");
                }));
                Console.WriteLine("action-beginInvoke-exit");
            };
            ac0.BeginInvoke(null, null);
            Console.WriteLine("ui thread sleep before");
            Thread.Sleep(1000);// ensure the ac0 is done
            Console.WriteLine("ui thread sleep after");
            Application.Current.Dispatcher.Invoke(new Action(delegate
            {
                Console.WriteLine("dispatcher invoke code");
            }));
            Console.WriteLine("ui thread exit");

Output:
ui thread sleep before
action-beginInvoke-enter
action-beginInvoke-exit
ui thread sleep after
dispatcher invoke code
ui thread exit
dispatcher begin invoke code

Then, I remove the Invoke code into another project and compile as a.dll:

            Action ac0 = delegate
            {
                Console.WriteLine("action-beginInvoke-enter");
                Application.Current.Dispatcher.BeginInvoke(new Action(delegate
                {
                    Console.WriteLine("dispatcher begin invoke code");
                }));
                Console.WriteLine("action-beginInvoke-exit");
            };
            ac0.BeginInvoke(null, null);
            Console.WriteLine("ui thread sleep before");
            Thread.Sleep(1000);// ensure the ac0 is done
            Console.WriteLine("ui thread sleep after");
            Test.WriteLine();// Test is a static class from dll
            Console.WriteLine("ui thread exit");

Here is the Test.WriteLine:

        Application.Current.Dispatcher.Invoke(new Action(delegate
        {
            Console.WriteLine("dispatcher invoke code");
        }));

the output is:
ui thread sleep before
action-beginInvoke-enter
action-beginInvoke-exit
ui thread sleep after
dispatcher begin invoke code
dispatcher invoke code
ui thread exit

I'm trying to figure out the order of execution for Dispatcher operations. As I know the UI thread is busy until executing to the last line. How could it execute the code "dispatcher begin invoke code" before that? And the code is the same except the Dispatcher.Invoke is removed into a dll. Why their output is different?

The short answer to your question - what is the execution order - is, the way you have it, it's unpredictable. But we can make it predictable by using TaskCompletionSource .

Long answer - you have a couple different things going on here, which I think is the source of your unexpected results.

First, you are using Action.BeginInvoke on ac0 . This invokes ac0 asynchronously, ie in the background, probably (if not always?) on a different thread than the one calling BeginInvoke . Unless you take specific steps (which I'll outline below), the execution order of the delegate versus your other code is undefined and may appear random.

Second, inside the delegate ac0 (which, again, is running asynchronously), you are then calling Dispatcher.Invoke with another delegate. Let's call this ac0_1 . ac0_1 is what actually writes to the console. Dispatcher.Invoke is different from Action.BeginInvoke in that anything you give to Dispatcher.Invoke always executes on a predictable thread - the UI thread. (With Action.BeginInvoke you don't know what thread it'll get executed on, except it (certainly?) won't be the UI thread). Dispatcher.Invoke is also different in that it doesn't return until the delegate has been completed - meaning it blocks the calling thread - which makes it very dangerous to mix with asynchronous operations.

So by mixing Dispatcher.Invoke and Action.BeginInvoke , you're mixing apples and oranges and are rarely going to get predictable results. Thus, unless you want to deal with an unmanageable chain of callbacks, and/or a high potential for total application locks, the best way IMO to guarantee code executes in the order you expect it to is to use TaskCompletionSource . It would work like this:

        TaskCompletionSource<object> task1 = new TaskCompletionSource<object>();        
        Action ac0 = delegate
        {
           Application.Current.Dispatcher.InvokeAsync(new Action(delegate
           {
              Console.WriteLine("ac0");
              task1.SetResult(null);
           }));
        };
        ac0.BeginInvoke(null, null);
        await task1.Task; // ensure the ac0 is done

        TaskCompletionSource<object> task2 = new TaskCompletionSource<object>();
        Application.Current.Dispatcher.InvokeAsync(new Action(delegate
        {
           Console.WriteLine("ac1");
           task2.SetResult(null);
        }));
        await task2.Task; 
        Console.WriteLine("over");

What is happening under the hood is that everything after await task1.Task essentially becomes a callback that gets executed when task1.SetResult(null) is called, but it still allows you to write code with an apparent linear execution flow.

You'll also notice I changed Dispatcher.Invoke to Dispatcher.InvokeAsync . Why? To avoid the possibility of locking. We don't know what thread is actually active when your first call to Dispatcher.Invoke is executed, but whatever it is will be blocked until the delegate is completed. Since we really virtually NEVER want to block threads (and thus virtually never want to use Thread.Sleep), it's best to always use the async versions of these methods and use TaskCompletionSource to get an awaitable Task to ensure the item has completed before continuing.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM