1

我正在尝试使用 MVVM 模式实现我的第一个应用程序。我已经设法让大多数事情正常工作,但现在我面临以下(恕我直言很常见)场景的问题:

按下Button(视图)将调用方法(模型)。使用ICommand(ViewModel)这很容易。但是如果必须执行耗时的操作怎么办?

我当前的解决方案要求我实现一个WorkQueue包含WorkQueueItems. 有一个与之关联的WorkQueue线程,它执行WorkQueueItems。每个WorkQueueItem都有 a Name, aStatus和 aProgress在执行期间更新。每个Window都有自己的WorkQueue可视化为StatusBar

我的问题: ViewModel 如何找到合适的WorkQueue?我是否必须将 传递WorkQueue给我创建的每个 ViewModel(这真的很烦人)?或者还有其他我可以使用的机制吗?

我对RoutedCommands 不是很熟悉 - 基本概念似乎朝着这个方向发展。希望看到的是一个解决方案,我可以将 a 绑定WorkQueueItem到一个命令/事件,然后它会冒泡到包含Window它被添加到Window's 的地方WorkQueue

我也考虑过制作WorkQueue一个单身人士——但这只有在我一次只有一个Window的情况下才有效。

4

2 回答 2

4

使用后来的 .Net Frameworks (4.0+) 和 WPF,您可以利用该System.Threading.Tasks库在后台提供大量此类工作。

如果说您的命令需要更新视图模型上的属性,但它必须等待信息,您只需启动一个任务来执行 IO:

this.FindDataCommand = new RelayCommand<string>(
    /* ICommand.Execute */
    value =>
    {
        Task.Factory
            .StartNew<IEnumerable<Foo>>(() => FindData(value))
            .ContinueWith(
                task =>
                {
                    this.foundData.Clear();
                    this.foundData.AddRange(task.Result);
                },
                TaskScheduler.FromCurrentSynchronizationContext());
    },

    /* ICommand.CanExecute */
    value => !String.IsNullOrWhitespace(value));

将其分解为可管理的部分,我们将开始一个调用 some method的新任务IEnumerable<Foo> FindData(string)。这是您一直编写的简单而无聊的同步代码。可能它已经存在于您的视图模型中!

接下来,我们告诉框架在完成使用时启动一项新任务ContinueWith,但改为在WPF 调度程序上执行。这使您可以避免 UI 元素的跨线程问题的麻烦。

您可以使用辅助类扩展它以进行监视:

public class TaskManager
{
    private static ConcurrentDictionary<Dispatcher, TaskManager> _map
        = new ConcurrentDictionary<Dispatcher, TaskManager>();

    public ObservableCollection<WorkItem> Running
    {
        get;
        private set;
    }

    public TaskManager()
    {
        this.Running = new ObservableCollection<WorkItem>();
    }

    public static TaskManager Get(Dispatcher dispatcher)
    {
        return _map.GetOrAdd(dispatcher, new TaskManager());
    }
    // ...

在 XAML 中使用此类将类似于将其实例添加到您的 Window 中ViewModel

public TaskManager CurrentTaskManager
{
    get { return TaskManager.Get(Dispatcher.CurrentDispatcher); }
}
// <StatusBarItem Content="{Binding CurrentTaskManager.Running.Count}" />

然后,您将向 TaskManager 添加一个方法来处理向 Running 集合添加任务和从 Running 集合添加任务:

    public Task<TResult> StartNew<TResult>(Func<TResult> work)
    {
         var task = Task.Factory
                        .StartNew<TResult>(work);

         // build our view model
         var workItem = new WorkItem(task);
         this.Running.Add(workItem);

         // Pass the result back using ContinueWith
         return task.ContinueWith(
             t => { this.Running.Remove(workItem); return t.Result; },
             TaskScheduler.FromCurrentSynchronizationContext());
    }

现在我们简单地改变我们的FindDataCommand实现:

TaskManager.Get(Dispatcher.CurrentDispatcher)
           .StartNew<IEnumerable<Foo>>(() => FindData(value))
           .ContinueWith(
               task =>
               {
                   this.foundData.Clear();
                   this.foundData.AddRange(task.Result);
               },
               TaskScheduler.FromCurrentSynchronizationContext());

该类WorkItem可以将类上的属性公开Task给 UI,或者可以将其扩展为封装 aCancellationToken以支持将来的取消。

于 2012-04-16T20:09:00.380 回答
1

我不确定我的问题是否正确,但我觉得在Dispatcher中使用 buil可以解决您的问题,并且您不需要WorkQueue手动实现,因为 Dispatcher 为您实现了这样一个队列并能够将“工作项”调度到 UI/任何thred 使用预定义的一组优先级。Dispatcher.Invoke()您可以使用或同步或异步执行操作Dispatcher.BeginInvoke()

有用的链接:

于 2012-04-16T19:49:57.197 回答