WinRT中如何在UI线程上运行代码

67

如何在WinRT(Windows 8 Metro)上运行UI线程的代码?

Invoke方法不存在。


8
未来的读者请注意:如果您的应用程序有多个窗口,那么就会有多个UI线程和调度程序。 - Filip Skakun
6个回答

81

直接从非 UI 线程获取 CoreWindow 更容易。以下代码将在任何情况下都有效,即使 GetForCurrentThread()Window.Current 返回 null

CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
    <lambda for your code which should run on the UI thread>);
例如:

for example:

CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal,
    () =>
    {
        // Your UI update code goes here!
    });

您需要引用 Windows.ApplicationModel.Core 命名空间:

using Windows.ApplicationModel.Core;

当我从UI线程访问它时,会出现System.NotImplementedException异常。 - Naren
从来没有在这里出现过这个异常。它可能来自其他地方,比如在您的块指令内部。 - Cœur
当我尝试访问Dispatcher时,我遇到了这个异常。我还没有使用Dispatcher执行任何代码。 - Naren
所以 CoreApplication.MainView.CoreWindow.Dispatcher 对你无效?在尝试其他解决方案时,是否遇到了异常?Windows.UI.Core.CoreWindow.GetForCurrentThread().Dispatcherthis.Dispatcher - Cœur
我也尝试过Windows.UI.Core.CoreWindow.GetForCurrentThread().Dispatcher,但它也抛出了异常。我猜this.Dispatcher在Windows.Runtime中不起作用。我已将其发布为单独的问题 - Naren
根据另一个线程,你的解决方案使用了 Deployment.Current.Dispatcher - Cœur

69

使用:

在您的UI线程中执行:

var dispatcher = Windows.UI.Core.CoreWindow.GetForCurrentThread().Dispatcher;

根据您的背景(非UI线程)

dispatcher.RunAsync(DispatcherPriority.Normal, 
    <lambda for your code which should run on the UI thread>);

这应该适用于 CP 和更高版本的构建。


5
有没有办法在非UI线程上获取调度程序?目前我从CoreWindow.GetForCurrentThread()中获取到null。 - Grigory
3
调度程序与 UI 线程绑定,因此您需要在 UI 线程上检索调度程序。一旦检索到调度程序,您可以将其记住。如果您在 XAML 应用程序中,则大多数 UI 对象都具有可以使用的调度程序成员。 - Larry Osterman
1
您的原始代码在 UI 线程上运行,任何基于 XAML 的事件回调也是如此。如果您从 UI 线程使用 "await" 关键字,则在 "await" 关键字后面的所有代码都将在 UI 线程上运行。但是,来自其他 WinRT API 的事件回调可能不在 UI 线程上,如果它们不在 UI 线程上,则可以使用调度程序返回到 UI 线程。如果您收到 WrongThreadException,则可能意味着 WinRT API 在非 UI 线程上进入。 - Larry Osterman
这个方法的示例 - Korki Korkig
4
@Larry:你可以从后台线程获取 Dispatcher,请参见此处:https://dev59.com/M2vXa4cB1Zd3GeqPFxt2#25760799 CoreApplication.MainView.CoreWindow.Dispatcher - eFloh
显示剩余2条评论

8

使用:

this.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => Frame.Navigate(typeof(Welcome), this));

对我来说它有效。


5
这并不确保在UI线程上运行它,只有当“this”是UI上下文中的一个对象时才会这样做。 - Luke Kim

5
我认为这是一种更简单的方式。 获取与UI相关联的TaskScheduler。
    var UISyncContext = TaskScheduler.FromCurrentSynchronizationContext();

然后开始一个新任务,在上述的UISyncContext上。

    Task.Factory.StartNew(() => { /* Do your UI stuff here; */}, new System.Threading.CancellationToken(), TaskCreationOptions.PreferFairness, UISyncContext);

FYI:我收到了一个InvalidOperationException“当前SynchronizationContext不能用作TaskScheduler”。 - Akku
我也收到了一个InvalidOperation错误。 - LowDev1
1
您的同步上下文为 null。这就是您遇到该异常的原因。我的代码没有问题。 - Deeb

0
在 UWP 上,我尝试设置 CaptureElement 控件(在 XAML 中定义)的 Source 属性时遇到了问题,它抱怨在不同的线程上准备,即使我试图通过 Page_Loaded 事件处理程序调用的代码来设置它。最终,我使用以下方法解决了这个问题:
previewControl.Dispatcher.TryRunAsync(CoreDispatcherPriority.Normal, () => {
   previewControl.Source = _mediaCapture;
}).GetAwaiter().GetResult();

0

DispatcherTimer也是一个选择。

我在必须在Xaml设计器中运行的代码中使用它(CoreWindow.Dispatcher等在UWP设计器中不可用)。

var localTimer = new DispatcherTimer
{
    Interval = TimeSpan.FromMilliseconds(0)
};
localTimer.Tick += (timer, e) =>
{
    (timer as DispatcherTimer).Stop();
    action();
};
localTimer.Start();

免责声明:
我应该指出,如果其他方法都失败了,这应该是最后的选择。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接