如何在异步操作后返回UI线程

时间:2013-03-12 21:03:42

标签: c# windows-8 windows-phone-8 task-parallel-library portable-class-library

我正在使用MVVM模式为Windows Store和Windows Phone 8开发相同应用程序的两个版本。每个应用都有自己的视图。 Model和ViewModel在Portable Class Libraray中共享。我正在使用TPL任务在模型中进行异步操作。由于可移植类库的限制,我不能使用async和await关键字。

任务完成后,我想回到UI线程并更新一些属性(这将导致ViewModel和View也更新)。

在我看来,这似乎是一种非常普遍的情况,所以我有点困惑,为什么事情变得如此艰难。

我尝试了两种不同的方法:

一个(不起作用)

在开始操作之前保存对调度程序的引用

TaskScheduler scheduler = TaskScheduler.FromCurrentSynchronizationContext();

然后将其传递给ContinueWith方法。

myTask.ContinueWith(t => myTaskCompleted(t.Result), scheduler);

这对我来说似乎是一个很好的解决方案但不起作用。 myTaskCompleted仍在另一个线程中执行。

第二

现在我尝试使用

Dispatcher.RunAsync(CoreDispatcherPriority.Normal, handler);

因为我无法直接使用PCL中的Dispatcher,所以我将对它的引用(隐藏在包装器中)传递给模型中的几乎每个对象。 (就像在这个answer)这最终有效,但它非常复杂和丑陋。

所以我的问题是:

  1. 回到Portable Class Libraray中的UI线程的推荐方法是什么?
  2. 我在尝试一个
  3. 时犯了什么错误

    我知道这个话题已经有很多问题,但遗憾的是没有什么能真正解决我的问题。

1 个答案:

答案 0 :(得分:2)

TPL将使用来自线程池的线程,并且UI线程是不在线程池上的“主线程”,并且永远不可用于运行任务。使用 ContinueWith 函数将从线程池中获取另一个线程来执行您的代码。您遇到的问题的核心在于,Windows Phone不会对属性更改进行排队,并会直接尝试更新视图。在代码的某处,你应该有一个 Changed 函数来广播属性的变化。我会用我的:

public void Changed(string Key) {
    // Check if the property changed has subscribers.
    if (PropertyChanged != null) {
        // Invoke the property changed.
        PropertyChanged(this, new PropertyChangedEventArgs(Key));
    }
}

这个 Changed 函数在WPF应用程序下可以正常工作,因为WPF会对属性更改进行排队,并在下一次UI帧更新时处理它们。由于Windows Phone没有,我们需要建立一个模式来在运行时更改此行为。我创建了一个名为 Dispatcher 的属性,我允许在运行时设置它。我的所有广播现已从已更改更改为 Dispatcher

private Action<string> _Dispatcher;
public Action<string> Dispatcher {
    get {
        if (_Dispatcher == null) {
            return Changed;
        }
        return _Dispatcher;
    }
    set {
        _Dispatcher = value;
    }
}

现在我们可以在Windows Phone应用程序中的运行时更改 Dispatcher 。我们需要编写一个函数来推迟更改,直到UI线程处于活动状态以广播更改。我在扩展中执行了此操作,因此在ViewModel上附加UI线程安全性会更容易一些。运行时更改将仅使用Windows Phone Dispatcher 来在UI线程上进行scheduele广播。实施如下:

public static void Attach(this ViewModelStore ViewModelStore, DependencyObject DependencyObject) {
    // Set the changed event dispatcher.
    ViewModelStore.Dispatcher = (Key) => {
        // Begin invoking of an action on the UI dispatcher.
        DependencyObject.Dispatcher.BeginInvoke(() => {
            // Raise the changed event.
            ViewModelStore.Changed(Key);
        });
    };
}

ViewModelStore 是我用于所有视图模型的泛型类,因此该函数允许我将线程安全广播机制附加到所有视图模型。 DependencyObject 是一个UI组件,例如视图。现在,您真正需要做的就是在视图模型上调用 attach

ProviderViewModel.Attach(this); // This is inside a Page.

所有广播都没有委托给UI线程,并调用UI进入的下一帧并相应地更新所有内容。您不必担心这样的线程安全,但您需要记住在Windows Phone应用程序中附加视图模型的新实例。如果还有其他问题,请告诉我,祝你好运!