2013-08-06 2 views
5

В моем приложении MVVM моя модель просмотра называет 3 разных метода обслуживания, преобразует данные из каждого в общий формат и затем обновляет пользовательский интерфейс с использованием уведомлений о свойствах/наблюдаемых коллекций и т. Д.Как продолжить выполнение нескольких задач без блокировки потока пользовательского интерфейса?

Каждый метод уровня обслуживания запускает новый 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. Это должно обновить пользовательский интерфейс и отобразить элемент управления BusyIndicator, но он не работает. Я также попытался добавить простые свойства строки и привязать их, и они также не обновляются. Функциональность IsBusy является частью базового класса и работает в других моделях представления, где у меня не более одной задачи, поэтому я не думаю, что есть проблема с уведомлением об имуществе или привязкой данных в XAML.

Все привязки данных, по-видимому, обновляются после завершения всего метода. Я не вижу никаких «исключений в первый раз» или ошибок привязки в окне вывода, что заставляет меня думать, что поток пользовательского интерфейса каким-то образом блокируется перед вызовом WaitAll.

Номер 2: Кажется, я возвращаю неправильные задачи из методов обслуживания. Я хочу, чтобы все после WaitAll запускалось после того, как модель представления преобразует все результаты всех методов обслуживания в обратные вызовы. Однако, если я возвращаю задачу продолжения из метода службы, продолжение никогда не будет вызвано, а WaitAll ждет навсегда. Странная вещь: контроль пользовательского интерфейса, связанный с ICollectionView, фактически отображает все правильно, я предположил, что это потому, что Data является наблюдаемой коллекцией, а CollectionViewSource знает об изменениях, связанных с коллекцией.

ответ

9

Вы можете использовать TaskFactory.ContinueWhenAll, чтобы построить продолжение, которое выполняется, когда все задачи ввода завершены.

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()); 

Обратите внимание, что это становится проще, если вы используете C# 5-х await/async синтаксис: не

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; 
} 

Однако, если я верну задачу продолжения от метода обслуживания продолжения не вызывается и WaitAll ждет навсегда

Проблема заключается в том, что ваша задача продолжения требует потока пользовательского интерфейса, и вы блокируете поток пользовательского интерфейса в вызове WaitAll. Это создает тупик, который не будет разрешаться.

Исправление выше должно исправить это - вы хотите вернуть продолжение как задачу, так как это то, что вам нужно дождаться завершения, но используя TaskFactory.ContinueWhenAll, вы освобождаете поток пользовательского интерфейса, чтобы он мог обрабатывать эти продолжения.

Обратите внимание, что это еще одна вещь, которая получает упрощена с C# 5. Вы можете написать свои другие методы, как:

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); 
} 

Это, как говорится, это, как правило, лучше писать методы, чтобы вернуть Task<T> вместо предоставления обратные вызовы, поскольку это упрощает оба конца использования.

+0

Я откладываю использование async/await, потому что я не уверен, что он «просто работает» с командами WPF и Prism DelegateCommand. Task.ContinueWhenAll, похоже, не существует для меня, мне нужно ссылаться на некоторую библиотеку расширений TPL? – BenCr

+0

@BenCr Извините, это TaskFactory.ContinueWhenAll. В общем, ожидание/асинхронный материал работает * лучше * чем продолжение задач с WPF. Главное отличие состоит в том, что вам не нужно прыгать через сумасшедшие обручи, чтобы справляться с исключениями. –

+0

Спасибо, Рид, похоже, он работает намного лучше. Надеюсь, кто-то может предоставить объяснение блокировки до того, как WaitAll был вызван, но это, безусловно, устраняет проблемы 1 и 2. Я дам асинхронный/ожидающий материал перейти на следующую итерацию и посмотреть, как я нахожусь. – BenCr