1

MVVM パターンを使用して最初のアプリケーションを実装しようとしています。私はほとんどのことをうまく機能させることができましたが、現在、次の(私見ではかなり一般的な)シナリオの問題に直面しています。

(ビュー) を押すとButton、メソッド (モデル) が呼び出されます。ICommand(ViewModel)を使用すると、これは非常に簡単です。しかし、時間のかかる操作を実行する必要がある場合はどうすればよいでしょうか?

私の現在のソリューションでは、WorkQueueを含むクラスを実装する必要がありましたWorkQueueItems。にWorkQueueは、 を実行するスレッドが関連付けられていWorkQueueItemます。それぞれWorkQueueItemに 、Name、 がStatusあり、Progress実行中に更新されます。それぞれWindowに独自のWorkQueue- として視覚化された がありStatusBarます。

私の問題: ViewModel はどのように適切なを見つけることができますWorkQueueか? 作成した各 ViewModel にを渡す必要WorkQueueがありますか (これは本当に面倒です)。または、使用できる他のメカニズムはありますか?

私はRoutedCommands にあまり詳しくありません - 基本的な概念はこの方向に進んでいるようです。見たいのは、 a を Command/Event にバインドし、それが に追加された場所にWorkQueueItemバブルアップするソリューションです。WindowWindowWorkQueue

シングルトンの作成も検討しWorkQueueましたが、これは一度に 1 つしかない場合にのみ機能Windowします。

4

2 に答える 2

4

後の.NetFramework(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));

これを管理可能な部分に分解して、いくつかのメソッドを呼び出す新しいタスクを開始してIEnumerable<Foo> FindData(string)います。これは、あなたがいつも書いてきた昔ながらの退屈な同期コードです。おそらくそれはあなたのビューモデルにすでに存在しています!

次に、フレームワークに、の使用が終了したときに新しいタスクを開始するように指示しますが、代わりにWPFディスパッチャーで実行します。これにより、UI要素に関するクロススレッドの問題の煩わしさを回避できます。ContinueWith

ヘルパークラスを使用して監視するためにこれを拡張できます。

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でこのクラスを使用すると、そのインスタンスをウィンドウに追加することになりますViewModel

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

次に、タスクマネージャーにメソッドを追加して、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のプロパティをUIに公開することも、将来のキャンセルをサポートするためTaskにをカプセル化するように拡張することもできます。CancellationToken

于 2012-04-16T20:09:00.380 に答える
1

質問が正しいかどうかはわかりませんが、Dispatcher でビルドを使用すると問題が解決し、 DispatcherWorkQueueがそのようなキューを実装し、「ワーカー アイテム」を UI/any にディスパッチできるため、手動で実装する必要はないと感じています。事前定義された一連の優先順位を使用してスレッド化されます。Dispatcher.Invoke()またはを使用して、操作を同期的または非同期的に実行できます。Dispatcher.BeginInvoke()

便利なリンク:

于 2012-04-16T19:49:57.197 に答える