如何实现后台工作线程队列

时间:2012-04-16 19:44:01

标签: wpf multithreading mvvm backgroundworker

我正在尝试使用MVVM模式实现我的第一个应用程序。我已经做了大部分工作,但是现在我遇到了以下问题(恕我直言)很常见的情况:

Button(查看)将调用方法(模型)。使用ICommand(ViewModel)非常简单。但是如果必须执行耗时的操作该怎么办?

我当前的解决方案要求我实施包含WorkQueue的{​​{1}}类。 WorkQueueItems有一个与之关联的线程,它执行WorkQueue s。每个WorkQueueItem都有WorkQueueItemNameStatus,在执行期间会更新。 每个Progress都有自己的Window - 可视化为WorkQueue

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

我对WorkQueue并不熟悉 - 强硬的基本概念似乎朝着这个方向发展。我很乐意看到一个解决方案,我可以将RoutedCommand绑定到一个命令/事件,然后命令/事件冒泡到包含WorkQueueItem的{​​{1}},并将其添加到Window的{ {1}}。

我还考虑过让Window成为单身人士 - 但这只有在我一次只有一个WorkQueue时才有效。

2 个答案:

答案 0 :(得分:4)

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

如果说你的命令需要更新View Model上的属性,但它必须等待信息,你只需启动一个任务来执行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));

将其分解为可管理的部分,我们starting a new task调用了一些方法IEnumerable<Foo> FindData(string)。这是你一直写的普通的无聊的同步代码。可能它已经存在于你的视图模型中了!

接下来,我们使用ContinueWith告诉框架start a new task when that one finishes,但要在WPF Dispatcher instead上执行此操作。这样可以避免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集合之间的任务添加:

    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,或者可以扩展为封装CancellationToken以支持将来取消。

答案 1 :(得分:1)

我不确定我的问题是否正确,但我觉得在Dispatcher中使用buil可以解决您的问题而且您不需要手动实现WorkQueue,因为Dispatcher会为您实现这样的队列并且能够使用预定义的优先级集将“工作项”分派到UI /任何thred。您可以使用Dispatcher.Invoke()Dispatcher.BeginInvoke()

同步或异步执行操作

有用的链接: