【问题标题】:How to implement a background worker thread queue如何实现后台工作线程队列
【发布时间】:2012-04-16 19:44:01
【问题描述】:

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

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

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

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

我对@9​​87654337@s 不是很熟悉——基本概念似乎朝着这个方向发展。希望看到一个解决方案,我可以将WorkQueueItem 绑定到命令/事件,然后冒泡到包含Window 的位置,然后将其添加到WindowWorkQueue

我也考虑过让 WorkQueue 成为单例 - 但这只有在我一次只有一个 Window 时才有效。

【问题讨论】:

标签: wpf multithreading mvvm backgroundworker


【解决方案1】:

使用后来的 .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));

将其分解为可管理的部分,我们是 starting a new task,它调用了一些方法 IEnumerable&lt;Foo&gt; 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 集合添加任务和从 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 以支持将来取消。

【讨论】:

  • 感谢 sn-p 和详细的解释。似乎我(再次)重新发明了轮子。但我的主要问题是我还想在窗口的状态栏中显示挂起的操作/任务/工作项。为此,我需要找到触发命令的窗口的唯一一个工作队列。
  • @Korexio:您可以添加一个帮助类来封装开始的新任务并使用它来跟踪它们。然后从任务框架中借用,您可以使用它来删除完成的项目。需要更多的工作来支持处理任务的不同状态,但这将是另一个问题。我希望这会有所帮助。
  • 感谢这个非常棒的例子。这正是我一直在寻找的。感谢您的帮助!
【解决方案2】:

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

有用的链接:

【讨论】:

  • 使用Dispatcher 会消除我的WorkQueue 类——但是当我想添加一个新的WorkQueueItem 时,我仍然必须找到合适的Dispatcher。此外,如果我想可视化待处理的项目,我必须为 Dispatcher 创建一个 ViewModel 并使用 Hooks 使我的 ViewModel 和 Dispatcher 保持同步。 Dispatchers 真的用于将工作分派到后台线程吗?
  • @Korexio:调度程序编组在 UI 线程上工作。在使用控件、绑定等时,这可以绕过 WPF 线程源限制。
  • @sixlettervariables:好的,这就是我一直使用它的目的。我的问题是我想分派给后台工作人员。我根本没有跨线程问题,我只是不知道如何将后台线程与窗口相关联,以便将窗口中的所有命令/工作项分派给这个后台工作人员。到目前为止,我发现的唯一方法是将线程的引用传递给我在窗口中创建/显示的每个 ViewModel...
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-05-30
  • 2014-08-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多