2013-08-06 13 views
5

MVVMアプリケーションでは、私のビューモデルは3つの異なるサービスメソッドを呼び出し、それぞれのデータを共通のフォーマットに変換し、プロパティ通知/観測可能なコレクションなどを使用してUIを更新します。UIスレッドをブロックせずに複数のタスクを続行するにはどうすればよいですか?

サービスレイヤの各メソッドは、 Taskを返し、ビューモデルにTaskを返します。ここに私のサービス方法の例があります。

public class ResourceService 
{ 
internal static Task LoadResources(Action<IEnumerable<Resource>> completedCallback, Action<Exception> errorCallback) 
{ 
    var t = Task.Factory.StartNew(() => 
    { 
     //... get resources from somewhere 
     return resources; 
    }); 

    t.ContinueWith(task => 
    { 
     if (task.IsFaulted) 
     { 
      errorCallback(task.Exception); 
      return; 
     } 
     completedCallback(task.Result); 
    }, TaskScheduler.FromCurrentSynchronizationContext()); 

    return t; 
} 
} 

ここでは、呼び出し元のコードとビューモデルの他の関連する部分は、これはほとんどの作品は...

private ObservableCollection<DataItem> Data = new ObservableCollection<DataItem>(); 

public ICollectionView DataView 
{ 
    get { return _dataView; } 
    set 
    { 
     if (_dataView != value) 
     { 
      _dataView = value; 
      RaisePropertyChange(() => DataView); 
     } 
    } 
} 

private void LoadData() 
{ 
    SetBusy("Loading..."); 

    Data.Clear(); 

    Task[] tasks = new Task[3] 
    { 
     LoadTools(), 
     LoadResources(), 
     LoadPersonel() 
    }; 

    Task.WaitAll(tasks); 

    DataView = CollectionViewSource.GetDefaultView(Data); 
    DataView.Filter = FilterTimelineData; 

    IsBusy = false; 
} 

private Task LoadResources() 
{ 
    return ResourceService.LoadResources(resources => 
    { 
     foreach(var r in resources) 
     { 
      var d = convertResource(r); 
      Data.Add(d); 
     } 
    }, 
    error => 
    { 
     // do some error handling 
    }); 
} 

ですが、小さな問題がいくつかあります。

番号1:最初にSetBusyを呼び出すときに、私がタスクを開始する前に、WaitAllを呼び出す前に、IsBusyプロパティをtrueに設定します。 UIを更新し、BusyIndi​​catorコントロールを表示する必要がありますが、機能していません。また、単純な文字列プロパティを追加しようとしましたが、それらをバインドすると、どちらも更新されていません。 IsBusy機能は基本クラスの一部であり、複数のタスクが実行されていない他のビューモデルでも機能するため、XAMLのプロパティ通知やデータバインディングに問題があるとは思われません。

すべてのデータバインディングは、メソッド全体が完了した後に更新されたようです。 UIのスレッドが何らかの形でWaitAllの呼び出しの前にブロックされていると私に信じさせる出力ウィンドウの「最初の例外」またはバインディングエラーは表示されません。

番号2:サービスメソッドから間違ったタスクを返すようです。ビューモデルがすべての結果をコールバック内のすべてのサービスメソッドから変換した後に、WaitAllの後に実行する必要があります。しかし、サービスメソッドから継続タスクを返すと、継続は呼び出されず、WaitAllは永遠に待機します。奇妙なことは、ICollectionViewにバインドされたUIコントロールが実際にすべて正しく表示されていることです。これはデータが観測可能なコレクションであり、CollectionViewSourceがコレクションの変更イベントを認識しているためです。

答えて

9

TaskFactory.ContinueWhenAllを使用すると、入力タスクがすべて完了したときに実行される継続を構築できます。

private async void LoadData() 
{ 
    SetBusy("Loading..."); 

    Data.Clear(); 

    Task[] tasks = new Task[3] 
    { 
     LoadTools(), 
     LoadResources(), 
     LoadPersonel() 
    }; 

    await Task.WhenAll(tasks); 

    DataView = CollectionViewSource.GetDefaultView(Data); 
    DataView.Filter = FilterTimelineData; 

    IsBusy = false; 
} 

私はサービスメソッドから継続タスクを返す場合は継続が呼ばれることは決してありません:これはあなたがC#5のawait/async構文を使用する場合に簡単になるという

Task[] tasks = new Task[3] 
{ 
    LoadTools(), 
    LoadResources(), 
    LoadPersonel() 
}; 

Task.Factory.ContinueWhenAll(tasks, t => 
{ 
    DataView = CollectionViewSource.GetDefaultView(Data); 
    DataView.Filter = FilterTimelineData; 

    IsBusy = false; 
}, CancellationToken.None, TaskContinuationOptions.None, 
    TaskScheduler.FromCurrentSynchronizationContext()); 

注意そしてWaitAllは永遠に待ちます

問題は継続タスクでUIスレッドが必要で、WaitAll呼び出しでUIスレッドをブロックしていることです。これによって解決されないデッドロックが作成されます。

これを修正する必要があります。完了を待つ必要があるので、タスクとして継続を返す必要がありますが、TaskFactory.ContinueWhenAllを使用すると、UIスレッドを解放してその継続を処理できるようになります。言われて、それがTask<T>代わりの提供を返すためにメソッドを書くために、通常より良いということ

internal static async Task LoadResources(Action<IEnumerable<Resource>> completedCallback, Action<Exception> errorCallback) 
{ 
    try 
    { 
    await Task.Run(() => 
    { 
     //... get resources from somewhere 
     return resources; 
    }); 
    } 
    catch (Exception e) 
    { 
    errorCallback(task.Exception); 
    } 

    completedCallback(task.Result); 
} 

:これはあなたのようにあなたの他のメソッドを書くことができますC#5で簡略化されます別のものであることを

注意コールバックを使用すると、両方の使用が簡単になります。

+0

私はWPFコマンドとPrism DelegateCommandで「うまく動作する」かどうかわからないので、async/awaitを使用していません。 Task.ContinueWhenAllは私のために存在しないようですが、私はいくつかのTPL拡張ライブラリを参照する必要がありますか? – BenCr

+0

@BenCrごめんなさい - タスクのファクトリー.ContinueWhenAll。一般的に、待機/非同期のものは、WPFでのタスク継続よりも優れています。主な違いは、あなたが例外を処理するために狂気のフープを飛び越える必要がないということです。 –

+0

ありがとうリード、これはもっとうまくいくようです。うまくいけば、WaitAllが呼び出される前に誰かがブロッキングの説明を与えることができるかもしれないが、これは確かに問題1と2を修正している。私は非同期/待っているものに次の反復で行き、 – BenCr

関連する問題