微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Dispatcher 操作的执行顺序是什么Invoke/BeginInvoke

如何解决Dispatcher 操作的执行顺序是什么Invoke/BeginInvoke

这是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");

输出
ui线程先休眠
动作开始调用输入
动作开始调用退出
ui线程在
之后睡眠 调度员调用代码
ui线程退出
调度员开始调用代码

然后,我将 Invoke 代码移到另一个项目中并编译为 .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");

这是 Test.WriteLine:

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

输出为:
ui线程先休眠
动作开始调用输入
动作开始调用退出
ui线程在
之后睡眠 调度员开始调用代码
调度员调用代码
ui线程退出

我正在尝试找出 dispatcher 操作的执行顺序。据我所知,UI 线程很忙,直到执行到最后一行。在此之前它如何执行代码“调度程序开始调用代码”?代码是相同的,除了 dispatcher.Invoke 被删除一个 dll 中。为什么他们的输出不同?

解决方法

对您的问题的简短回答 - 执行顺序是什么 - 是您拥有它的方式,它是不可预测的。但是我们可以通过使用 TaskCompletionSource 使其可预测。

答案很长——这里有几件不同的事情,我认为这是您意外结果的来源。

首先,您在 Action.BeginInvoke 上使用 ac0。这会异步调用 ac0,即在后台,可能(如果不总是?)在与调用 BeginInvoke 不同的线程上。除非您采取特定步骤(我将在下面概述),否则委托与其他代码的执行顺序是未定义的,并且可能看起来是随机的。

其次,在委托 ac0(同样是异步运行的)中,您将使用另一个委托调用 Dispatcher.Invoke。我们称之为ac0_1ac0_1 是实际写入控制台的内容。 Dispatcher.InvokeAction.BeginInvoke 的不同之处在于,您提供给 Dispatcher.Invoke 的任何内容始终在可预测的线程(UI 线程)上执行。 (使用 Action.BeginInvoke 你不知道它将在哪个线程上执行,除了它(当然?)不会是 UI 线程)。 Dispatcher.Invoke 的不同之处还在于它在委托完成之前不会返回 - 这意味着它会阻塞调用线程 - 这使得与异步操作混合使用非常危险。

因此,通过混合 Dispatcher.InvokeAction.BeginInvoke,您将苹果和橙子混合在一起,并且很少会获得可预测的结果。因此,除非您想处理无法管理的回调链和/或整个应用程序锁定的高潜力,否则 IMO 保证代码按您期望的顺序执行的最佳方法是使用 TaskCompletionSource。它会像这样工作:

        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");

幕后发生的事情是,await task1.Task 之后的所有内容本质上都变成了在调用 task1.SetResult(null) 时执行的回调,但它仍然允许您编写具有明显线性执行流程的代码。

您还会注意到我将 Dispatcher.Invoke 更改为 Dispatcher.InvokeAsync。为什么?避免锁死的可能性。当您第一次调用 Dispatcher.Invoke 时,我们不知道哪个线程实际处于活动状态,但无论它是什么,都会被阻塞,直到委托完成。由于我们实际上从不想阻塞线程(因此实际上从不想使用 Thread.Sleep),因此最好始终使用这些方法的异步版本并使用 TaskCompletionSource 来获得可等待的 Task在继续之前确保项目已完成。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。